Android Slices with Examples

Android slices feature allows content of your app to be exposed and displayed in different app. In this post, you can learn about slices with examples.

Table of Contents

Android Slices

Android slices consist of slice provider and slice presenter. A slice provider defines slices in the app to which it belongs to and slice presenter which exists in the same app or different app displays the slices.

Slice provider when requested gives slices. Each slice contains information about a feature of the app. The content is structured. It can contain input controls and actions.

Slice presenter gets the slice using URI which identifies a slice, interprets the structure of slice and displays it.

You can define Android slices in your app so that other apps such as Google search (slices presenter) can display them when users search for something which matches to a feature in your app. This way user can interact with your app, view dynamic content, or take action from the search results itself and land in your app to complete the flow.

Android slices is part of Jetpack and can support devices running Android 4.4 and above.

Setup

To use slices in your app, you need to add slices library to your build.gradle file.

implementation 'androidx.slice:slice-builders:1.0.0'

You need to make sure that you are using androidx libraries, so replace android dependencies with androidx libraries in build.gradle file.

How Android slices Work

Android slices framework provides API for slice provider and presenter. The main components of the slice API are Slice, SlicesProvider, TemplateSliceBuilder and its sub classes, and SliceView.

Slice provider defines slices by using templates, populating data and adding allowed controls and actions.

Slice is identified by URI.

Slice presenter is informed about available slices in a particular app.

Slice presenter gets Slices using URLs and displays them in SliceView.

User interacts with slices, views content and takes action on the slice. Actions take the user to the app to continue with the flow.

Creating Slices Provider

To provide slices from your app, you need to create slice provider and export the provider by adding the provider to manifest file.

You can use slice provider tool in android studio to create basic implementation of slice provider class, add it to manifest file and add slice builders library to build.gradle file. To access the tool, you need to right click app in android studio and select New > Other > Slice provider option.

Then in the slice provider configuration widow, enter provider class name, URI authorities and other details and submit.

Slice Provider Manifest Entry

Following is an example of provider element added to manifest xml file.

<provider android:name=".CouponSlices" android:authorities="com.zoftino.slices" android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.app.slice.category.SLICE" /> <data android:host="zoftino.com" android:pathPrefix="/" android:scheme="https" /> </intent-filter> </provider>

Slice presenter (SliceView) can access the above slice provider using the URI content://com.zoftino.slices/. Each slice provided by the provider is identified with a URL. For example, URL content://com.zoftino.slices/coupon identifies coupon slice within the slices provider.

Slice Provider Example

Slice provider is content provider. Slice provider is created by extending SliceProvider class and implementing onBindSlice() method which takes URI and returns the slice that matches to the URI.

Slices are created using various template builders which are explained in the following sections. Builders are supplied with app feature content and slice actions.

In the following example, ListBuilder is used with RowBuilder, which contains SliceAction, to create Slice.

public class CouponSlices extends SliceProvider { @Override public boolean onCreateSliceProvider() { return true; } public Slice onBindSlice(Uri sliceUri) { if (getContext() == null) { return null; } return getDefaultSlice(sliceUri); } private SliceAction createDefaultActivityAction() { return SliceAction.create( PendingIntent.getActivity( getContext(), 0, new Intent(getContext(), MainActivity.class), 0 ), IconCompat.createWithResource(getContext(), R.drawable.cashback), ListBuilder.ICON_IMAGE, "Coupon App Main" ); } private Slice getDefaultSlice(Uri sliceUri){ return new ListBuilder(getContext(), sliceUri, ListBuilder.ICON_IMAGE) .setAccentColor(Color.GREEN) .addRow( new RowBuilder() .setTitle("Coupons") .setSubtitle("latest coupons from stores") .setPrimaryAction(createDefaultActivityAction()) ) .build(); } }

Slice Presenter or SliceView

You can create slice presenter. You can use own slice presenter or use sample slice viewer available here for testing your slices.

Slices are displayed in SliceView. To create a slice presenter, first add SliceView widget to layout.

<ScrollView android:id="@+id/slice_scroll" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/title_slice"> <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content"> <androidx.slice.widget.SliceView android:id="@+id/slice" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="8dp" android:elevation="6dp" android:paddingEnd="8dp" android:paddingStart="8dp" android:background="@color/colorAccent"/> </FrameLayout> </ScrollView>

In the activity, get the SliceView object and add a slice to it by calling setSlice method of SliceView object passing slice object to it. Slice can be obtained using SliceViewManager object by calling bindSlice() method on it and passing target slice URI.

SliceViewManager sliceViewManager = SliceViewManager.getInstance(this); SliceView sliceView = findViewById(R.id.slice); sliceView.setSlice(sliceViewManager.bindSlice(getIntent().getData()));

Slice view mode can be set by calling setMode() method and passing one of the mode constant such as SliceView.MODE_SHORTCUT, SliceView.MODE_SMALL and SliceView.MODE_LARGE to it.

svSmall.setMode(SliceView.MODE_SHORTCUT);

