Muzei 3.4 is now available on Google Play and with it comes new functionality and Muzei API 3.4, which enables your Source to take advantage of many of these features.
Commands get an upgrade
Since the first release of Muzei, sources have been able to provide custom commands. Each command would appear in the overflow menu. When the user tapped the command, it would trigger the onCommand()
method of your MuzeiArtProvider
.
This approach had two big issues:
- It was hard for users to find a command behind the overflow menu.
- Due to restrictions on starting activities from the background, any attempts to start an activity from
onCommand()
would fail on Android 10 and higher devices.
Muzei 3.4 address both of these concerns via a new API based on RemoteActionCompat
which you can see as soon as you load up Muzei 3.4.
Yep, the new getCommandActions()
API lets you associate an icon with your command and respects the use of setShowAsIcon(true)
to show your command as an icon right next to the Next Artwork command (there is a limit to how many icons can be displayed, so your action may still be displayed in the overflow menu if you overuse this functionality).
The new API also uses a PendingIntent
to trigger your command. This means that launching an activity from a command just means you use PendingIntent.getActivity
and that works exactly the same across all API levels (even on the latest versions of Android) without the need to use Intent.FLAG_NEW_TASK
either. Similarly, if you have background work, you’d use PendingIntent.getBroadcast
or PendingIntent.getService
to do your work.
Examples of custom commands
The UnsplashExampleArtProvider
has two commands — one that shows as an icon to go to the author’s Unsplash profile and a second that lets you visit Unsplash to explore more artwork.
Creating the profile command is based on the Artwork
passed to getCommandActions()
(remember, each Artwork
can have separate commands):
private fun createViewProfileAction(
context: Context,
artwork: Artwork
): RemoteActionCompat {
val profileUri = artwork.metadata?.toUri() ?: return null
val title = context.getString(R.string.action_view_profile,
artwork.byline)
val intent = Intent(Intent.ACTION_VIEW, profileUri)
return RemoteActionCompat(
IconCompat.createWithResource(context,
R.drawable.source_ic_profile),
title,
title, // content description for the tooltip
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT))
}
Here, the Unsplash Example source has used the metadata
field to store the profile URI for the user. For the icon, RemoteActionCompat
relies on IconCompat
, which lets you build an icon from a bitmap, content://
URI, a byte array, or the most common case: a resource ID. Each icon should be 24x24dp. You’ll provide a title (which will appear if the action is in the overflow menu) and a content description (which will be used as the tooltip for the icon) and the PendingIntent
that should be triggered when the command is tapped.
Creating the visit Unsplash action for the overflow menu is very similar with constructing the RemoteActionCompat
:
private fun createVisitUnsplashAction(
context: Context
): RemoteActionCompat {
val title = context.getString(R.string.action_visit_unsplash)
val unsplashUri = context.getString(R.string.unsplash_link)
val intent = Intent(Intent.ACTION_VIEW, unsplashUri.toUri())
return RemoteActionCompat(
IconCompat.createWithResource(context,
R.drawable.muzei_launch_command),
title,
title,
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT)).apply {
setShouldShowIcon(false)
}
}
Muzei API 3.4 ships with an R.drawable.muzei_launch_command
icon which can be used as a default icon for your command. You’ll note that we need to explicitly call setShowShowIcon(false)
— the default for a RemoteActionCompat
is true, so you’ll need to set it to false if you always want your command to only appear in the overflow menu.
By extracting these out into their own methods, getCommandActions()
can simply return a listOf()
including the two RemoteActionCompat
instances returned by the methods.
Integration with the File Picker
Android has provided a mechanism for integrating your own app into the default file picker and Files app since API 19 in the form of a custom DocumentsProvider
. But actually implementing one is easier said than done.
To make it easy to expose artwork from your MuzeiArtProvider
to the default file picker, Muzei API 3.4 now allows you to add a prebuilt MuzeiArtDocumentsProvider
to your manifest:
<provider android:name="com.google.android.apps.muzei.api.provider.MuzeiArtDocumentsProvider"
android:authorities="${yourAuthority}.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
You’ll note the android:authorities
— this needs to match the android:authorities
on your MuzeiArtProvider
entry in your manifest plus the .documents
suffix. With just that, the two providers will automatically be kept in sync. There’s no extra work you need to do besides opting in to this new API.
Note: this API is most appropriate when your provider provides multiple artwork via
addArtwork
orsetArtwork
with multiple artwork. Avoid using this for artwork like the ‘Featured Art’ source that only have one artwork.
Muzei 3.4’s ‘My Photos’ source takes advantage of this out of the box, allowing you to add one of your chosen photos to an email you’re composing in Gmail or allow manually backing up your images by copying them to your local storage or Google Drive.
Improved performance
One of the most critical user experiences is selecting your source for the first time. Each moment the user needs to wait for that first artwork to appear stretches on forever.
Just by upgrading your source to Muzei API 3.4, you’ll find vastly improved performance when you call setArtwork
or addArtwork
with a large number of artwork. This can result in changes that used to take 16 seconds to now take less than a second and particularly large data sets of 1000s of images (which used to take 1–2 minutes!) now also take just a second.
Better User Onboarding for Sources
When building a custom source, the trickiest part is perhaps getting users from installing your app to actually selecting your source. This has often involved giving users instructions to manually follow.
This is even more critical when running on Android 10 or higher devices, where launchers using the standard LauncherApps.getActivityList()
API will show an icon for every app. While it helps raise awareness that your app is installed, the default behavior of just linking to the system Settings screen for you app (with an Uninstall button) is not particularly useful.
Muzei API 3.4 adds two new APIs which greatly improve this critical flow:
- The
isProviderSelected()
API lets you check whether your source has been selected by the user in Muzei. - The
createChooseProviderIntent()
API lets you deep link directly into Muzei’s Sources screen, scrolling directly to your source so that user can immediately select it.
This lets you verify that user has selected your source and, if not, redirect them directly to where they can go to select it.
It is strongly recommended to catch any ActivityNotFoundException
raised when starting the Intent
returned by createChooseProviderIntent()
and falling back to getLaunchIntentForPackage()
with Muzei’s package name (to support older versions of Muzei that don’t support deep linking to the Sources screen) and a link to Muzei’s Play Store listing (in cases where the user has not installed Muzei at all).
For a full example, look at Muzei Ghibli’s RedirectActivity
.
And so much more…
Muzei 3.4 has plenty of other improvements:
- Muzei 3.4 is the first version translated to other languages! If you’d like to help our crowdsourcing effort in expanding Muzei to support every language, please join our Crowdin project.
- If you’re shipping a version of your source that can be installed directly on Wear OS devices, custom commands you publish will now be displayed (using those nice icons you provide via
getCommandActions()
).
Muzei API 3.4 also has its own improvements:
- The long deprecated
MuzeiArtSource
API has been removed entirely. - The Muzei API has been completely rewritten in Kotlin. You’ll notice a number of reified versions of methods (avoiding that
::class.java
mess) and the fact thatArtwork
is now a immutable class. You can certainly still write your source in the Java programming language, but we’d certainly strongly recommend moving to Kotlin. - The reference docs at api.muzei.co have been fully updated for Muzei 3.4 with improved examples and cross linking between related documentation.
Muzei has been a huge passion project for me and I can’t thank Roman Nurik for giving me the opportunity to continue to contribute.
If you’re looking for examples for upgrading your source to Muzei API 3.4, take a look at pull requests I’ve filed for:
- Muzei Ghibli
- Muzei Earth View
- Muzplash
- Pixel Art for Muzei
- Muzei Bing Image of the Day
- Muzei Photos Album
- Heroes Comic Covers for Muzei
- Distant Worlds for Muzei
If you’d like to help sponsor future development and provide a token of gratitude (and get into Muzei’s private alpha!), you can sponsor me on Github.