From Punched Cards to Prompts
AndroidIntroduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Slices provide the ability for your app to share
information and controls with other apps. In my previous post,
Share a Slice of Your App, I covered
the basics of Slices on Android. Introduced in Android Pie (API 28), they are packaged as a
library and work all the way back to Android KitKat (API 19). If you haven’t already, give my introduction to Slices a read through to
learn about the main concepts in Slices, how to create a simple one, and how to view them on your
devices. Before continuing with this post, make sure you have a SliceProvider in place to build off
of for the interactive slices.
In my previous post, I covered adding a toggle action to your Slice to make it more
interactive. In this post, I’ll show how to add a range action. Ranges allow users to control any
field that accepts a discrete range of options, such as volume or brightness. This type of control
also requires you to create a component to handle the data in your app. Read on to find out more!
Similar to the toggle action, you need to provide a Service
or BroadcastReceiver
to handle the
data coming from the Slice. For this example, I’ll use a BroadcastReceiver
. Don’t forget to
register your receiver in your AndroidManifest.xml
so the system can start it properly.
class NerdLevelReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Handle the range data from the Intent
}
}
Again, I’ll fill out the actual implementation once I complete my Slice. This will allow me to
create a valid PendingIntent
for my Slice action.
Next, I’ll create a different path for my Slice called /range
.
override fun onBindSlice(sliceUri: Uri): Slice? {
val context = getContext() ?: return null
return when (sliceUri.path) {
"/range" -> {
// Display range slice
}
else -> {
...
}
}
}
One important thing to know about the range action is that you need to specify a primary Slice
action. If you don’t, the app will crash with an IllegalStateException
. So first, I’ll
create my primary action for my range Slice:
override fun onBindSlice(sliceUri: Uri): Slice? {
context ?: return null
return when (sliceUri.path) {
"/range" -> {
// Display range slice
val primarySliceAction = createPrimarySliceAction()
}
else -> {
...
}
}
}
private fun createPrimarySliceAction(): SliceAction {
val primaryTitle = context.getString(R.string.view_all_booking_options)
val primaryIntent = Intent(context, BookingActivity::class.java).let {
it.putExtra(EXTRA_BOOKING_STRING, primaryTitle)
PendingIntent.getActivity(context, DEFAULT_REQUEST_CODE, it, 0)
}
val primaryBitmap = BitmapFactory.decodeResource(context.resources, R.drawable.ic_main)
val primaryIcon = IconCompat.createWithBitmap(primaryBitmap)
return SliceAction.create(
primaryIntent,
primaryIcon,
ICON_IMAGE,
primaryTitle
)
}
With that in place, I can move on to creating my Slice. The inputRange
is used to create the
range action. A PendingIntent
is provided for the component responsible for handling the updates.
A max and current value are also provided to configure the current state of the range.
override fun onBindSlice(sliceUri: Uri): Slice? {
context ?: return null
return when (sliceUri.path) {
"/range" -> {
// Display range slice
val primarySliceAction = createPrimarySliceAction()
val nerdLevelIntent = Intent(context, NerdLevelReceiver::class.java).let {
PendingIntent.getBroadcast(context, 0, it, 0)
}
val nerdLevelTitle = context.getString(R.string.nerd_level_title)
list(context, sliceUri, ListBuilder.INFINITY) {
inputRange {
title = nerdLevelTitle
inputAction = nerdLevelIntent
max = 100
value = 50
primaryAction = primarySliceAction
}
}
}
else -> {
...
}
}
}
With my slice implementation in place, I can fill out my BroadcastReceiver
. Like the toggle
implementation, the Intent
passed to the receiver has extra data about the current value of the
range. This method will be called many times as the user moves the slider so don’t take too long in
your receiver or you risk the system killing your receiver before it finishes.
class NerdLevelReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.hasExtra(Slice.EXTRA_RANGE_VALUE)) {
val rangeValue = intent.getIntExtra(Slice.EXTRA_RANGE_VALUE, -1)
Log.d("NERD LEVEL", "Have range value: $rangeValue")
NerdLevelManager.nerdLevel = rangeValue
}
}
}
With my receiver in place, I can run my Slice and see the range value output to LogCat as I move
the slider.
Range actions provide a great user interface for users to control any functionality that needs a
value from a discrete range. You can either use a Service
or a BroadcastReceiver
to handle
the user’s input. Just configure a PendingIntent
for your component and the system will start it
when the data changes.
I hope you enjoyed these posts on interactive Slices. Watch out for more posts on Slices in the
future. If you have any questions please feel free to comment below. Thanks for reading!
Introduction When computer programming was young, code was punched into cards. That is, holes were punched into a piece of cardboard in a format...
Jetpack Compose is a declarative framework for building native Android UI recommended by Google. To simplify and accelerate UI development, the framework turns the...
Big Nerd Ranch is chock-full of incredibly talented people. Today, we’re starting a series, Tell Our BNR Story, where folks within our industry share...