Android

An implementation pattern for updating Android widgets using alarms

I recently added a widget to the Shush! app.  I hadn't implemented a widget before, and I found that there was some complexity involved that I didn't anticipate, so I thought I'd share the approach I ended up taking.

Some quick background - Shush! is a utility app that lets you set a "quiet time" for your ringer, restoring it back after the duration you request.  I wanted a widget that would show the time remaining while in "Shush! mode" (among other features).

The widget therefore has two modes:

1) when Shush! mode is not active, the widget is visually static:

widget-inactive.png
 

2) when Shush! mode is active, the widget needs to be updated frequently (once per minute) to count down the remaining time

 

To implement the countdown behavior, when activating Shush! mode, I register a PendingIntent with the AlarmManager for one minute in the future.  The intent calls back to a BroadcastReceiver that updates the widget's display and registers another PendingIntent for a minute later.  When Shush! mode is deactivated, I unregister the outstanding alarm and switch the widget display to its static state.

OK, that wasn't too hard.  Ship it!

Hmm, wait a sec.  During testing, I noticed that during Shush! mode, even though I'm using a "non-waking" alarm, the alarms continued to be triggered while the screen is off.  It turns out that "screen off" != "sleeping".  It doesn't make sense to waste CPU cycles updating the widget if the screen is off, so to avoid that, when activating Shush! mode, I decided to register a BroadcastReceiver that filters for ACTION_SCREEN_OFF and ACTION_SCREEN_ON.  But (sigh) it turns out that one cannot set up a BroadcastReceiver that filters on these actions via the manifest (apparently it's a special case), so I needed to do it programmatically.  Of course, if you're going to do it programmatically, you have to keep that BroadcastReceiver instance around in memory for as long as you need it to be listening, so I created a Service to keep it alive.  I start the Service when Shush! mode is activated, and the Service creates and registers the screen on/off BroadcastReceiver.   When Shush! mode is deactivated, I stop the Service, which unregisters the BroadcastReceiver.

The BroadcastReceiver handles ACTION_SCREEN_OFF by canceling any outstanding alarm, and handles ACTION_SCREEN_ON by updating the widget, and setting a new alarm.

Great, that should do it.  Oh, wait, what about reboots?  Well, unfortunately Android forgets all alarms during a reboot, so they have to be reestablished again afterward.  I do this with a BroadcastReceiver that filters for BOOT_COMPLETED.  Upon receiving the broadcast, this receiver updates the widget and, if Shush! mode is activated, starts the Service.

So there you have it - a widget, a BroadcastReceiver to handle alarms, a BroadcastReceiver to handle screen on/off events, a BroadcastReceiver to handle reboots, and a Service to keep the screen on/off receiver alive.  Never a dull day as an Android developer!

Initialize your Android component's dagger object graph on a per-method basis in your Robolectric tests

{note: this blog entry is about dagger 1}

When writing unit tests for Android applications, my go-to tools are Robolectric, dagger, and Mockito.  The combination covers a lot of ground in terms of providing "stand ins" for a lot of Android system functionality and factoring out dependencies from the objects under test by injecting mocks into them.  In my production code, my components derive from helper classes in my fb-android-dagger library, which takes care of creating object graphs and self-injecting those components during their initialization logic. fb-android-dagger's component base classes allows subclasses (including test classes) to participate in establishing the dagger modules to use when creating the object graph.  Thus, a production component might use a module which provides dependency X, while a test component could subclass from the production component and tack on a test module which overrides the production module and provides a mock X instead.

This all works pretty well, but it has a limitation -- the opportunity to override production modules with test modules exists only at the time that the component's initialization method runs.  In practice, this means that if the different test methods in a test class want different mocks to be injected into the component under test, they need to employ some technique to ensure that (1) the set of modules used to create the object graph is established by code which is specific to the test method (which rules out the @Before method, since it is method-independent), and (2) the component under test doesn't get initialized until after (1).

My usual approach to this problem is to defer initialization (which includes object graph generation and injection) of the component under test until inside the test method (as opposed to the @Before method).  In the test method, I first set up my mocks (which are fields of the test class) to provide the desired behavior, then initialize the component under test.  For the test subclass of my component, I override getModules() to supply a module (with overrides=true) that has provider methods which provide the mocks configured in the test method.  Note that this overriding module class must be non-static to have access to the mocks in the test class.

In the following example, assume that MyBroadcastReceiver is the class of the object under test, and it extends InjectingBroadcastReceiver from fb-android-dagger.


@RunWith(RobolectricTestRunner.class)
public class MyTest {
    private X mockX = Mockito.mock(X.class);
 
    BroadcastReceiver testReceiver = new TestMyBroadcastReceiver();
 
    @Test
    public void onReceive_doesSomethingWhenXIsEnabled() {
        // for this test, I want to simulate an "enabled" X
        Mockito.when(mockX.getEnabled()).thenReturn(true);
 
        // onReceive will build the object graph and inject itself using a provider
        // which returns mockX.
        testReceiver.onReceive(...);
 
        // ... verification logic...
    }
 
    @Test
    public void someMethod_doesSomethingDifferentWhenXIsDisabled() {
        // for this test, I want to simulate a "disabled" X
        Mockito.when(mockX.getEnabled()).thenReturn(true);
 
        // etc.
    }
 
    private class TestMyBroadcastReceiver extends MyBroadcastReceiver {
        @Override
        protected List<Object> getModules() {
            List<Object> result = super.getModules();
            result.add(new MockXModule());
            return result;
        }          
    }
 
    @Module(injects=MyBroadcastReceiver, overrides=true)
    private class MockXModule {
        @Provides
        public X provideX() {
            return mockX;
        }
    }
}

However, this approach doesn't work if you want to tweak the Application-scope object graph on a per-method basis, because Robolectric creates and initializes (and hence injects) the Application object for you implicitly before your test method runs.  Even if you have a test application class that overrides getModules() and adds modules with provider methods that leverage the state of the test object (i.e. using the technique above), it won't work, because getModules() will get called before the test method runs, and therefore the test method will have no opportunity to configure the test object's state prior to injection of the application object.

Robolectric does provide hooks in the form of the TestLifecycleApplication interface, whose beforeTest, prepareTest, and afterTest methods you can implement in your test application class, but they aren't helpful in this case, since they all run after your test application's onCreate() method has already been called (and thus its object graph has already been created and used to inject it).

One workaround to this problem would be to use different test application classes for different methods, using Robolectric 2.4's @Config(application=...) annotation on a per-method basis.  With this approach, you'd need your different test application classes to specify different modules in their getModules() implementations, each hardcoded to provide the desired mock behavior for the corresponding test method.  Note: here we're assuming that MyBroadcastReceiver's dependency on X is satisfied from the Application-scope object graph.


@Test 
@Config(application=TestMyBroadcastReceiverWithXEnabled.class) 
public void onReceive_doesSomethingWhenXIsEnabled() { ... }

@Test 
@Config(application=TestMyBroadcastReceiverWithXDisabled.class) 
public void onReceive_doesSomethingDifferentWhenXIsDisabled() { ... }

private class TestMyBroadcastReceiverWithXEnabled extends MyBroadcastReceiver { 
    @Override protected List<Object> getModules() { 
    List<Object> result = super.getModules();
    result.add(new MockXEnabledModule()); 
    return result; 
    } 
}

private class TestMyBroadcastReceiverWithXDisabled extends MyBroadcastReceiver { 
    @Override protected List<Object> getModules() { 
        List<Object> result = super.getModules(); 
        result.add(new MockXDisabledModule()); 
        return result; 
    } 
}

@Module(injects=MyBroadcastReceiver, overrides=true) 
private class MockXEnabledModule { 
    @Provides public X provideX() { 
        X mockX = mock(X.class); 
        when(mockX.isEnabled()).thenReturn(true); 
        return mockX; 
    } 
}

@Module(injects=MyBroadcastReceiver, overrides=true) 
private class MockXDisabledModule { 
    @Provides public X provideX() {
        X mockX = mock(X.class);
        when(mockX.isEnabled()).thenReturn(false); 
        return mockX; 
    }
}

 

That can get a little unwieldy when you start having combinations of method-specific modules, since each combination would need a different test application subclass.  To avoid that kind of proliferation, I've implemented a subclass of RobolectricTestRunner called InjectingRobolectricTestRunner, which looks for annotations like


@Test 
@With(appModules={MockXEnabledModule.class}) 
public void onReceive_doesSomethingWhenXIsEnabled() { ... } 

and takes care of seeding the annotation-specified modules into the Application object prior to its onCreate() method being called.  This required two small enhancements to InjectingApplication in fb-android-dagger: (a) a new setter method by which the InjectingRobolectricTestRunner can assign the seed modules, and (b) an update to its getModules() method, to add the seed modules into the list it returns.

In some cases, this approach may eliminate the need for test application subclasses altogether, but it works with them just as well -- the annotations establish the method-specific modules, and the test application class' getModules() method tacks on any others that are not method-specific.  As with the first approach described above, note that the test-specific modules must be hardcoded to exhibit the desired method-specific behavior. Here's the code for InjectingRobolectricTestRunner:


public class InjectingRobolectricTestRunner extends RobolectricTestRunner {
    public InjectingRobolectricTestRunner(Class testClass) throws InitializationError { 
        super(testClass); 
    }

    @Override protected Class getTestLifecycleClass() { 
        return InjectingTestLifecycle.class; 
    }

    public static class InjectingTestLifecycle extends DefaultTestLifecycle {

        @Override public Application createApplication(Method method, AndroidManifest appManifest, Config config) { 
            InjectingApplication injectingApplication = (InjectingApplication) super.createApplication(method, appManifest, config);

            List<Object> seedModules = new ArrayList<>();

            if (method.isAnnotationPresent(With.class)) { 
                With with = method.getAnnotation(With.class); 
                for (Class<?> clazz : with.appModules()) { 
                    try { 
                        seedModules.add(clazz.newInstance()); 
                    } catch (InstantiationException e) { 
                        e.printStackTrace(); 
                    } catch (IllegalAccessException e) { 
                        e.printStackTrace(); 
                    } 
                }
            }

            injectingApplication.addSeedModules(seedModules); 
            return injectingApplication; 
        }
    }

    @Qualifier 
    @Target(METHOD) 
    @Documented 
    @Retention(RUNTIME) 
    public @interface With { 
        Class<?>[] appModules(); 
    } 
}