WorkManager, App Widgets, and the Cost of Side Effects

WorkManager is “a nice piece of kit”. It has a fairly clean and easy API and hides a lot of the complexity of scheduling background work that is not time-sensitive.

However, it has side effects.

To be able to restart your scheduled work after a reboot, WorkManager registers an ACTION_BOOT_COMPLETED receiver named androidx.work.impl.background.systemalarm.RescheduleReceiver . To be a good citizen, WorkManager only enables that receiver when you have relevant work and disables it otherwise. That way, your app does not unnecessarily slow down the boot process if there is no reason for your app to get control at boot time.

However, enabling and disabling a component, such as a receiver, triggers an ACTION_PACKAGE_CHANGED broadcast. Few apps directly have any code that watches for this broadcast, let alone would be harmed by having that broadcast be sent more times that might otherwise be necessary.

App widgets, though, are affected by ACTION_PACKAGE_CHANGED . Specifically, ACTION_PACKAGE_CHANGED triggers an onUpdate() call to your AppWidgetProvider .

That too may not be a problem for most app widgets. Ideally, your AppWidgetProvider makes no assumptions about when, or how frequently, it gets called with onUpdate() .

This bug report points out one area where this is a problem: with an AppWidgetProvider scheduling work in onUpdate() . The flow then becomes:

A “regular” onUpdate() call comes in

call comes in Your AppWidgetProvider schedules some work with WorkManager

schedules some work with WorkManager enables RescheduleReceiver

enables That triggers ACTION_PACKAGE_CHANGED , which triggers an onUpdate() call

, which triggers an call Your AppWidgetProvider schedules some work with WorkManager again

schedules some work with again WorkManager eventually gets through those two pieces of work

eventually gets through those two pieces of work WorkManager disables RescheduleReceiver , since it is no longer needed

disables , since it is no longer needed That triggers ACTION_PACKAGE_CHANGED , which triggers an onUpdate() call

, which triggers an call Your AppWidgetProvider schedules some work with WorkManager

schedules some work with WorkManager enables RescheduleReceiver

enables And we’re in an infinite loop

The recommendation from Google is to avoid unconditionally scheduling work with WorkManager from onUpdate() . Instead, only do it if you know that the work is needed and that it is safe to do so, meaning that you will not get into the infinite loop.

That advice may be difficult for some to implement.

Beyond that, WorkManager is the one causing the side effect. In principle, WorkManager should provide options to avoid or manage that side effect. Side effects from libraries are “negative externalities”, to use an economics term. They are like pollution: the developer of the library does not bear the cost, whereas users of the library do, and sometimes users of the apps using the library do. If your library has the potential for side effects, be sure to document those side effects and, to the greatest extent possible, give ways for developers to manage or mitigate the side effects.

IOW, library developers need to “give a hoot — don’t pollute”.

Want an expert opinion on your Android app architecture decisions? Perhaps Mark Murphy can help!

— Nov 24, 2018