android

Android P Slices: the missing documentation — part 1

“It depends” 🤷‍♂️ — Google Developer Expert for Android, Flutter and Identity. A geek 🤓 who has a serious thing for good design ✨ and for emojis 🤟

Android P introduced the Slices API, but with only sparse Javadocs and no indication of what they could be used for. After some digging and with a lot of speculation, we present you with the missing documentation for Slices!

Slices in action!

What are Slices?

The easiest, and somewhat correct, way to think of Slices is as RemoteViews v2. Now, that is not a completely correct definition, but will help you understand what we’re dealing with here.

If you’ve ever developed a homescreen widget, or created a custom Notification, you’ll know what RemoteViews are. If you haven’t, RemoteViews are the only way to display your own UI inside of another process before Android P — and they’ve been with us since API 1.

In that perspective, Slices are just another way to display your UI inside of another app or process. There are also some very tangible differences with RemoteViews though, and we’ll see what they are in the next installment of this series.

A slice is a way to declare a static piece of information and provide indications on how it should be presented to users. It is a serialisable piece of data, not a direct representation of the UI like a layout XML is. Slices also allow for extension with the SliceSpec mechanism, which allows both providers and consumers of slices to negotiate a format they both know.

But first, we need to cover the big question:

Why are Slices being introduced?

⚠️ WARNING: intense speculation from here onwards!

I believe the answer is: Google Assistant and Google Feed. The reason being that, while the current state of slice rendering doesn’t look finished or polished in any way, I can clearly recognise the same UI patterns that the Assistant and Feed use:

Google Assistant An equivalent Slice
A search result in Assistant A search result in a slice

These admittedly do not look exactly the same, but if you disregard styling, the building blocks are clearly there. Another example:

Google Assistant An equivalent Slice
A Lens result in Assistant A Lens result in a slice

They aren’t exactly the same, but the structure is close enough to at least suggest that Slices could be the foundation for an hypothetical upcoming Assistant API that apps could use to surface their content in Assistant answers, or in the Google Feed, on devices. Again this is pure speculation, but the signs are pointing in that direction.

Who can use slices?

Everyone can! Although the Slice API has been introduced in the Android P Developer Preview 1, there is a support library component as well which exposes the same public API through the androidx.slice package:

  • Slices provides a framework for apps to embed templated content from other apps.
    • slices-builders contains methods to build content in a template format.
    • slices-view contain methods to present that content.
Source: Recent Support Library Revisions — Revision 28.0.0 Alpha 1

Now, Android being Android, when I say "everyone" I obviously mean "everyone on API 24+" (that is Android 7.0 Nougat), which is the minSdkVersion for the compat package. That said, it's still very nice to see that it's supported on a 2-years old Android version right at launch, although this leaves out still a lot of users and developers, since for many minSdkVersion 19 is still a dream.

This article looks at things from the compat point of view as it’s certainly the most interesting and because it ships with the sources and javadoc, which makes it easier to work with the compat library version. That library simply wraps the platform implementation on P anyway, converting the slices back and forth between the platform version and the compat version.

At the current state of things, slices are still pretty broken. Let’s see what the ideal flow is, and then we’ll be able to identify where things fall apart.

Journey of a Slice, end to end

A slice can come from anywhere on a device; any app can act as a slice provider, and any app can consume slices from any other app, including itself, by acting as a slice host:

Roles in a slice relationship

The slice provider app is any app that exposes a SliceProvider, which is a fancy ContentProvider that takes in a Uri (or an explicit Intent) and emits a Slice.

The slice host app is any app that uses SliceManager to bind, pin or otherwise interact with a SliceProvider via a Uri (or an explicit Intent).

Both the provider and host apps will need to:

  • Have compileSdkVersion 'android-P' and targetSdkVersion 'P'
  • Have a dependency on either 'com.android.support:slices-view:28.0.0-alpha1' or 'com.android.support:slices-builders:28.0.0-alpha1', depending on their role

Accessing slices

Assuming there is at least one slice provider app installed, a host app will need to know the URI (or, we’ll imply from now on, an explicit intent) for a slice to be able to display it. There are several ways to obtain slices to display:

  1. A one-off retrieval calling sliceManager.bindSlice().

  2. Signal interest for updates about a URI by using sliceManager.pinSlice() — this does not actually provide you with updates, you'll still need to call bindSlice() manually. Pinning does not persist across reboots.

    I am not entirely sure why you would need this as a separate API, as pinning in and by itself is not particularly useful; it’s needed for observing changes, but you still need to fetch the content. It looks like it may have been exposed just so that SliceLiveData can access it.

  3. Register a changes callback with sliceManager.registerSliceCallback(), which will also pin the slice for you, and provides a callback with fresh Slices as they are emitted.

  4. Use SliceLiveData to obtain a LiveData<Slice> that you can use to effortlessly update your app as new data is emitted. The SliceLiveData also pins the slice for you.

Solution number 4 is by far the easiest, but unfortunately due to an issue in the current release it does not work; pinning is currently broken and if you use it in any form, your app may crash with something like java.lang.SecurityException: Call pinSlice requires full slice access.

Besides this little growing pain, the SliceManager and SliceProvider do an excellent job at managing all the permissions for you. Contrary to what error messages may say, you do not need to declare that your app uses the BIND_SLICE permission, nor to define an explicit permission for your SliceProvider in the manifest. In fact, if you add permissions manually you may hit issues, so I would recommend against doing that, even if leaving an exported and unprotected content provider is not a generally advisable strategy.

Once a host app gets hold of a Slice, the only thing left to pass it on to a SliceView:

val slice = // ...
sliceView.setSlice(slice)

The first time a host app tries to access another app's slices, the SliceView will show a — very crude — notice that the user needs to grant permission to the app to access slices:

The slice permission request item

The user can tap the notice to show a permission dialog controlled by the platform or compat slices framework, where they can grant or deny permission to access a certain app's, or all apps', slices:

The slice permission request dialog

After that, if the host is observing for slice updates, they will receive the actual slice content and can display it on their SliceView.

Or at least, that's how it's supposed to work. Unfortunately, a bug in the current version means that when the SliceView displays the permission slice, it fails to make it clickable, so hosts cannot display the actual slice content since they never get to ask for permission.

The permission slice does, however, contain the pending intent that should be triggered as the SliceAction of its first sub-item. You could then, until this is fixed, proceed to identifying permission requests based on a few characteristics:

private fun Slice.isPermissionRequest(appName: String): Boolean {
  // Permission slices have only one item, whose action is a PendingIntent to
  // show the permission granting activity (which shows a dialog). We cannot
  // easily check the contents of the action PendingIntent, so for now we just
  // assume that if it's not null it's good enough. We'll check other signals
  // afterwards to verify if it's really a permission slice.
  if (items.count() != 1 || items[0].action == null) {
    return false
  }
  // Permission slices contain one slice item, which contains a slice item which is
  // a FORMAT_TEXT with a fixed format
  val subItems = items.first().slice.items
  val firstSubItem = subItems.firstOrNull()
  return firstSubItem?.format == android.app.slice.SliceItem.FORMAT_TEXT &&
    firstSubItem.text == "$appName wants to show Slice Provider slices"
}

This code assumes the slice provider you are connecting to has the name Slice Provider. Currently there is no way to discover slice providers, their names, or any details about them, so you have to hardcode the name, or use a laxer check on the text.

And then, based on that detection, could hack your way to the permission dialog:

internal fun SliceManager.requestPermission(sliceUri: Uri, activity: Activity) {
  try {
    val intentSender: IntentSender? = bindSlice(sliceUri)!!.permissionRequestPendingIntent.intentSender
    activity.startIntentSenderForResult(intentSender, REQUEST_CODE_PERMISSIONS, null, 0, 0, 0)
  } catch (e: IllegalArgumentException) {
    Toast.makeText(
      activity,
      "Looks like we've hit a bug in Slices, likely fixed in alpha2. " +
        "Uninstall the app and reboot the device/emulator.",
      Toast.LENGTH_LONG
    ).show()
  }
}

There is a try-catch in there because the slices framework in some cases can get confused and lose the permission state, therefore we need to caution ourselves. It is a rare case, and I could not reproduce it reliably, but better safe than sorry.

Once this bug is fixed, you won't need all this anymore.

Styling a slice

I am sad to say, there's very, very little you can do to control the appearance of a slice inside of a SliceView. Unfortunately this seems to be by design, but I have put in a feature request to get more customisation options on the rendering side.

One alternative approach would be to create a new view implementation that can render slices, basically reimplementing the androidx.slice.builders package almost in its entirety. Rendering slices is no trivial matter and I would recommend not embarking in such an adventure unless there really is no alternative, and a very stringent requirement. Very, very few host apps should ever need to do this. I could imagine the Google Assistant being one of these; it could either implement a different way to render slices so they adapt to its UI design, or an hypothetical SDK on the provider side could simply declare a new set of supported SliceSpecs which the Assistant app knows how to render properly, sidestepping the built-in list slice and subitems.

