ActionProviders are a really nice feature. They provide a nice way to show an alternative or a secondary context in an activity.
ShareActionProvider takes them to another level. The secondary context of showing a list of shareable activities is available as a menu item. In addition, they show the most frequently used Activity for quick sharing. But they come with a downside. As the name says, ActionProviders can only be present in an ActionBar. Most of the popular android apps don’t use an ActionBar as it is very limiting. Firefox for Android has the same problem, as it doesn’t use an ActionBar. How do we provide the user with such a fast and easy access to the most frequently used activity?
Since Firefox for Android uses a custom menu, adding an ActionProvider logic inside a ListView was easy. Analogous to the ShareActionProvider, the main menu item gives the list of shareable activities as a
SubMenu, and the most frequently used app is shown along with the actual menu item. To use the same metrics as Android for the frequency calculation, we chose to use the same underlying model. Android’s implementation of storing and receiving applications from an XML file, ordering the applications based on a time based weight is available in
ActivityChooserModel. (Note: When used, one would have to copy this file to their code base, as this file is an internal file).
While working on this feature, I was left with so many puzzling discoveries!
- While setting an Intent with ShareActionProvider for the first time, it calls ActivityChooserModel’s setIntent(). This triggers reading the list from the history file by calling
readHistoricalDataImpl(). The problem here is that, even though the comment — oops, just a comment! — says everything is done off the UI thread, the reading of a file from the disk and parsing it happens in the UI thread. This causes StrictModeViolation, and hogs the UI.
- ShareActionProvider’s setShareIntent() should be used sparsely. Though one of the Android articles touches this, the reason is not explained. Let’s say the application is a photo sharing application and it needs to update the image date when the user flips through the gallery. Calling
setShareIntent()sounds the easiest way. However, setShareIntent() rebuilds the list every single time. And, as I mentioned earlier, setShareIntent() runs on the UI thread. This would result in UI jankiness. Instead, it’s better to store the Intent that was given to the ShareActionProvider, and update the
EXTRAparameters in it, leaving the
ACTIONand MIME type intact. This way, the intent remains the same, and the data varies, without doing any work on the UI thread. But hey, there is not getter for this method! So, it’s our responsibility to store it in our class.
- Even getters in this file wants to ensure a consistent state. In case something had changed with the list of activities, either it loads them all once again, or reads from the disk. Which thread will this run on? Clicking on the Share icon, would ask the ActionProvider to get a list of activities. The event handling runs on the UI thread. This in turn ensures a consistent state and returns the items back on the UI thread. So, every setter or getter will (usually) be run on the UI thread and could cause considerable work to be done on the main thread.
- Lets say, some other application wants to expose Share with a custom ActionProvider like Firefox for Android. When they try to use the ActivityChooserModel, there is a reference to
PackageMonitor. Unfortunately this file is internal and has a lot of other internal dependencies. Commenting the related code will leave the history file, and the list in a stale state if some other shareable app is installed/uninstalled while this app is in the background.
Phew! Basically ShareActionProvider is some sort of a UI hog, if not used correctly. ActionProviders cannot be used outside of ActionBar, even though they provide a nice paradigm. Why Android, why?