Toss a Coin to your Widget! Or don’t…

Part 1 of 3

Miriam Cordes
intive Developers
4 min readJun 22, 2021

--

Written by Sarah Will, Jenni Thieroff and Miriam Cordes

A descriptive article image showing a blurred IDE, coins, a smartphone with an installed Home screen widget and a screaming emoji
Toss a Coin to your Widget! Or don’t…¹

Widgets on Android have been around for a long, long time now. But with the recent introduction of Home screen widgets on iOS, they are destined to become more popular on Android as well.

But because of the age of widgets on Android, let’s just say that their API isn’t among the brightest stars of Android development. If you look at the official guide for widgets, you’ll quickly notice that not many updates have happened since then. The screenshots showcasing the use of widgets show the nowadays seemingly antiquated UI of Android 4.4. Of course, we can only hope that the documentation will be completely revised during the upcoming renewals of the widget API with Android 12. Fingers crossed!

In this series of articles, we want to take a deeper look into the caveats and pitfalls of developing a widget for Android. This article is not an essential guide on how to introduce a widget, you can already find good documentation on that here: https://developer.android.com/guide/topics/appwidgets

In the first part of the series, we want to focus on widgets and background processes. Let’s dive in!

A simple widget on the Android Home screen²

Using a Service will (probably) get you in trouble

The official widget documentation mentions the use of a Service (android.app.Service) when describing how to use the onUpdate() callback of your AppWidgetProvider class:

This method is also called when the user adds the App Widget, so it should perform the essential setup, such as define event handlers for Views and start a temporary Service, if necessary.

The use of Service has been limited since Android 8.0 introduced background execution limits in 2017. If you try to start a standard Service from your code in onUpdate(), this will only work if your app is not identified by Android as running in the background.

This might still be the case if you just switched to the Home screen to add a new widget. But the moment your app is classified as running in the background, starting a service will result in an IllegalStateException that crashes the app process, so using Service (or IntentService) for Widget updates should no longer be recommended without noting that.

In case you have longer running actions triggered by onUpdate(), you can, for example, use JobIntentService and JobScheduler. Unfortunately, as of now, WorkManager is not an option here, which we will explain in the next part.

App Widget and WorkManager — A fatal combination

In 2018 Google introduced the WorkManager API, which can be used to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or the device restarts. WorkManager replaces previous Android background scheduling APIs like FirebaseJobDispatcher, GcmNetworkManager or JobScheduler. FirebaseJobDispatcher and GcmNetworkManager were even deprecated in 2020 in favor of Android WorkManager. And while WorkManager is a powerful and easy to use API, there is one big pitfall if you combine it with widgets: the combination might lead to repeated, unwanted calls of the onUpdate() function of the AppWidgetProvider class. These calls result in the UI being updated repeatedly and the widget “flickering”, since the layout of the widget constantly switches between initialLayoutand the updated widget layout.

CommonsWare has an excellent blogpost about this side effect where they explain the technical details that lead to the unwanted behavior of the widget. And although there are multiple issues (e.g., issue 115575872 or issue 119920965) filed at Google about this side effect, it does not look like this will be fixed any time soon. But why exactly is the combination of WorkManager and the widget so problematic? Let us have a look at some technical details of WorkManager:

Whenever some work is scheduled with WorkManager, an ACTION_BOOT_RECEIVER(androidx.work.impl.background.systemalarm.RescheduleReceiver) is registered to restart scheduled work after a device rebooted. This results in an ACTION_PACKAGE_CHANGED broadcast which triggers the onUpdate() function of the AppWidgetProvider Class. After WorkManager has finished it disables the ACTION_BOOT_RECEIVER since it is no longer needed, resulting in an ACTION_PACKAGE_CHANGED broadcast, triggering the onUpdate() call.

If you happen to use WorkManager inside the onUpdate() function of the AppWidgetProvider Class, you will now be stuck in an infinite loop. And if you use WorkManager for anything else in your app or even if your app uses a third-party framework that relies on WorkManager onUpdate() will be called whenever some work is scheduled or WorkManager has finished.

Illustrative Diagram displaying the loop when using WorkManager within the onUpdate method of AppWidgetProvider
WorkManager onUpdate() loop

Unfortunately, there is no real fix for this issue now, but you can avoid that WorkManager disables the RescheduleReceiver and therefore prevent the ACTION_PACKAGE_CHANGED broadcast being triggered by scheduling another WorkRequest in the distant future (e.g., 10 years, see issue 115575872).

So far, so good for our first article. Stay tuned for part 2, where we get our hands dirty with widgets and the UI. Cheers!

[1]: Emoji by Noto Emoji fonts
[2]: Photo of carrots by Chokniti Khongchum from Pexels

--

--