This codelab shows how easy it is to create your own interactive Slice on Android. By the end of the codelab, you'll have customized an interactive Slice that you can interact with from outside of your app.
Slices are UI templates that can display rich, dynamic, and interactive content outside of your app in other surfaces, such as the Google Assistant.
Slices help users perform tasks faster by enabling engagement outside of the fullscreen app experience.
Example 1: Check-in or check-out of your rental:
Example 2: Control music on a surface outside of your app:
A Sice represents a piece of app content; you should design your Slices with the following in mind:
Let's get started!
If you run into any issues (code bugs, grammatical errors, unclear wording, etc.) as you work through this codelab, please report the issue via the Report a mistake link in the lower left corner of the codelab.
You can clone our starter project to help you get you started quickly.
If you have Git installed, run the command below. You can check by typing git --version
in the terminal / command line and verify that it executes correctly.
git clone https://github.com/googlecodelabs/slices-basic-codelab
If you do not have Git, you can
Start Android Studio, and select Open an existing Android Studio project from the Welcome screen. Open the project directory and double click the build.gradle
file in the slices-basic-codelab directory:
After the project has loaded, you might see an alert that says "Unregistered VCS root detected". We won't be pushing any changes to the repo, so you can click "Ignore" or the "X" in the upper-right corner.
If you are in the Android view, you should see a set of folder icons in the upper-left corner of the project window. These icons should look similar to those in the screenshot below. If you are in the Project view, expand the slices-basic-codelab project to see the set of folder icons.
There are two folder icons: base
and complete
. Each folder icon represents a module.
Please note that Android Studio might take several seconds to compile the project in the background for the first time. During this time, you'll see a spinner in the status bar at the bottom of Android Studio:
Android Studio needs to pull in some necessary components, so be sure to wait until the processes finishes before making code changes.
In addition, if you get a prompt that says something similar to "Reload for language changes to take effect?", choose "Yes".
Once Android Studio builds the project for the first time, you're ready to create a Slice. You'll start in the base
module and build a Slice from scratch. You'll then add code from each step to base
.
The complete
module can be used for checking your work or for you to reference if you encounter any issues.
Here's an overview of key components:
AndroidManifest
- In your app's manifest, you need to declare a SliceProvider
for external services to request a Slice.
TemperatureSliceProvider
- Use this code to create and style the Slice
.MainActivity
- Contains Temperature Activity
we will be mirroring in our Slice
.TemperatureBroadcastReceiver
- Handles all temperature change requests from both Activity
and Slice
.If you need help setting up an Android emulator, see Launch a emulator and run your app.
First, we need to install the Slice Viewer so we can view our Slice outside of our app.
$ cd ~/local/path/to/slices-basic-codelab $ adb install -r -t slice-viewer.apk Performing Streamed Install Success
Let's run our app and trigger the Slice Viewer. Please note that the Android device or emulator must be running Android KitKat or higher.
base
configuration from the drop-down selector and click the green triangle (Run) button next to it:Slices
.In this step, you've learned the following:
Let's look at the building block of Slices, the SliceProvider
.
In this section, you'll create the scaffolding needed for a generic Slice.
Every Slice has a corresponding Uri
. Our particular Uri
is below.
content://com.android.example.slicecodelab/temperature
We'll talk more about the Uri
later in the codelab.
Let's now discuss design.
Remember, we'll create a Slice that mimics our main temperature control activity.
Ideally, the Slice would be hooked up to an actual thermostat, but for this codelab, the temperature controls will just adjust a number within our app. Our Slice will contain the following components:
We should end up with something like the screenshot below. Note that the Slice is just the small rectangle that displays the temperature:
First we need to add the right dependencies. Remember, everything will be done in the base
module. If you run into issues along the way, check the complete
module to see a complete and working version of the codelab.
If you're using Java, instead of the KTX dependency, you'll need to add slice-core
and slice-builders
to your app's gradle file:
dependencies {
implementation 'androidx.slice:slice-core:1.0.0'
implementation 'androidx.slice:slice-builders:1.0.0'
}
Because we are using Kotlin, we can use the Kotlin slice-builders-ktx
library. Part of the Android KTX extension libraries, this library provides extensions that simplify creating Slices.
dependencies {
implementation 'androidx.slice:slice-builders-ktx:1.0.1'
}
In the base
module, we've added this dependency for you.If you'd like to take a look at the dependencies, open the build.gradle
file.
To build Slices, we must extend SliceProvider
. We've provided a barebones class, but we need to add it to the manifest.
In the base
module, open AndroidManifest
, and search for TODO: Step 2.1, Add SliceProvider to Manifest.
Below that comment, add the following code snippet:
<!-- TODO: Step 2.1, Add SliceProvider to Manifest. -->
<provider
android:authorities="com.example.android.slicesbasiccodelab"
android:name=".TemperatureSliceProvider"
android:grantUriPermissions="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.app.slice.category.SLICE" />
</intent-filter>
</provider>
As you might have guessed, our SliceProvider
is named TemperatureSliceProvider.
Each Slice has a Uri
, and this provider performs the mapping between Uri
s and Slice
s. When a surface wants to display your Slice
, it will do so by binding to a Uri
.
While the path of the Uri
is usually handled within the SliceProvider
itself, the host is in the authorities
field.
The additional attributes help other surfaces to find and request our Slice (more info).
Next we need to alert the TemperatureSliceProvider
(a ContentProvider
) when the temperature changes. This will update our temperature in both our MainActivity
and our Slice
.
In the base
module, open the MainActivity
file, and search for TODO: Step 2.2, Notify TemperatureSliceProvider the temperature changed.
Add the following:
// TODO: Step 2.2, Notify TemperatureSliceProvider the temperature changed.
if (temperature != newTemperature) {
temperature = newTemperature
// Notify slice via URI that the temperature has changed so they can update views.
// NOTE: TemperatureSliceProvider is derived from ContentProvider, so we are
// actually assigning a ContentProvider to this authority (URI).
val uri = TemperatureSliceProvider.getUri(context, "temperature")
context.contentResolver.notifyChange(uri, null)
}
Now we are ready to start building out our TemperatureSliceProvider
.
Let's continue to implement TemperatureSliceProvider.
In the base
module, open the TemperatureSliceProvider
file and search for TODO: Step 2.3, Review non-nullable Context variable.
It should look like this:
override fun onCreateSliceProvider(): Boolean {
// TODO: Step 2.3, Review non-nullable Context variable.
contextNonNull = context ?: return false
return true
}
The onCreateSliceProvider()
method is where you initialize all variables for your Slice. In our case, we want to make sure our context is non-nullable. This simplifies code much later in our class, but more importantly, we can't create Slices without the Context
.
In the base
module, search for TODO: Step 2.4, Define a slice path in onBindSlice()
within TemperatureSliceProvider
, and add the code below.
// TODO: Step 2.4, Define a slice path.
when (sliceUri.path) {
"/temperature" -> return createTemperatureSlice(sliceUri)
}
When a surface needs a Slice, the system will trigger the onBindSlice()
method and pass in a Uri
.
When onBindSlice()
is triggered, we should return a Slice
if we have a matching Uri
. We need to check the path to differentiate between different Slices. In this case, we have only one Slice available for the temperature/
path.
Run the base module. You should see a screen similar to the following:
Click the Launch Slice Viewer button. You should see the Slice that we just created, similar to the image below:
In this step, you've learned the following:
SliceProvider
classSliceProvider
to your manifestUri
requestNext, we'll dive into building an interactive Slice.
In this section, we'll build an interactive Slice.
You probably noticed in the last section that the Slice was built automatically in the method createTemperatureSlice()
.
This is just a simple private method where we construct our Slice. Let's take a more detailed look at how to build a Slice.
In the base
module, open the TemperatureSliceProvider
file, and search for Step 3.1, Review Slice's ListBuilder.
You should see the following code:
// TODO: Step 3.1, Review Slice's ListBuilder.
return list(contextNonNull, sliceUri, ListBuilder.INFINITY) {
setAccentColor(ContextCompat.getColor(contextNonNull, R.color.slice_accent_color))
...
The main building block of a Slice is the ListBuilder
.
ListBuilder
allows you to add different types of rows that are displayed in your Slice, and
because we are using the Slice KTX library, we can use the DSL version of ListBuilder
which allows us to just use list()
and include some general arguments before defining the
structure of our Slice
(the main part below).
Here we're constructing our Slice's ListBuilder
with the context, Uri
, and duration. Since our Slice is not time-sensitive, we can use a time-to-live value of INFINITY
. For more information, check out the ListBuilder
documentation.
Next, let's customize our content.
In the base
module, open the TemperatureSliceProvider
file, and search for TODO: Step 3.2, Create a Slice Header (title and primary action).
Replace the current header{}
block with the code below.
// TODO: Step 3.2, Create a Slice Header (title and primary action).
header {
title = getTemperatureString(contextNonNull)
// Launches the main Activity associated with the Slice.
primaryAction = SliceAction.create(
PendingIntent.getActivity(
contextNonNull,
sliceUri.hashCode(),
Intent(contextNonNull, MainActivity::class.java),
0
),
IconCompat.createWithResource(contextNonNull, R.drawable.ic_home),
ListBuilder.ICON_IMAGE,
contextNonNull.getString(R.string.slice_action_primary_title)
)
}
The first row of your Slice should always be a header. The header supports a title, subtitle, and a tappable action (usually used to launch an Activity).
If you read through the code, we are setting two things:
PendingIntent
launches MainActivity
Icon
to displayIf we wanted additional rows, we could add them via a RowBuilder
or GridBuilder
.
In our case, we only want one row, but we do want to add a couple of additional actions.
In the base
module, open the TemperatureSliceProvider
file, and search for TODO: Step 3.3, Add Temperature Up Slice Action.
Below the comment, add the following code.
// TODO: Step 3.3, Add Temperature Up Slice Action.
addAction(
SliceAction.create(
createTemperatureChangePendingIntent(getTemperature() + 1),
IconCompat.createWithResource(contextNonNull, R.drawable.ic_temp_up),
ListBuilder.ICON_IMAGE,
contextNonNull.getString(R.string.increase_temperature)
)
)
A SliceAction
represents an action. It supports tappable icons, custom toggle icons, and default toggles. You can use a SliceAction
to add extra buttons to your row.
In our case, we're adding a button (increase temperature) to our header.
Now let's add the other button to decrease the temperature.
In the base
module, open the TemperatureSliceProvider
file, and search for Step 3.4, Add Temperature Down Slice Action.
Below the comment, add the following code.
// TODO: Step 3.4, Add Temperature Down Slice Action.
addAction(
SliceAction.create(
createTemperatureChangePendingIntent(getTemperature() - 1),
IconCompat.createWithResource(contextNonNull, R.drawable.ic_temp_down),
ListBuilder.ICON_IMAGE,
contextNonNull.getString(R.string.decrease_temperature)
)
)
Now we have two SliceAction
s built right into our Slice.
Now our Slice is finished, but before we test it, let's review the PendingIntent
code.
In the base
module, open the TemperatureSliceProvider
file, and search for Step 3.5, Review Pending Intent Creation.
You should see the following code:
// TODO: Step 3.5, Review Pending Intent Creation.
// PendingIntent that triggers an increase/decrease in temperature.
private fun createTemperatureChangePendingIntent(value: Int): PendingIntent {
val intent = Intent(ACTION_CHANGE_TEMPERATURE)
.setClass(contextNonNull, TemperatureBroadcastReceiver::class.java)
.putExtra(EXTRA_TEMPERATURE_VALUE, value)
return PendingIntent.getBroadcast(
contextNonNull, requestCode++, intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
You don't need to add any code here. We are simply using a PendingIntent
to trigger the TemperatureBroadcastReceiver
in our app. This increases/decreases the temperature.
Let's run our app and see how this looks.
Run the base
module. After the app has launched, click the Launch Slice Viewer button.
After that action, you should see a screen similar to the following:
Feel free to play with the temperature, and then launch the main activity by clicking on the Slice. You should see the same value in the app!
In this step you've learned:
SliceAction
s to make the Slice interactiveCongratulations! You're a Slices wizard! The earth trembles at your newfound ability!
To learn more about Slice
s, check out our getting started guide.
For more Slices info, watch these great videos: