Slices are a new way to embed your app content in other surfaces. Slices are supported via a flexible templating system

Their templated content allow slices to feel at home in whatever surface hosts them, but their dynamic nature aims to be much more rich and updatable than existing APIs.

What is a slice?

A slice represents a piece of app content; it should be:

This codelab will walk you through creating an app that provides an interactive slice.

What you'll need

Download the code

Click the following link to download all the code for this codelab:

Code on GitHub

Unpack the downloaded zip file. This will unpack a root folder (slices-basic-codelab), which contains one folder for each step of this codelab, along with all of the resources you will need.

The step-NN folders contain the desired end state of each step of this codelab. They are there for reference.

Open up the source from step-00 in the code lab, build the project, and run it. You should see our simple temperature controls app. Tapping on each of the buttons will change the displayed temperature.

We're going to create a slice for temperature controls. Ideally these would be hooked up to an actual thermostat, but for the purposes of this code lab, the temperature controls will just adjust the number within our app. The key components of this slice are:

  1. The current temperature
  2. Controls to raise and lower the temperature
  3. A primary action (tapping on the slice)

First ensure you're building with the necessary slice libraries. 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-alpha1'
    implementation 'androidx.slice:slice-builders:1.0.0-alpha1'
}

Note:

There are a couple of framework APIs for Slices as well as APIs in Jetpack. You will only need APIs in Jetpack so when importing, import the androidx version.

To be able to build slices, first create a class that extends SliceProvider. Each slice has a Uri, and the provider performs the mapping between Uris and Slices. When a surface wants to display your Slice, it will do so by binding to a Uri.

Your slice provider should be defined in your AndroidManifest so that your slice can be found by other apps. It is safe to export SliceProviders because they handle all permission checks internally.

    <application
        ...

        <!-- To provide slices you must define a slice provider -->
        <provider
            android:authorities="com.android.example.slicecodelab"
            android:name=".MySliceProvider"
            android:exported="true">
        </provider>

        ...
    </application>

For this codelab we'll handle a single slice defined by this Uri:

content://com.android.example.slicecodelab/temperature

Let's implement our SliceProvider and handle this Uri:

public class MySliceProvider extends SliceProvider {
    @Override
    public boolean onCreateSliceProvider() {
        return true;
    }

    public Slice onBindSlice(Uri sliceUri) {
        switch(sliceUri.getPath()) {
            case "/temperature":
                return createTemperatureSlice(sliceUri);
        }
        return null;
    }

    @Nullable
    private Slice createTemperatureSlice(Uri sliceUri) {
        // TODO: complete in a later step.
        return null;
    }
}

To build a slice you can use one of our slice template builders. We'll first create a row with text indicating the current temperature.

    private Slice createTemperatureSlice(Uri sliceUri) {
        // Construct our parent builder
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);

        // Construct the builder for the row
        ListBuilder.RowBuilder temperatureRow = new ListBuilder.RowBuilder(listBuilder);

        // Set title
        temperatureRow.setTitle(MainActivity.getTemperatureString(getContext()));

        // TODO: add actions to row; in later step

        // Add the row to the parent builder
        listBuilder.addRow(temperatureRow);

        // Build the slice
        return listBuilder.build();
    }

Test your slice

The purpose of slices is to allow your app content to be surfaced outside of your app. To test that your slice works, you'll need an app that can present slices.

Follow the "Download and install the Slice Viewer" section of the Getting Started guide to instrument the SliceViewer app to show your Slice. Where necessary, use the correct Slice Uri (slice-content://com.android.example.slicecodelab/temperature) instead of the default value in the guide.

Note: Slices can only be shown in an app if that app has permission to access those slice Uris. The first time Slice Viewer accesses your slice, you'll see a permission slice. Tapping on it will take you through the steps to grant access.

Once you've granted permission, you'll see your slice:

Note that if you leave the Slice Viewer app, change the temperature in the Temp Controls app, and return to the Slice Viewer app, the temperature in the slice will update to reflect the new value.

A slice that tells the temperature is nice, but slices are better when they have useful actions, so let's add some.

This slice has three actions:

  1. Increase temperature
  2. Decrease temperature
  3. Open the Temp Controls app

The third action will be the primary action for this row. The primary action is invoked when the row is tapped.

To define these, you'll create a SliceAction for each action. SliceActions require a PendingIntent to respond to action events (handling a change in temperature, for example). Let's define a BroadcastReceiver to handle the PendingIntents for our slice.

We'll define a custom intent action for temperature change events, and an intent extra to retrieve the new value for the temperature.

public class MyBroadcastReceiver extends BroadcastReceiver {

    public static String ACTION_CHANGE_TEMP = "com.android.example.slicecodelab.ACTION_CHANGE_TEMP";
    public static String EXTRA_TEMP_VALUE = "com.android.example.slicecodelab.EXTRA_TEMP_VALUE";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (ACTION_CHANGE_TEMP.equals(action) && intent.getExtras() != null) {
            int newValue = intent.getExtras().getInt(EXTRA_TEMP_VALUE, MainActivity.sTemperature);
            updateTemperature(context, newValue);
        }
    }
}

