IoC in native Android apps
Recently I spent some time producing MeasureMe, a low friction personal measurement tool for Android devices. The development time for the first version was four weeks. Looking at my check-ins for the project I see that I spent the first one and a half weeks getting the architecture sorted out, that is almost a third of my time. It was my first “proper” native Android application, and I did not have long to complete the project so I found it interesting to reflect on what I considered essentials of software development. The big pieces of the architecture that needed sorting out were
- Logging
- Inversion of Control
- Unit Testing
- Automated Building
How different this list would have looked five years ago, in that time IoC and Unit Testing have elevated themselves to essentials.
I though it would be interesting to explore the decisions around some of these architecture choices, starting with IoC. There were two main options RoboGuice and Dagger.
RoboGuice
The RoboGuice patter for dependency injection is pretty standard. My background is in Castle Windsor, LinFu, NInject and I found it similar to those frameworks. For example to inject a logger into an activity the activity would be declared like this
public class OpenSourceLicensesActivity extends RoboActivity { @Inject private ILoggerFactory logger; @InjectView(R.id.txtAndroiSupportLibraryLicense) private TextView v7license; @InjectView(R.id.txtRoboguiceLicense) private TextView roboguicelicense; @InjectView(R.id.txtSlf4jLicense) private TextView slf4jlicense; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_open_source_licenses); logger.getCurrentApplicationLogger().debug("OSLicensesActivity.onCreate()");
The logger is decorated with the @Inject attribute and then is just used without any initialisation. The Activity extends RoboActivity which injects the dependency.
Then the IoC container looks like this
public class IoCModule implements Module { @Override public void configure(Binder binder) { // singletons binder.bind(ILoggerFactory.class).toInstance(new SlfLoggerFactory()); binder.bind(ISystemTime.class).toInstance(new SystemTime()); // reops //binder.bind(new TypeLiteral>() {}).to(AlarmRepository.class); binder.bind(IAlarmRepository.class).to(AlarmRepository.class); binder.bind(IMeasurementRepository.class).to(MeasurementRepository.class); binder.bind(IMeasurementScaleRepository.class).to(MeasurementScaleRepository.class); binder.bind(IAlarmMeasurementRepository.class).to(AlarmMeasurementRepository.class); // writers binder.bind(IRecordedMeasurementWriter.class).to(RecordedMeasurementWriter.class); // logic binder.bind(IPreferencesHelper.class).to(PreferencesHelper.class); binder.bind(ILogSender.class).to(LogSender.class); }
And finally we initialise the container in the application class.
public class MeasureMe extends Application { // Deliberately not using IoC for this - as this class is where IoC is setup final private Logger _logger = LoggerFactory.getLogger(MeasureMe.class); @Override public void onCreate() { _logger.warn("Application started v{}", getVersionName(getApplicationContext())); super.onCreate(); RoboGuice.setBaseApplicationInjector( this, RoboGuice.DEFAULT_STAGE, RoboGuice.newDefaultRoboModule(this), new IoCModule()); _logger.debug("Application IoC bound");
The @InjectView in the activity class is a short hand method of attaching UI dependencies, it eliminates the calls to findViewById.
Dagger
By contrast the Dagger activity class looks like this
public class MainActivity extends Activity { @Inject ILoggerFactory logger; private void initIoC() { MeasureMe app = (MeasureMe) getApplication(); app.getObjectGraph().inject(this); } @Override protected void onCreate(Bundle savedInstanceState) { initIoC(); logger.getApplicationLogger().debug("MainActivity started - Dagger IoC 2"); super.onCreate(savedInstanceState);
Notice that this time the Activity does not extend a magic base class, instead we need to initialise the IoC container ourselves, of course this could easily be removed to our own custom base class.
The IoC container look like this
@Module(injects = MainActivity.class) public class IoCModule { @Provides @Singleton ILoggerFactory provideLogger() { return new SlfLoggerFactory(); } }
The syntax here is a little different from other frameworks. This is mainly because the injection mechanism does not do all its magic at runtime like RoboGuice, rather it emits Java at compile time so that some of the load is removed when the application is running.
Finally the main application looks like this
public class MeasureMe extends Application { final Logger logger = LoggerFactory.getLogger(MeasureMe.class); private ObjectGraph objectGraph; @Override public void onCreate() { logger.debug("Application started"); objectGraph = ObjectGraph.create(new IoCModule()); logger.debug("Object graph bound"); super.onCreate(); } public ObjectGraph getObjectGraph() { return this.objectGraph; } }
The method getObjectGraph is used by our activities to inject their own dependencies.
Selecting a framework.
It was tricky to pick between these two approaches, as both mechanism appeared to work as expected. In the end I chose RoboGuice. The main reasons were
- The IoC Module syntax is more familiar to me given my background in Castle Windsor, LinFu, NInject.
- The product did appear to be more mature and build upon Google Guice
- Dagger relied upon a specific version of the javawriter library
- The need to write the extra code to inject dependencies when using Dapper seemed clunky.
Reflections
Obviously the decision between the two frameworks was made early on in the project. With the benefit of hindsight there are some other factors that should be considered.
As it turned out one of the weaknesses of RoboGuice turns out to be the need for specific Robo* base classes, for example RoboActivity, RoboService. The problem manifested itself when I wanted to use the new ActionBar, I used the Google Android Support Library however RoboGuice does not yet support ActionBar, though support is planned for version 3.
This has meant that in the end I did need to write the extra code to inject myself in activities that used the ActionBar, which was most of them. So the code looked more like this
//public class MainActivity extends RoboActivity { public class MainActivity extends ActionBarActivity { @Inject private ILoggerFactory logger; @Inject private ISystemTime systemTime; @Inject private IPreferencesHelper preferences; @Inject private IAlarmRepository alarmRepo; @Inject private IMeasurementRepository measurementRepo; @Inject private ILogSender measurementLogSender; AlarmArrayAdapter adapter; //@InjectView(R.id.lvAlarms) private ListView lvAlarms; //@InjectView(R.id.lvAlarms) private LinearLayout layoutNoData;; private void HookupControls() { // to get the ActionBar I have to give up the InjectView until RoboGuice 3 // Inject is OK but its just too fiddley to get the view to work // we can override this for testing - cheesy but gets us out of a hole lvAlarms = (ListView)findViewById(R.id.lvAlarms); layoutNoData = (LinearLayout)findViewById(R.id.layNoData); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // this will inject non view dependencies RoboGuice.getInjector(this.getApplicationContext()).injectMembers(this); logger.getCurrentApplicationLogger().debug("MainActivity started"); HookupControls();
I also lose the ability to inject UI dependencies. Given this restriction I guess there is even less to choose between Dagger and RoboGuice, I guess it I would need to look again when RoboGuice v3 is released.