Upcoming and OnDemand Webinars View full list

Share a Slice of your App

Brian Gardner

fancy slice

Providing users quick access to data and controls for your app is part of a good user experience. It lets
them see the info they care about in the places that are important to them.
App Actions provide one way of accomplishing this.
When the user searches in the Google Search app or in the Google Assistant, apps can
provide actions from your app that are relevant to the user’s query. These were previously fairly limited,
mostly just providing a link into your app with some specific data. The new
Slices API provides a way for you to improve the
user experience by showing more information as well as a variety of controls, such as sliders and
toggles, directly in the Search app or the Assistant.

The ability to add custom controls makes slices a very useful tool for giving users control over
your application while they aren’t directly using it. These controls can be provided in a variety
of ways, from buttons, to toggles, and sliders. You can also provide a click action to take the
user into your app when they click on the slice itself. This way the user can use your app’s normal
UI to accomplish their task if they so choose. In this post I’ll discuss how to create a basic
slice, then show how to make it more interactive and valuable to your users. I’ll wrap up with
showing how you can test your slices using the SliceViewer.

Implement a Slice

While not required, a good first step to implementing your slice is to install Android Studio 3.2.
It includes a few niceties such as new lint checks and a template you can use to build your
provider. It also includes a tool to make refactoring from the support libraries to Android X
easier.

In this example I’m going to use the AndroidX dependency to implement my slice, but when I started
my project was still using the old Android support libraries. I first had to convert my project to
the new dependencies before I could get started. Luckily, Android Studio did all of the heavy
lifting for me in this regard. Under “Refactor” there is a menu item called “Migrate to AndroidX”.

migration tool

When you select the migration tool it will prompt you to back up your project just in case. While
it seems unlikely that anything will go horribly wrong in the process it’s probably a good idea to
do it just in case. That said, if you’re using some kind of source control you can easily revert your
changes.

backup prompt

After the backup prompt, Studio will search your project for any usages of the support libraries
and present you with a refactoring preview. This is the same kind of preview you are shown when you
rename something. If nothing looks too out of the ordinary you can click on ‘Do Refactor’ to
perform the migration.

refactor preview

Once the migration completes, Studio will prompt you to do another Gradle sync to pull down the new
AndroidX dependencies. When the sync completes you are ready to start implementing your slice.

First up is adding the slice dependency to your project. Open up your app-level build.gradle file
and add it.

dependencies {
    ...

    implementation "androidx.slice:slice-builders-ktx:1.0.0-alpha4"

    ...
}

Once the dependency is in place, do another Gradle sync to pull it down. Once it is ready you can
move on to implementing your provider.

The mechanism that the system uses to provide a slice is called a
SliceProvider. Instead
of creating one manually you can use the built in templates in Studio 3.2. Right click on the java
folder and select New > Other > Slice Provider.

slice provider template

This will open a form that prompts you for the details of your slice provider. You need to provide
the class name of the object to be created. You also need to provide the authorities that the
provider will handle. If this strikes you as similar to the ContentProvider authorities, that’s
because it is. SliceProvider is actually a subclass of the ContentProvider.

slice provider form

Next, you can provide a host URL. This will be set in the AndroidManifest.xml as the data tag for
your provider’s intent filter. Last, you will supply the path prefix you want your data tag to be
configured with. When you submit you info it will create your provider class and add a registration
in the manifest. If you don’t want to set this info you don’t have to. It just means a little more
work later on to figure out which slice to display.

<provider
    android:name=".JustASliceProvider"
    android:authorities="com.bignerdranch.android.justaslice"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.app.slice.category.SLICE" />
        <data
            android:host="android.bignerdranch.com"
            android:pathPrefix="/"
            android:scheme="http"/>
    </intent-filter>
</provider>

The provider tag in the manifest is filled out with the data you provided. The authority and class
name are in the top-level provider tag. Your host url and path are included on the data tag in the
intent filter. Any time a SliceHost wants to display one of your app’s slices it will create the
view slice intent and send it to your app. Your provider will be started and it will return the
slice back to the host.

The SliceProvider implementation has a lot going on, but for a basic slice there is not much for you
to actually create. The first method to note is the onCreateSliceProvider method. This method is
called when your provider is initialized. You can setup your provider here but you should remember
that this method is run on the main thread so you shouldn’t do any long running operations here. If
your initialization succeeds, then the method should return true to tell the system your provider
is ready.

class JustASliceProvider : SliceProvider() {

    override fun onCreateSliceProvider(): Boolean {
        // initialize your provider dependencies here
        return true
    }

	...
}

The main method we will focus on is the onBindSlice method. This method takes a Uri as a
parameter and will return a Slice. The default implementation from the template indicates what we
are trying to do in this method.