What you can theme with the existing API is:

  • Override the tint colour, using SliceView.setTint() or the tint attribute in the view style — by default, slices use the host's colorAccent
    • All images that are not big images (e.g., a LARGE_IMAGE in a grid cell)
    • It is not possible not to tint icons in slices, unfortunately
    • It is not possible to define a per-item tint, all items are tinted with the same colour
    • The tint is applied as a color filter on the images, which means the tinting mode is SRC_ATOP — this means that translucent tints lose their alpha and become fully opaque
  • Set the display mode, using SliceView.setMode()
    • The supported modes are MODE_SMALL, MODE_LARGE, and MODE_SHORTCUT
    • MODE_LARGE is the default, uses a large template (i.e., a RecyclerView that can contain multiple rows of various types)
    • MODE_SMALL will only show the first item, be it a list row or a grid item
    • MODE_SHORTCUT will only show a shortcut using the slice icon, title and action (or fallbacks if those aren't available)
  • Make the slice scrollable or not scrollable, using SliceView.setScrollable()
    • This only applies to MODE_LARGE, since it's the only mode that shows more than one item
  • Set the title and subtitle colours for items using the titleColor and subtitleColor attributes in the view style
    • Their defaults are the theme's textColorPrimary and textColorSecondary, respectively
    • There is no corresponding API in code for this
  • Set the text size for various items, using the respective attributes in the view style:
    • headerTitleSize, headerSubtitleSize, titleSize, subtitleSize, gridTitleSize, gridSubtitleSize
    • There is no corresponding API in code for this

A few growing pains

Currently, there’s a series of bugs and limitations that I have spotted in the slice framework implementation. I have added to the appendix all the issues I have reported so far to the Android P Preview issue tracker. If you are building an app that uses slices today, you will want to check that list out to know what to expect.

In the next part of the series

In the second part of this series is an explanation of how to create a SliceProvider to provide slices to other apps, how a slice is structured, and a few more issues and tricks. Besides that, we'll try to come up with some ideas on how slices could be used to create rich SDKs that allow apps to integrate pre-built pieces of UI they don't need to, or should control.

You can find the source code for the sample app accompanying this series on GitHub.


Appendix: list of issues encountered in P DP1 and 28.0.0-alpha1

While developing the sample app, I hit several issues with the first preview and alpha build of slices. Here’s a rundown of the ones that concern an app that wants to host slices:

  • Anything that implies pinning a slice makes the host app crash (#75001463)

  • Running on Android P DP1, you get a dialog that informs that a bunch of gray listed methods are being called (#75001461)

    API compatibility dialog on P DP1

    • If you inspect the logcat you can see a bunch of messages complaining about access to hidden methods; this seems connected to some LiveData internals calling gray listed methods
  • You can have a header, but if you don't, the first item becomes automagically a header, and you can't avoid it (#74917014)

    • Except for the defaults of having a slightly larger text size, a header looks exactly the same as a normal list row
  • Slice actions are not displayed (#74889520)

    • If you're in large mode then the slice actions should be displayed in the header, but this is not documented anywhere
    • If you're not, they're supposed to show in a LinearLayout right below whatever is the slice content that is being displayed
  • When you use a GridBuilder you would expect to get a grid out of it, but what you get is a one-line horizontal list of items in a list row (#74889524)

    • The list of items can only host up to 5 items, including a "see more" item
    • There is no way to create a scrollable carousel with these items
  • All "see more" items look exactly the same as any other item, so I am not sure why you would need one (#74889525)

  • If your slice contains an Input Range (basically, a slider), then its action will be triggered immediately when the slice is bound to the view (#75004842)

    • Input Ranges must define an action, so that's an unavoidable issue
    • You could work around it using sliceView.setOnSliceActionListener() and ignoring the first action after binding if it has an EventInfo.actionType of type ACTION_TYPE_SLIDER but there is no way to suppress actions from being carried out, only to know they happened
    • Until the bug is fixed it's recommended not to use Input Ranges
  • There is no programmatic way to know if you have the user permission for accessing slices from a certain app, or any app (#74889527)

    • There is also no programmatic way to request permissions to show slices except at the point where the first interaction happens via the SliceManager, and there is no way to provide a custom onboarding UI leading to the permissions request dialog
    • You can work around it and hack your way to your goal, but that's not really recommended unless you have no alternatives
    • The built-in “permission needed” slice is not clickable (#74917009)
  • There is no built-in discovery mechanism for slices, and no way to list all the slice providers on the system (#75245750)

    • This also means there is no direct way to check if a certain slice provider is available without going to the implementation detail of slice providers being content providers and using the package manager to check
    • But that is limited in that there is no way to check if a content provider is a slice provider without knowing its authority and adding it to a whitelist
  • There is no easy way to provide support for custom SliceSpecs in the SliceView — what is built in to it is all you can have (#74917012)

    • There are no hooks into the slice rendering process
    • There are no hooks into the slice views and their styling except what outlined in the previous section
  • A list row with only a titleItem, or endItem image makes the SliceView crash (#75001462)

    • This can be worked around by also assigning an empty title to it: RowBuilder(listBuilder).setTitle("") ...
  • A bunch of necessary APIs are marked as restricted to the library/library group, but are necessary to use unrestricted APIs (#75001467)

    • androidx.slice.widget.SliceView.SliceMode, androidx.slice.widget.EventInfo.SliceRowType, androidx.slice.widget.EventInfo.SliceActionType, androidx.slice.widget.EventInfo.SliceButtonPosition, androidx.slice.Slice.SliceHint, androidx.slice.SliceItem.SliceType, androidx.slice.builders.GridBuilder.ImageMode, androidx.slice.builders.MessagingSliceBuilder
    • A bunch of toString()s in public API classes, which makes little sense to me

While the slice items rendering is rather inflexible, but some tricks can be applied to get a similar result to what you might want. We'll see a bunch of them in the next part of this mini-series.


Thanks a lot to everyone that helped me making this article more readable and interesting, and to those who helped me dig into Slices!

Enjoyed this article? There's more...

We send out a small, valuable newsletter with the best stories, app design & development resources every month.

No spam, no giving your data away, unsubscribe anytime.

About Novoda

We plan, design, and develop the world’s most desirable software products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact