fbpx

Blogs from the Ranch

< Back to Our Blog

Icing on the Slice: Providing more value to users with toggle actions

Avatar

Brian Gardner

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 it 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 multiple actions to your Slice to make it more
interactive. In this post, I’ll show how to add a toggle action. This type of action is good for
controls the user can enable or disable in your application. This type of control also requires you
to create a component to handle the data in your app. Read on to find out more!

Add toggle action

For toggle actions, you will need to create either a Service or a BroadcastReceiver to handle
the updates. The Intent object that these components receive will contain extra data indicating
if the toggle is enabled or not. You can handle the action appropriately there. For this example
I’ll use a Service to handle the updates.

For now my Service will be very plain. I’ll fill out the actual implementation once I finish my
Slice. This will just act as a placeholder so I can create a valid PendingIntent.

class FocusService : IntentService("FocusService") {

    override fun onHandleIntent(intent: Intent?) {
        // Handle the toggle change
    }
}

Don’t forget to register your service in your AndroidManifest.xml so the system can start it
correctly.

The next step is to create a path for your toggle Slice. To continue with my creative streak, I’ll
call this path /toggle.

override fun onBindSlice(sliceUri: Uri): Slice? {
    context ?: return null
    return when (sliceUri.path) {
        "/toggle" -> {
            // Display toggle slice
        }
        else -> {
            ...
        }
    }
}

To create my toggle action I can use the createToggle() static method on the SliceAction class.
This method needs a PendingIntent, a title, and a boolean flag indicating if the toggle is
checked.

override fun onBindSlice(sliceUri: Uri): Slice? {
    context ?: return null
    return when (sliceUri.path) {
        "/toggle" -> {
            // Display toggle slice
            val toggleAction = createToggleAction()
        }
        else -> {
            ...
        }
    }
}

private fun createToggleAction(): SliceAction {
    val isFocusing = FocusManager.focusing
    val focusIntent = Intent(context, FocusService::class.java).let {
        PendingIntent.getService(context, 0, it, 0)
    }
    val toggleTitle = context.getString(R.string.toggle_title)
    return SliceAction.createToggle(
            focusIntent,
            toggleTitle,
            isFocusing
    )
}

The FocusManager is just an object responsible for determining the current state of my toggle.
The data is stored in a property that can be returned quickly back to my SliceProvider.

object FocusManager {
    public var focusing = false
}

Returning this value quickly is important so there isn’t a delay when your Slice is being
displayed. In fact, if you take too long to return the information, an exception is thrown, and your
Slice will not be shown. Even reading from SharedPreferences is too long in this case. If reading
your data takes too long, you will need to initialize your Slice with placeholder data while you
query the real data in the background. Once you have the data, you can notify your SliceProvider
that the data has changed and it will rebind accordingly.

Once I have my toggle action, I can create the rest of the data for my Slice. I’ll change the text
of my Slice based on my toggle state. If my user is not focusing, I will prompt them to start, and
if they are focusing, I’ll prompt them to turn it off when they are done.

override fun onBindSlice(sliceUri: Uri): Slice? {
    context ?: return null
    return when (sliceUri.path) {
        "/toggle" -> {
            // Display toggle slice
            val toggleAction = createToggleAction()

            val focusTitleText: String
            val focusSubtitleText: String
            if (FocusManager.focusing) {
                focusTitleText = context.getString(R.string.focus_title_text_enabled)
                focusSubtitleText = context.getString(R.string.focus_subtitle_text_enabled)
            } else {
                focusTitleText = context.getString(R.string.focus_title_text_disabled)
                focusSubtitleText = context.getString(R.string.focus_subtitle_text_disabled)
            }
        }
        else -> {
            ...
        }
    }
}

With my action, title, and subtitle ready, I can move on to creating my Slice. I can add my toggle
action as my only action so the user can see it and interact with it.

override fun onBindSlice(sliceUri: Uri): Slice? {
    context ?: return null
    return when (sliceUri.path) {
        "/toggle" -> {
            ...
            } else {
                focusTitleText = context.getString(R.string.focus_title_text_disabled)
                focusSubtitleText = context.getString(R.string.focus_subtitle_text_disabled)
            }

            list(context, sliceUri, ListBuilder.INFINITY) {
                header {
                    title = focusTitleText
                    subtitle = focusSubtitleText
                }
                addAction(toggleAction)
            }
        }
        else -> {
            ...
        }
    }
}

The last step is to fill out my FocusService to handle the toggle changes. I can pull out the
extra data from the intent and set it on my FocusManager. I will also notify the system that the
content for my Slice URI has changed so that they system can tell my SliceProvider to rebind my
Slice with the updated data.

class FocusService : IntentService("FocusService") {

    override fun onHandleIntent(intent: Intent?) {
        intent ?: return
        val toggleState = if (intent.hasExtra(Slice.EXTRA_TOGGLE_STATE)) {
            intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, false)
        } else {
            false
        }

        FocusManager.focusing = toggleState

        val sliceUri = Uri.parse("content://com.bignerdranch.android.icingontheslice/toggle")
        contentResolver.notifyChange(sliceUri, null)
    }
}

With this in place, I can run my Slice and see the content change when I enable and disable the
toggle.

Example of a slice displaying content based on its toggle disabled

Example of a slice displaying content based on its toggle enabled

Fin

Toggle actions provide an easy way for users to enable or disable specific functionality in your
app in an easy to find place. 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.

In the next post, I’ll cover how to add a range action to your Slice. This allows users to provide
a value from a discrete set of options.

I hope you enjoyed the post. If you have any questions please feel free to comment below. Thanks
for reading!

Avatar

Brian Gardner

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project