Following screen shows slice view displaying a slice from slice provider discussed in the previous section. SliceView in shortcut mode displays only icon image which is passed to slice action. Slice images need to be in vector xml format.

SliceAction

Slice is associated with action which takes user from slice presenter app to a feature in the app to which the slice belongs. Slice action makes the user land on a particular screen in the target app using pending intent which is added to the slice. Slices can have multiple actions. Primary action is activated when a slice is clicked. Slice action is created using SliceAction class and added to the template which creates the slice.

To create SliceAction, you can use one of the static methods such as create, createDeeplink and createToggle. These methods take pending intent, icon image and title as input parameters.

Example in the previous section shows how slice action is created using create() method. Now let’s see, how to create slice action with toggle using createToggle() method of SliceAction which takes pending intent, title and boolean value which indicates whether toggle is on or off.

You can get the toggle state of the slice action in the target activity by reading extras from intent. The toggle state is sent as Boolean value with key android.app.slice.extra.TOGGLE_STATE.

if (i.getBooleanExtra("android.app.slice.extra.TOGGLE_STATE", false)) Toast.makeText(this, "Toggle slice action toggle state is: " + i.getBooleanExtra("android.app.slice.extra.TOGGLE_STATE", false), Toast.LENGTH_LONG).show();

You can pass extra data in the pending intent related to slice and capture it in the activity of pending intent to.

public static Slice getCouponSliceWithToggle(Uri sliceUri, Context context){ Intent i = new Intent(context, CouponActivity.class); i.putExtra("cashback", true); SliceAction sa = SliceAction.createToggle( PendingIntent.getActivity( context, 0, i, 0 ), "Coupon app cashback option", false ); return new ListBuilder(context, sliceUri, ListBuilder.INFINITY) .addRow( new ListBuilder.RowBuilder() .setTitle("Coupons cashback") .setSubtitle("Enable cashback offers") .setPrimaryAction(sa) ) .build(); }

Slice Templates

Data in slice can be displayed in list of rows or grid of rows and cells. Slice provider can use ListBuilder or GridBuilder for structuring slice content. SliceView can process the slice content structured using ListBuilder or GridBuilder and display the content as structured.

Row types which can be added to ListBuilder are rows returned by ListBuilder.HeaderBuilder, ListBuilder.RowBuilder, ListBuilder.RangeBuilder, ListBuilder.InputRangeBuilder and GridRowBuilder.

GridRowBuilder row is created by adding cells to it using GridRowBuilder.CellBuilder.

RowBuilder

Rows can be defined using ListBuilder.RowBuilder which allows you to add title item, title, subtitle, end item and primary action.

Title item and end items can be time, icon or action.

Each row in the list can be associated with different slice actions. In the below example, when user clicks first row, main screen will be shown. When user clicks second row, coupons screen will be shown. As shown in the example from the previous section, you can identify a particular slice action in the destination activity using data added to the pending intent of the slice action.

public static Slice getCouponSliceWithMultipleRows(Uri sliceUri, Context context){ return new ListBuilder(context, sliceUri, ListBuilder.ICON_IMAGE) .addRow( new ListBuilder.RowBuilder() .setTitleItem(System.currentTimeMillis()) .setTitle("Coupons") .setSubtitle("Best offers from top stores") .setPrimaryAction(SliceActionUtil.createDefaultActivityAction(context)) .addEndItem(createWithResource(context, R.drawable.offer), ListBuilder.ICON_IMAGE) ) .addRow( new ListBuilder.RowBuilder() .setTitleItem(createWithResource(context, R.drawable.phone), ListBuilder.ICON_IMAGE) .setTitle("Mobile") .setSubtitle("Huge discounts on mobiles") .setPrimaryAction(SliceActionUtil.createCouponAction(context)) .addEndItem(System.currentTimeMillis()) ) .build(); }

HeaderBuilder

ListBuilder.HeaderBuilder is used to add header to the list. The difference between header and row is that you can add description to header and header can’t contain title item and end item.

public static Slice getDealsSliceWithHeader(Uri sliceUri, Context context){ ListBuilder listBuilder = new ListBuilder(context, sliceUri, ListBuilder.ICON_IMAGE) .setAccentColor(0xff0F9F58) .setHeader( new ListBuilder.HeaderBuilder() .setTitle("Deals") .setSubtitle("Best deals") .setSummary("View latest deals from top stores in all categories.") .setPrimaryAction(SliceActionUtil.createCouponAction(context))); int i =0; for(String deal : CouponData.getDeals()){ listBuilder.addRow( new ListBuilder.RowBuilder() .setTitle(deal) .setTitleItem(createWithResource(context, CouponData.getIcons().get(i)), ListBuilder.ICON_IMAGE) .setPrimaryAction(SliceActionUtil.createCouponAction(context)) ); i++; } return listBuilder.build(); }

RangeBuilder

If you want to display progress indicator in the slice, you can use RangeBuilder. With RangeBuilder you can add horizontal progress indicator as row to the list builder.

