If you’ve built an Android home screen widget before you’d know that compared to regular Android in-app user interfaces, your hands are seemingly tied regarding what you are allowed to do with UI elements. Need to use a ScrollView in a home screen widget? Nope, not supported. Want to set an onClick listener on a button to perform an action? No go, you have to broadcast a pending intent for that action to be completed later. Change the style attribute for a textView at runtime? … you get the idea.
All these incapabilities when developing home screen widgets are by design. Since widgets occupy the home screen which is the platform for launching all apps on an Android user’s device and an arbitrary number of them can be added to the home screen, widgets need to do a lot less or the perceived performance of the entire OS would suffer.
This isn’t to say you can’t create complex home screen widgets, but doing so usually entails managing a lot of state because of the limitations previously mentioned. One of the things I try to instill in younger engineers is to keep things simple, not to be conflated with avoiding writing complex systems (although that is also a viable strategy), but to manage complexity by looking for patterns then refactor and re-implement.
The crux of this post is to impart some of the knowledge I gained by building a complex home screen widget, namely the “Data Usage Widget” that accompanies the mCent Android app. This post also assumes you have an intermediate experience building Android applications and some knowledge about widgets.
mCent Data Usage Widget in two modes (top and daily)
Note: In the example snippets below, I took some liberty in removing some implementation details, imports, declarations as such by denoting ellipses (
...) in the code. Please feel free to comment on the post if the code isn’t clear or can be improved.
1. Better Control Over Widget Update Cycle
In the AppWidgetProviderInfo Metadata (the xml file referenced when declaring the widget in the your application’s manifest), this file holds the values of attributes of your widget, such as widget dimensions, layout resources, update cycle, etc. Since we are creating a more complex widget, every update to the widget could be inherently resource expensive. Whether it’s downloading resources from the internet which may consume the user’s data allotment, or reading a lot of I/O resources that can impact battery life, you probably want to do these updates at specific times or not do them at all because of the state of the device. Examples that come to mind are not updating the widget when the battery is low, or most likely when the user is most likely to not be interacting with the device (e.g. at night time). What we want to do is set up a different update schedule and disabling the update cycle in the AppWidgetProviderInfo by setting
0. To setup a different update cycle, we do this by creating a background service with a periodic task that can be stopped or restarted depending on the state of the device. Luckily we’ve written a post on how to do exactly that. If you follow those exact guidelines, the method
doPeriodicTask in the
BroadcastReceiver (PeriodicTaskReceiver.java) is where we broadcast a signal to update the widget. Example:
Now periodic updates to the widget are now controlled by your background service and can be directed to stop updating (and restart) when the device is in a certain state by making the
PeriodicTaskReceiver class listen for the appropriate events.
2. PendingIntents and onReceive
PendingIntents with conjunction with the overridden method
onReceive of your
AppWidgetProvider class are how widgets communicate to either update when some state has changed or react to an action by a user (e.g. on clicking of a button in the home screen widget). Here we are not doing anything special to how they fundamentally work, just some organization around the code to make it more readable and easier to work with.
Here we are creating methods that return
PendingIntents for use with actions that require them in home screen widgets such as “on click” events. Separating these out and grouping them together prove to be far much better than inlining them in cases where widgets have a lot of interactivity. In addition, all the PendingIntents we create have an “extra” that contain the
appWidgetIds plus another which contains the state you are trying to convey when updating the widget. When the widget provider class for your widget receives this broadcasts, the extras serves as a distinction as to where the broadcast came from and what state has to be set before the call to update the widget occur.
As for the
The above should be straight forward. If the extra with the key named the constant
WIDGET_IDS_KEY exists in our intent, we are probably got here by way of our periodic updater we set up initially, the default
onReceive implementation is then called. If we the aforementioned extra exists, we then save the state passed as extras in the intent and call the
onUpdate method of the widget provider class.
3. Caching Image Resources
Displaying resources fetched from the internet on home screen widgets is a bit tricky due to the asynchronous nature of fetching assets from a url, and the non-immediate feedback loop when dealing with widgets. An obvious way to solve this problem is to start an async request to fetch resources, have a placeholder asset in the interim, save the resource when the download has finished, then triggering an update when the save is completed. It gets increasingly more complicated when you have multiple downloads and might only want to update the widget as few times as possible as opposed every time a download is completed to avoid other expensive operations that come with updating the widget.
The approach here is first having a way of saving and fetching the resource to disk :
In the class above, we use the application’s cache directory dictated by the Android OS. This has an advantage that the disk space is automatically reclaimed in the case it is needed by other system resources. The
DownloadImageTask class referenced above contains the constant
FILENAME_PREFIX, which helps to identify files saved by this process. It also holds the mechanism for asynchronously downloading the assets:
Start a download with “new DownloadImageTask (context, imageId).execute(imageURL);” in your android widget update cycle when a call to
null. The subsequent widget update triggered by the completion of the downloaded resource should now fetch the cached image.
Another tip that can be applied here is buffering of calls to manually update a widget on a scheduled
TimerTask. Doing so allows multiple calls to update the widget within a certain period of time to only execute the actual update once. Helpful when downloading multiple assets using the “DownloadImageTask” but minimizes updating of the widget caused my multiple download completions. This can be achieved be breaking out how we manually update widget into it’s own class and setting up the timer task:
And that concludes this post regarding lessons and tips learned from building fairly complex home screen widgets. Hopefully with these suggestions you too can manage state and the limitations imposed on developers when dealing with Android widgets.