override fun onBindSlice(sliceUri: Uri): Slice? {
    val context = getContext() ?: return null
    return if (sliceUri.path == "/") {
        ListBuilder(context, sliceUri, ListBuilder.INFINITY)
                .addRow { it.setTitle("URI found.") }
                .build()
    } else {
        // Unknown path
        ListBuilder(context, sliceUri, ListBuilder.INFINITY)
                .addRow { it.setTitle("URI not found.") }
                .build()
    }
}

If we do not have access to a context object we return null because we can’t build our slice
without one. The Slice we return from this method depends on the Uri parameter we receive. If we
recognize the path of the Uri we can build the appropriate slice for the data requested. If we
can’t, we just return a plain slice with an error message. Next, let’s tweak the recognized slice
and dive more into how it works.

Slices are created using various builders. The default example uses a ListBuilder object
which allows you to display a list of individual rows to the user. I won’t delve too far into
specifics in this post, but there are plenty of other
templates) you can use to build a rich UI
for your content.

To create your list you need to pass it the context, the slice Uri parameter, and a time to live
parameter. If your information is time sensitive in the slice you will provide a number of
milliseconds that the slice is valid for. In this case the data is not time sensitive so we pass in
the INFINITY constant.

Once you have your builder you can add additional rows to it. This uses a lambda to simplify
creating the RowBuilder, which is the type of the it in the curly braces. In this case we are
just setting the title to indicate we know what the Uri is. Last up, we call build() to create
the slice and return it.

What we will do next is change the title of our row as well as add a primary action to our slice.
Slices require a primary action so the host knows what to do if the user clicks on your slice. In
most cases it will open up the screen in your app that is relevant to the data being displayed.

Creating a SliceAction)
does take a bit of work. You need to provide the PendingIntent to fire when it is clicked. It also
needs an icon, an image mode, and a title.

// Create action for slice
val pendingIntent = Intent(context, MainActivity::class.java).let { intent ->
    PendingIntent.getActivity(context, 0, intent, 0)
}
val bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
val icon = IconCompat.createWithBitmap(bitmap)
val imageMode = ListBuilder.SMALL_IMAGE
val title = "Action Title"
val sliceAction = SliceAction.create(pendingIntent, icon, imageMode, title)

Once the SliceAction is set up you can apply it to your RowBuilder using the setPrimaryAction()
method. While adding it the title can also be changed too.

ListBuilder(context, sliceUri, ListBuilder.INFINITY)
        .addRow { with(it) {
            setTitle("I'm just a boring slice!")
            setPrimaryAction(sliceAction)
        } }
        .build()

This will add your SliceAction to the row item. When the user clicks on that row it will open the
MainActivity of the app.

Test your slice

Once you create your Slice, you’ll no doubt want to test it to ensure it looks correct. Google
created a Slice Viewer project that allows
you to test your slices without needing to implement a SliceView yourself. Go to the linked Github
page and view the releases. Download the slice-viewer.apk file from there to your computer.

Once the apk has been downloaded you need to install it on your device. You can do so using adb.

adb install -r -t slice-viewer.apk

Once the apk is installed on your device you next create a run configuration to easily run your
slice in the viewer. Go to Run > Edit Configurations to get started.

edit configurations

Click the plus icon to add a new run configuration. Select Android App from the list of options.

add new configuration

Fill out the run configuration. You should give it a descriptive name so you know it is for running
your slice. Select your app as the module to run. Under the launch options, select “URL” for your
launch mode and enter in the URL you want it to launch. Use slice-content:// scheme and add your
provider authority as the rest of the URL.

slice configuration

Click OK to save your configuration, then run it to see your slice at work.

almost done

Well, almost. When you run your slice configuration it will start the slice viewer and it will
display something. If you read the text on what is displayed it says that the slice viewer sample
wants to show your slice. You just need to give it permission to. Click on the slice to view the
permission dialog in your app.

slice permission dialog

Accepting the permission will send you back to the slice viewer, where your slice should now be
visible.

successful slice

Issues

While building out this example I ran into a problem creating the IconCompat object for my
SliceAction. I tried using the createWithResource() method to pass in my launcher icon but the
icon was not displayed and I got errors in LogCat saying that it could not find the resource. I
switched to creating a bitmap out of the resource and used the createWithBitmap() method to
create my IconCompat object and that worked instead. I’m not sure if this is an issue with the
createWithResource() method or not, but it’s something to watch out for.

Fin

Minus the hiccups, implementing a slice is fairly quick. In this post we’ve looked at the main
parts of a slice and how to get your device setup to display them. In the following posts we will
take a deeper look at the more creative things you can do with slices. I hope you enjoyed the post.
If you have any questions please feel free to comment below. Thanks for reading!

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project