Implementing Android App Shortcuts
With every release of Android, there comes a myriad of new features and Android Nougat 7.1 is no different. One of these new features is App Shortcuts. App Shortcuts allow the user to access primary actions in your app straight from the launcher, taking the user deep into your application, by long pressing on your app icon. Users can also pin these shortcuts to the home screen for even quicker access to your app’s primary actions. In this tutorial, we’ll cover how to boost your user’s experience by implementing App Shortcuts in your app.
The first thing you should know about implementing App Shortcuts is that there are two types of shortcuts: static and dynamic. Static shortcuts are generated from an XML file that you create. This means that they can only be updated when the user updates the app. On the other hand, dynamic shortcuts can be created and destroyed at runtime, meaning you can do things like show different shortcuts based on frequency of use. I’ve created a sample app in Kotlin called Konstellations demonstrating the use of both types of shortcuts and the source is on Github. I’ll be going through the source code of Konstellations as I explain how to implement App Shortcuts, so you can get your hands on a real world example.
The first kind of shortcut we’ll implement is a static shortcut. In your AndroidManifest.xml, we have to add some metadata to your launcher activity, but in practice, any Activity with the intent filter set to the android.intent.action.MAIN action and the android.intent.category.LAUNCHER category will do. In my manifest, I added the <meta-data> tag to MainActivity which is my launcher activity. The name attribute is set to “android.app.shortcuts” and the resource attribute is set to “@xml/shortcuts”.
Now we need to actually make the file where the shortcuts are defined. I created mine as res/xml-v25/shortcuts.xml. Note the resource qualifier; I had to add this so Android Studio would stop yelling at me. It’s a good practice regardless because this feature is only available on API level 25 and above. A copy of my shortcuts.xml is below.
As you can see, this needs a root <shortcuts> tag, then you can add a <shortcut> tag to add a shortcut. Don’t go too crazy adding shortcuts though. Android only allows you to have up to five shortcuts, but the documentation strongly suggests providing four (any combination of static and dynamic). Oddly enough, I couldn’t get a fifth shortcut to show up, so it seems their “suggestion” is actually enforced. Add any more than five shortcuts and your app will crash! Now let’s take a closer look at the <shortcut> tag attributes.
android:enabled is a boolean describing whether or not the shortcut is enabled. If disabled, the shortcut will not appear in the list of shortcuts when a user long presses on your app icon in their launcher. In addition, if this shortcut was previously pinned to the user’s home screen, it will appear grayed out, and an error message will appear when the user clicks on it. The default message is “Shortcut isn’t available”, but you can override that by using android:shortcutDisabledMessage and providing a string resource.
You use android:icon to provide an icon for your shortcut. I strongly recommend following the design guidelines here so that you can provide your user with an experience that’s consistent across apps. For the basics though, you’ll want to have a 48dp x 48dp circle that’s Material Grey 100 (or #F5F5F5). Then drop a 24dp x 24dp icon in the middle of that circle and you’ll be golden.
Each shortcut needs a unique android:id so Android can recognize the shortcut.
Both android:shortcutLongLabel and android:shortcutShortLabel are labels shown to the user displaying the name of the shortcut. It’s recommended that the short label be less than or equal to 10 characters, and the long label be less than or equal to 25 characters. Android will pick the appropriate one to display based on the space available, but I’ve noticed that the short label is usually used when placed on the home screen. The long label is usually used whenever there is space in the app shortcut menu.
Each <shortcut> tag needs to have at least one <intent> tag specifying the Activity to be launched when the shortcut is clicked. The android:action attribute is mandatory, even if you don’t need an actual action. If you don’t have an action to specify, I’d recommend just using “android.intent.action.VIEW” although the actual string you use doesn’t really matter. It’s okay to put multiple <intent> tags in a <shortcut> tag. Android will handle this by launching the last <intent> specified, and putting the other <intent>s in the backstack. You can also specify the category of a shortcut using the <categories> tag, but as of this writing, the only supported category is “android.shortcut.conversation”. I’d imagine this would only be useful for messaging apps.
Dynamic shortcuts are the second type of shortcuts, and to interact with them (create/destroy/update) you’ll need to use the ShortcutManager. You can get a hold of a ShortcutManager using
I wrote a small inline helper function to check the API version every time I used the ShortcutManager, so I wouldn’t have to do it all over the app. It’s defined below in ShortcutHelper.kt :
On application startup in KonstellationApplication.kt, I use the shortcut action to update all the shortcuts. The code for updating shortcuts is as follows:
Here we’re creating a List<ShortcutInfo> and setting this as the list of dynamic shortcuts that Android will display. First, we get all of the Constellations and sort them by the number of times they were visited in descending order. Then we have a call to map, which is really the bulk of this function. In it we are providing a function that takes a Constellation enum and returns a ShortcutInfo object representing that Constellation. As you can see, the builder API for ShortcutInfo is very similar to the XML used to create static shortcuts. In the constructor, we provide a Context object and the ID for the shortcut. Then we provide a short and a long label, as well as the icon for the shortcut and the Intents that should be launched when it’s clicked. Finally, we only take the first three ShortcutInfo objects to avoid exceeding the limit of shortcuts. There’s a hard limit of five total shortcuts, but Android recommends only showing four. The fourth shortcut comes from the static shortcut we created earlier.
In this example, I’m replacing the list of dynamic shortcuts entirely, but you can also modify the list of dynamic shortcuts via ShortcutManager.addDynamicShortcuts(List), ShortcutManager.updateShortcuts(List), and ShortcutManager.removeDynamicShortcuts(List).
Now that we know how to make both types of shortcuts, let’s talk about turning them on and off. Like I mentioned before, it’s possible to disable a shortcut if it isn’t relevant anymore, or re-enable it when it does become relevant through the “enabled” attribute. In Konstellations, I do this with a toggle in the overflow menu of ConstellationDetailActivity.kt
When “Enable Shortcut” is clicked, we call ShortcutManager.enableShortcuts(List) and pass in the list of shortcuts we want to enable. When “Disable Shortcut” is clicked, we call ShortcutManager.disableShortcuts(List) and pass in the shortcuts to disable.
Now that we’ve got the basics down, there’s a couple more things you should know about app shortcuts. Every time a user navigates to a part of your app accessible via shortcut, whether they got there using the shortcut or just navigating through the app, you should call ShortcutManager.reportShortcutUsed(String). This gives launcher apps the information they need to to build a prediction model so that they can promote the shortcuts that are likely to be used at the moment. In my onCreate of ConstellationDetailActivity, I make a call to ShortcutHelper.trackShortcutUsed(…) like so:
and the implementation for trackShortcutUsed(…) is below:
In this method, I’m calling the aforementioned reportShortcutUsed(String) method. Afterwards, I keep count of how many times the shortcut has been used via SharedPreferences, so that I can directly provide the most used shortcuts to the ShortcutManager. This is the logic that makes my dynamic shortcuts truly dynamic. For your dynamic shortcuts, you should do whatever makes the most sense for your own application. For example, if you were writing a messaging app, you might provide shortcuts for the most recent conversations.
This next best practice is important if you allow the user to back up your app via android:allowBackup=“true” in the manifest. The only shortcuts that will be automatically restored to the user’s homescreen are the static shortcuts that were pinned. Because dynamic shortcuts are not backed up, you’ll have to write logic to re-publish them if a user opens your app on a new device. The Android documentation recommends that you check the number of shortcuts returned by getDynamicShortcuts() each time you launch your app and re-publish dynamic shortcuts as needed. They provide the following Java sample code as an example:
When you’re actively developing, you’ll probably be using the updateShortcuts(), setDynamicShortcuts(), and addDynamicShortcuts() methods quite frequently. It’s important to know that Android limits how often these methods can be called if your app is backgrounded (no foreground Activities or Services). In a production environment, this limit can be reset by bringing your app to the foreground. When in a development environment, you can go to Settings > Developer Options > Reset ShortcutManager rate-limiting to get rid of this limit. If you prefer using the command line, you can execute the following command:
adb shell cmd shortcut reset-throttling [ --user your-user-id ]
And with that you should be well on your way to implementing Android 7.1’s new feature App Shortcuts! You can find Android’s official documentation for App Shortcuts here. Feel free to leave any questions in the comments, and thanks for reading!
Be sure to to follow me on Twitter @AOrobator for more awesome Android tips. If you found this article helpful, make sure to hit the green heart!