public static Slice getCouponSliceWithRange(Uri sliceUri, Context context){ return new ListBuilder(context, sliceUri, ListBuilder.ICON_IMAGE) .addRow( new ListBuilder.RowBuilder() .setTitleItem(System.currentTimeMillis()) .setTitle("Watches") .setSubtitle("Get upto 90% off on branded watches, limited") .setPrimaryAction(SliceActionUtil.createCouponAction(context)) .addEndItem(createWithResource(context, R.drawable.watch), ListBuilder.ICON_IMAGE) ) .addRange(new ListBuilder.RangeBuilder() .setContentDescription("offers taken and total offers") .setTitle("Limited Number") .setSubtitle("Grab it before gone!") .setPrimaryAction(SliceActionUtil.createCouponAction(context)) .setMax(40) .setValue(20)) .build(); }

InputRangeBuilder

There may be a feature in your application which requires user to input a value which must be between two values. For this feature, if you want to define a slice with range option on it to allow users to select a value in the allowed range, then you can use InputRangeBuilder to create slice.

Using InputRangeBuilder, you can add rows with horizontal slider to the slice. You can add title, subtitle, description, primary action, minimum, maximum and current values and pending intent to the input range row.

The pending intent added to input range row gets fired when user changes the current value by sliding the input range slider. The updated value is added to the intent as an extra using android.app.slice.extra.RANGE_VALUE as key.

Following example shows how to use InputRangeBuilder.

public static Slice getCouponSliceWithInputRange(Uri sliceUri, Context context){ PendingIntent pi = PendingIntent.getActivity( context, 0, new Intent(context, CouponActivity.class), 0); return new ListBuilder(context, sliceUri, ListBuilder.INFINITY) .setHeader( new ListBuilder.HeaderBuilder() .setTitle("Discount Range") .setSubtitle("View offers in the discount ragne you selcted") .setSummary("View offers in the discount ragne you selcted") .setPrimaryAction(SliceActionUtil.createCouponAction(context))) .addInputRange(new ListBuilder.InputRangeBuilder() .setContentDescription("Select discount") .setTitle("Select Discount") .setSubtitle("View offers for the selected discount") .setInputAction(pi) .setPrimaryAction(SliceActionUtil.createCouponAction(context)) .setMax(90) .setMin(10) .setValue(40)) .build(); }

GridBuilder

GridBuilder is used to create grid rows which can be added to list builder for building slices. Grid row contains row of cells. You can add image, text and pending intent to a cell. The pending intent added to a cell will be fired when the cell is clicked. Grid can be associated with slice action by adding primary slice action to GridBuilder.

A cell is added to a grid using addCell method of grid builder and grid is added to list builder using addGridRow method.

Following example shows how to use GridBuilder and CellBuilder.

public static Slice getCouponSliceWithGridRow(Uri sliceUri, Context context){ PendingIntent pi = PendingIntent.getActivity( context, 0, new Intent(context, CouponActivity.class), 0); GridRowBuilder gridRowBuilder = new GridRowBuilder(); gridRowBuilder.addCell(new GridRowBuilder.CellBuilder() .addImage(createWithResource(context,R.drawable.laptop), ListBuilder.ICON_IMAGE) .addText("laptop offers") .setContentIntent(pi)) .addCell(new GridRowBuilder.CellBuilder() .addImage(createWithResource(context,R.drawable.phone), ListBuilder.ICON_IMAGE) .addText("mobile offers") .setContentIntent(pi)) .addCell(new GridRowBuilder.CellBuilder() .addImage(createWithResource(context,R.drawable.watch), ListBuilder.ICON_IMAGE) .addText("watch offers") .setContentIntent(pi)) .addCell(new GridRowBuilder.CellBuilder() .addImage(createWithResource(context,R.drawable.tv), ListBuilder.ICON_IMAGE) .addText("tv offers") .setContentIntent(pi)); return new ListBuilder(context, sliceUri, ListBuilder.INFINITY) .setHeader( new ListBuilder.HeaderBuilder() .setTitle("Coupons") .setSubtitle("View all coupon categories") .setPrimaryAction(SliceActionUtil.createCouponAction(context))) .addGridRow(gridRowBuilder) .build(); }

Dynamic Slice Content

Slices can contain dynamic data meaning slice can shows content which changes frequently. This slice feature of displaying dynamic data uses notifychange feature of content resolver and content observer.

Your app needs to notify about slice data change using the content resolver to slice observer. Slice observer is part of slice presenter.

To create slice observer in the slice presenter, you need to use SliceLiveData. To observer a slice in the slice presenter, first crate LiveData using fromUri method of SliceLiveData passing the slice URI to it. Then call observe() method on LiveData object passing SliceView object.

LiveData liveData = SliceLiveData.fromUri(this, sliceUri); liveData.observe(this, sliceView);

To notify about slice data change from your app, get content resolver and call notifyChange method passing slice URI to it.

getContentResolver().notifyChange(uri, null);

Delaying Content

If loading of slice content takes time, you can return the slice with rows containing null subtitles and/or end items with indicator to let the slice presenter know that content is loading.

Once content is loaded, slice observer is notified using content resolver as discussed in the previous section.