Don't forget to add your BroadcastReceiver to your AndroidManifest.xml

    <application
        ...

        <receiver android:name=".MyBroadcastReceiver"/>

        ...
    </application>

Now that our BroadcastReceiver is defined we can handle PendingIntents. To construct our temperature change intent we can add the following to our SliceProvider:

    // This static int should go in your SliceProvider.
    private static int sReqCode = 0;

    // This method constructs a PendingIntent to trigger the BroadcastReceiver.
    private PendingIntent getChangeTempIntent(int value) {
        Intent intent = new Intent(MyBroadcastReceiver.ACTION_CHANGE_TEMP);
        intent.setClass(getContext(), MyBroadcastReceiver.class);
        intent.putExtra(MyBroadcastReceiver.EXTRA_TEMP_VALUE, value);
        return PendingIntent.getBroadcast(getContext(), sReqCode++, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
    }

And we can create SliceActions to wrap our app's PendingIntents:

private Slice createTemperatureSlice(Uri sliceUri) {
        // Define the actions used in this slice
        SliceAction tempUp = new SliceAction(getChangeTempIntent(MainActivity.sTemperature + 1),
                IconCompat.createWithResource(getContext(), R.drawable.ic_temp_up),
                "Increase temperature");

        SliceAction tempDown = new SliceAction(getChangeTempIntent(MainActivity.sTemperature - 1),
                IconCompat.createWithResource(getContext(), R.drawable.ic_temp_down),
                "Decrease temperature");

        // The primary action; this will activate when the row is tapped
        Intent intent = new Intent(getContext(), MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, 0);
        SliceAction openTempActivity = new SliceAction(pendingIntent,
                IconCompat.createWithResource(getContext(),
                        R.drawable.ic_launcher_foreground), "Temperature controls");

...
}

And finally, we'll add these actions to our Slice:

private Slice createTemperatureSlice(Uri sliceUri) {
...
        // Construct our parent builder
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);

        // Construct the builder for the row
        ListBuilder.RowBuilder temperatureRow = new ListBuilder.RowBuilder(listBuilder);

        // Set title
        temperatureRow.setTitle(MainActivity.getTemperatureString(getContext()));

        // Add the actions to appear at the end of the row
        temperatureRow.addEndItem(tempDown);
        temperatureRow.addEndItem(tempUp);
        // Set primary action for the row
        temperatureRow.setPrimaryAction(openTempActivity);

        // Add the row to the parent builder
        listBuilder.addRow(temperatureRow);

        // Build the slice
        return listBuilder.build();
}

Now let's take a look at the updated slice in Slice Viewer:

When you tap on the main content of the slice, it should open up our Home Slice app. When you tap on the buttons to adjust the temperature, however, nothing seems to happen! Let's fix that.

Updating your slice

To let a slice know that content for it has updated, call notifyChange on the Uri associated with the slice. Notifying a Uri might look like this:

Uri uri = Uri.parse("content://com.android.example.slicecodelab/temperature")
context.getContentResolver().notifyChange(uri, null);

This will trigger a call to SliceProvider#onBindSlice if your slice is being displayed anywhere, allowing you to reconstruct your slice with the updated content.

Let's add this code snippet to the updateTemperature method in MainActivity so that whenever the temperature is updated, any temperature slices that might be displayed will also be updated.

    public static void updateTemperature(Context context, int newValue) {
        newValue = MathUtils.clamp(newValue, 10, 30); // Lets keep temperatures reasonable

        if (newValue != sTemperature) {
            sTemperature = newValue;

            // Should notify the URI to let any slices that might be displaying know to update.
            Uri uri = Uri.parse("content://com.android.example.slicecodelab/temperature")
            context.getContentResolver().notifyChange(uri, null);
        }
    }

Now check out your slice in Slice Viewer. Tapping the temperature controls will correctly update the displayed temperature in the slice.

Congratulations, you've built your first fully functional slice!

For more information on templates and building slices check out:

https://developer.android.com/guide/slices/getting-started