360andev mike nakhimovich share fb

Swordfighting with Dagger

Are you tired of seeing examples of CoffeeMakers? Have you ever wondered if you really need that provides method? Want to learn how to inject something besides Thermosiphon?

This 360AnDev talk will cover real life patterns and use cases for Dagger 2 as it’s implemented in the New York Times newsreader app. We’ll compare the Dagger Way with its unwieldy alternative and dive into project organization, simplicity in testing, object graphs, and how to never forget to save/restore your intent extras.

This talk is a great sequel to Dan Lew’s talk on Dependency Injection Made Simple.


Introduction (0:00)

My name is Mike Nakhimovich, and I’m an Android developer at The New York Times. This is a follow up to the wonderful introduction that Dan just gave to dependency injection. My talk is going to have three parts on how we use Dagger 2 at the The New York Times. I’m going to answer some of the questions regarding how to actually architect and structure a fairly large code base to work with Dagger.

What is Dagger (0:45)

First, a little intro. What is Dagger? Like many of you know, Dagger gives you an alternative way to instantiate and manage your objects. Rather than you instantiating them inside of your classes, Dagger gives you a location within modules for you to be able to instantiate. A little history lesson, Guice, a library developed at Google was a dependency injection library, which I kind of consider Dagger 0. It had heavy reflection, which was very slow on Android.

So the fine folks at Square released Dagger 1 a few years ago, which was able to take away a lot of the expensive calls that Guice was making, and give us a dependency injection library that was specific to Android. This was great for most cases. It did have a few problems, like ProGuarding was a little bit difficult, since there was some reflection. Even though it was one of the fastest dependency injection libraries we had, because there was some reflection, it still wasn’t as fast as it needed to be.

So, that’s where we are now, Dagger 2, it was open sourced, again, by Google. And it is basically the continuation of what Dagger 1 was with some speed improvements.

Why do we need it? Why do we need to do dependency injection, dependency inversion, or any of this? To me, good code is testable code. By that I mean it’s not code that has tests, but it’s at the very least code that you can write tests for. The reason that you want to write tests is to have less developer anxiety.

I started at the Times about a year ago, and I was really happy that we had about 1,000 unit tests at that point, which made it dramatically easier for me to commit new code without breaking something in production. Similarly, now that we have interns starting for the summer, they also are going to be able to benefit from us having more tests.

For us to have tests, what we need to do is structure our code base in a manner where it is testable. Not all code is testable, only code that is organized in a certain manner can be tested.

Let’s look at something that looks like my code in the beginning:


public class MyClass {
    private Model model;
    public MyClass() { this.model = new Model(); }

    public String getName() { return model.getName(); }
}

I started Java in high school, and this looked like a simple class that I would have made. I made a class, and this class had a single dependency that was a Model. I said, well, why not end the constructor and instantiate this model?

The problem with doing it this way is we have a single method which we’re expecting someone to call our getName method. What we can’t do is test reliably whether getName was called.

Luckily, the internet exists. I spend a lot of time on Reddit, and on the Android subreddit. The internet told me that I need to externalize my dependencies. What I mean by externalizing dependencies is to my constructor, I should be passing in that model object, rather than instantiating it within this class:


public class MyClass {
...
    public MyClass(Model model) { this.model = model; }
    public String getName() { return model.getName(); }
}...

public void testGetData(){
    Model model = mock(Model.class);
    when(model.getName()).thenReturn("Mike");
    MyClass myClass = new MyClass(model).getName();
    verify(myClass.getName()).isEq("Mike");
}

You shouldn’t really have new keywords within any of your objects, passing in your dependencies, which will allow you to write tests like testGetData, where you can create a mock model object, record what behaviors should happen when someone calls getName. You can then make an instance of your class and validate whether, with that particular model, getName delegated and returned the proper value that we wanted to see.

This is really simple. Again, this isn’t really Dagger. This is just externalizing your dependencies to make it easier for you to pass in one dependency during your regular source and another dependency during your test when you’re actually running them.

The next question is, where’s Model going to come from? This is where dependency injection and Dagger, specifically, is going to come to your rescue:


@Provides
Model provideModel(){
    return new Model();
}

What Dagger lets you do is externalize these object creations. We externalize them by making something that looks like a factory method. It’s simply a method that creates an object and returns it, and the only thing that is Dagger specific here is that we will annotate this method with @Provides:


@Module
public class AppModule{
@Provides
Model provideModel(){
    return new Model();
}
...

We’ll take this @Provides method and stick it into a module. Think of a module as a container for a bunch of these Provides methods. Normally you would have one too many of these Provides methods within a module. That is the first thing that we’re getting to that is specific to Dagger 2 instead of Dagger 1

Dagger 2 has the concept of a @Component. A component is something like a manager. This component both contains all of those dependencies, and also, that class that everything that wants to use your Dagger dependencies is going to be connecting to:


@Singleton
@Component(modules = {MyModule.class})
public interface AppComponent {
    void inject(MyActivity activity);
}

What we have here is an interface AppComponent, which we annotate with what modules should be in this component. Think of it as what dependencies should be a part of this component.

When we compile and run an annotation processor, and it gives us an implementation of this component, I think of this as just a dependency manager that is just containing all the actual instances that were inside of all of the modules that were a part of this component.

Here, you’re looking at me, not making an instance of my interface:


component = DaggerAppComponent.builder()
    .myModule(new MyModule(this))
    .build();

Get more development news like this

I’m making an instance of a generated AppComponent that Dagger was able to generate based on what interface I told it to use.

So in Dagger 2 world this component is what that object graph call was in Dagger 1:


protected void onCreate(Bundle savedInstanceState) {
    getApplicationComponent().inject(this);

Rather than you calling a single class that is an object graph and passing it your modules, you will be instead making components that are going to be generated, that you will then, hook into, from your activities, from your views, from your services, from all of these Android classes that do not have constructors.

Once I register by saying, “hey, I want this component to satisfy all of the dependencies,” or “inject all of the dependencies for this particular activity,” we get to have some fun. We can then start annotating fields with inject, and once we call that `getApplicationComponent().inject(this)Next, Dagger will satisfy all of those dependencies with actual instances.

We can do this in one of two manners. We could either annotate fields for classes that we don’t have constructors for, or we can annotate constructors directly, which will then have their arguments pass in from Dagger. Dagger gives you two ways to do this.

That’s as far as I really want to go as an intro of how Dagger and how dependency injection works. Now let’s get to more of the fun stuff of how we’re able to do this in a fairly large application.

Main Topics (7:20)

I’m going to break this up into three sections. The first section is going to be talking about our module and component architecture.

Our architecture is a little robust. We have six different libraries that we pull into our main application. These libraries have their own Dagger modules. We also have Build Types and flavors, spanning Google and Amazon. Being a news reader app, we have a huge user base on Kindle.

The second topic that I’ll be getting into is scopes. How we’re able to have objgraphs that are both singletons for our whole application or objects that are scoped just for a particular activity.

And finally, I’m going to touch on testing. Some of the problems that people have had upgrading from Dagger 1 to Dagger 2 is how you’re going to be overriding from tests, and we were able to find a simple Java pattern to allow us to do that.

Code Organization (8:25)

Our app is broken up into six different build variants, three for each platform: Amazon and Google.

  • We have our debug builds for our developers
  • We have a beta group that we release to; that is a small, almost like a release build.
  • We also have our release build which goes out to our millions of users.

How do we manage all of this? What does this look like?

The first thing I want to talk about is modules. Where do we keep all the different modules that we’re then going to be pulling into the components that our app are going to be using?

The first type of module is our App Module. Currently, we only have one production app, which is our flagship reader application. But we do have times when we have Hack Week projects where teams want to build, say, a crossword app or some other app that is going to be wanting to use Dagger. That is a module that is specific to each client application.

We also have Library Modules. I’m on the framework team, and we build and maintain libraries that each already have their own Dagger module which allows apps that will be using our libraries to use Dagger. And they should also be able to use those classes without ever including Dagger.

Next, we have Build Type Modules. These would be modules specific to debug, beta, and release.

And finally, we have Flavor Modules for Amazon and Google which is really important, since Google and Amazon seem to want to do payments differently, and where you’re doing push notifications differently.

App Module (10:01)

The App Module manages all of the singletons that are for the application. An example of this would be our parser. We provide an interface of a parser, which is actually an implementation of GSON. We pre-configure that within our app module so that anytime anyone wants a parser within our application they’re going to get the same singleton, or the same exact object that will be already pre-configured with all of our type adapters and serializers that we know our code is going to need.

Some of these objects are IO managers. So we have a custom network layer and also a persistence manager. Both of those will depend on this parser and also give us a singleton going to the REST application whether someone wants to write something from or to disk or go to the network.

Finally, the third type of interesting object that we’re holding within our app module are configuration objects. Like I said, we had different library projects. These library projects will expose some kind of manager that our client application will be able to hook in to. But, for example, for our e-commerce library to work, our client application, the one that is actually including these libraries, we’ll need to have an e-commerce config.

At first, we were doing something where we first instantiate an instance of our e-com manager. Then to that, we said whatever our config is. But we thought we could do a little better than that. We wanted to have some kind of validation that will show that our e-commerce manager, which is coming from our library project, needs to have some provided config that is going into it.

Let me show you how we do that:


@Module
public class ECommModule {

@Provides
@Singleton
public ECommBuilder provideECommBuilder(ECommConfig config){
return new ECommManagerBuilder().setConfig(config);
}

Like I said, we had our app module providing an EComm config. Just a simple @Provides method that’ll return an ECommConfig. We then have our library modules, here’s one of them, our ECommModule, which is living in an actual AAR. We are going to be returning an instance of an ECommBuilder, this is one of our dependencies for our e-commerce for all of our payment stuff.

What’s interesting here is that we’re going to be providing a config from the client application to the library. Now we’re going to be providing an interface here and we’re doing that so that our reader application can give us an EComm reader config. If we have a crosswords application it can give us the crosswords ECommConfig.

As long as there is a module within that same objgraph or the same component that has this ECommConfig our graph will validate and someone will be able to use this ECommBuilder.

To take it one step further, we have our EComm library, and as I described, we need to be able to support e-commerce for both Amazon and Google. We have our Amazon variance that needs the Amazon implementation of our e-commerce, and our Google variance for our Google e-commerce.

How can Dagger help us with this? Let’s move on to our next concept which is qualifiers within Dagger:


@Module public class ECommModule {

@Provides @Singleton
public ECommBuilder provideECommBuilder(ECommConfig config){
return new ECommManagerBuilder().setConfig(config);
}

@Provides @Singleton @Google
public ECommManager providesGoogleEComm (ECommBuilder builder,
GooglePayments googlePayments)

Not only is Dagger able to provide for you an implementation of an interface, but you can also type using a qualifier those implementations. What you’re looking at here is that I am returning an ECommManager for the rest of my application to use. I’m saying that I’m providing it. I’m saying that it’s a @Singleton, and I’m adding a third annotation here, which is a qualifier annotation saying, add @Google.

What this means is that if someone tried to inject just an ECommManager, it wouldn’t let them. They’re going to have to do @Inject, @Google to get the particular ECommManager that is coming from this Provides method.

Why is this important?


@Module public class ECommModule {

@Provides @Singleton
public ECommBuilder provideECommBuilder(ECommConfig config){
return new ECommManagerBuilder().setConfig(config);
}
...
@Provides @Singleton @Amazon
public ECommManager providesAmazonEComm (ECommBuilder builder,
AmazonPayments amazonPayments)

Because we also are going to be providing, from the same module, another ECommManager that is going to have all of the Amazon payments rather than the Google payments that will go into our ECommBuilder. We have this library now, and it’s providing two different implementations of our e-commerce object.

Flavor Module (14:08)

Now let’s flow it into our Flavor Modules:


@Module public class FlavorModule {

@Singleton
@Provides
ECommManager provideECommManager(@Google ECommManager ecomm)
}

Now back to our main project. Our client project would then source Google and Amazon, and will also have something that is called a flavor. This FlavorModule will be providing to our application a non-qualified version of an ECommManager. Before, what we were doing from the library is providing a Google or an Amazon ECommManager. We take that dependency and bring it into another @Provides method within our FlavorModule. Then, from our FlavorModule we can provide just an ECommManager that’ll be either the Amazon or the Google one.

Based on which flavor you’re currently choosing within Android Studio, it’ll load the particular Flavor Module, either the Google or the Amazon Flavor Module and return from it a implementation of an ECommManager which will be backed by code that should only go into an Amazon APK or a Google APK.

What this allows us to do by having this Flavor Modules is Proguard out the other code. Unfortunately, if we left the Amazon payments code in our Google build, the APK won’t actually compile. Same thing with the Amazon APK. So, we used Proguard.

Since there will only be an execution path to get our ECommManager for Google within this flavor, the other one can be stripped out with Proguard without a problem. It’s important to see that our library is providing both Amazon and Google implementations same object. But due to us having a Flavor Module, only one will go into one build variant or another.

Type Module (15:52)

The same way that we have modules for Google and Amazon, we have modules for debug, beta and release. I’m not really going to show the module, but I’ll talk about some of the things that this helps us manage. For one thing, logging. We use SLF4J at work and the reason that we use this is because we want to use the same logging library for our Java projects as we do for our Android projects.

What we do within our Type Module is return our logger and we will be returning different implementations of this logger. In our beta builds, those are the ones that are going out to small beta groups, we want to have the most amount of logging-in to make sure that we know what our users are doing. Within our release build, we’ll be returning a No-Op implementation of this logger.

What you should be seeing here is the same way that as our Flavor Module, our Type Module can now pick for our build type what version of logging it needs. We do the same thing for payments. Where our debug build, which is what our developers use, we’ll use a No-Op version of payments. You don’t need to enter credit cards to subscribe.

Finally, the device ID. We’ll use a static device ID for our debug builds so that we’ll be able to check the logs on the server and know that these were builds that came from a developer phone, not from a build that’s in the wild.

Those are our modules and that’s how we split them. We have some modules in libraries, some that are in flavors, and some modules that are in our build types. They’re living in all the different source trees and in external libraries.

Component Composition (17:27)

Next I want to look at component composition. A component is that concept that Dagger 2 has that Dagger 1 did not. By having a component it allowed Dagger to remove any of the reflective calls that you had.

Now, the first thing we’re going to do is make a base component:


interface BaseComponent {
    void inject(NYTApplication target);
}

Using Dagger 1, you put all of those activities and services and classes that you want registering with Dagger within a module like little constructor or module annotation. While Dagger 2 gives you a class to do this and they call that class a component.

What we’re going to do is make a component and here we’re going to put a base component into our source main and this base component will have inject methods for registration only for classes that live within our main source.

What we’re going to do next, is within source flavors, so source Amazon and source Google, the same place we have these Flavor Modules, we’re going to make a component that’s going to use Java inheritance and inherit from our base component:


public interface FlavorComponent extends BaseComponent {
    void inject(ADMessaging target);
}

The reason that we want to do this is because we have activities and services that need to be in either the Amazon or the Google build. Here is where we bring those inject methods in. ADMessaging is what we use to do push messaging for Amazon builds and we need to register it with Dagger. We’ll leave an inject method within our FlavorComponent.

And finally, we have our top level component:


public interface ApplicationComponent extends FlavorComponent {
}

This is our ApplicationComponent. It’s going to live within the build type, source debug, source beta, or source release. It’ll extend from this FlavorComponent, which is extending from the base component. This is the only component that Dagger will actually be aware of:


@Singleton @Component
public interface ApplicationComponent extends FlavorComponent {
}

When I said the other things were components, they were just base classes, middle-tier classes that this was extending from. This is the first component and the only component that Dagger’s going to see as a application component:


@Singleton @Component(modules =
{ApplicationModule.class, FlavorModule.class, TypeModule.class,
AnalyticsModule.class, ECommModule.class, PushClientModule.class })

public interface ApplicationComponent extends FlavorComponent {
}

That’s why on this application component which is going to be living within each build type we will be adding all our modules. We’re going to add that first application module. Then add the FlavorModule, which will be whichever flavor you currently have selected. We’ll add the TypeModule. We’ll also add all of those library modules.

Since everything is on the same component, it’s going to be on the same, objgraph and we should be able to provide stuff from one module to the next. That’s how we’re able to provide stuff into our ECommModule which is in the library project from our application module that is depending on that library.

Anything that’s going to be registering with this component is going to have access to all of the providers for the particular build variant that we are looking for. That’s how we declare our component for Dagger to see.

Usage of Generated App Components (20:28)

Now let’s look at how we can register with this component. We can start injecting all of these classes. We made a little helper class at the New York Times which we call a ComponentFactory:


public class ComponentFactory {
    public AppComponent buildComponent(Application context) {
        return componentBuilder(context).build();
    }

    // We override it for functional tests.
DaggerApplicationComponent.Builder componentBuilder(Application context) {
    return DaggerApplicationComponent.builder()
        .applicationModule(new ApplicationModule(context)}
}

There’s a little bit of boilerplate to create a component. You’re going to be calling your generated code that has the same name as your component. You’ll also have to be passing into a builder each of the modules that have a constructor that you’re trying to pass a context to. We’ve rolled this all into a simple, convenient class we call an App Component Factory. It’s also important to not that this component builder we keep packaged local, so that we can override them from tests. I’ll get to that near the end.


private void buildComponentAndInject() {
    appComponent = componentFactory().buildComponent(this);
    appComponent.inject(this);
}

public ComponentFactory componentFactory() {
    return new ComponentFactory();
}

Within our NYT application class, we’re able to make an instance of our ComponentFactory. We get to build our component and both retain this component within our application class and have Dagger inject all the dependencies into our application class. That is where that app component is going to live.

Anything that’s going to want to use that appComponent will be able to from the application context, and then call the inject method. That’ll be true for anything across any flavor or any build type.

Activity Scope (21:50)

Now I want to step into the activity scope. Everything that we’ve been talking about now has been application scoped. We have objects like our network client, which is an application singleton. We have things like our e-commerce that is going to be application scoped as well. You only have one of them.

Dagger lets you set up scopes. Besides just having an application scope, like singleton, we can have a scope for our activities. The way that we do this is with sub-components.

What we’re going to do is make an activity component. Before we had an application component. This application component was one for our whole application. We made an instance of it and retained it in our application class. We could also make activity components that’ll be one per activity. Because this is going to be one per activity it can both bind to our activity context, so it can provide it.

It could also share objects that are going to be activity singletons. I know that’s not the right name for it, but that’s how I think about it. It’ll be singletons that are going to be one per activity that you can share between all of the views, fragments, and presenters that will be under that activity.

Now we’ll first start with the component and then go into our modules, the reverse of how we did the application one. Here we have an activity component:


@Subcomponent(modules = {ActivityModule.class, BundleModule.class})
@ScopeActivity
public interface ActivityComponent {
    void inject(ArticleView view);
}

Add to AppComponent:

Activitycomponent plusActivityComponent(ActivityModule activityModule);

Rather than marking it as a component, we will mark it as a @Subcomponent. We’ll also define what modules is going to be specific to each activity.

We have our application component with all of those @Provides methods. All of those instances that are for the application. Here we’re going to be adding in more Provides methods and more ways to inject members to other modules.

We’ll also be creating our own scope annotation. This one is @ScopeActivity. That is different than the qualifier annotation we used because this one will define scope rather than try to differentiate two things.

We’ll also register all of our views, activities and anything else that we want to be using in this graph. The graph that can provide stuff that is for a particular activity, rather than for the whole application. Finally, we’ll add a convenience method to our application component. You can call this anything you want, what is on the bottom, what is important is that it’s going to return an ActivityComponent.

And you’re going to be adding this to your application component interface:


    public final class ActivityComponentFactory {

    public static ActivityComponent create(Activity activity) {
        return ((NYTApp)activity.getApplicationContext).getComponent()
        .plusActivityComponent(new ActivityModule(activity));
    }
}

Once you add that you can create, just like we had an application ComponentFactory, we’ll have an ActivityComponentFactory. That will get a reference to our application component from our application class, and it’ll call that plusActivityComponent method passing it whatever modules are going to be in here that depend on the activity context. We can instantiate our activity module, pass it the activity context, and now this module will be able to both inject an activity context and be activity specific.

Similar to how we were able to instantiate our application component, we can instantiate an activity component:


public void onCreate(@Nullable Bundle savedInstanceState) {
    activityComponent = ActivityComponentFactory.create(this);
    activityComponent.inject(this);

I’m doing it here in an onCreate of an activity. We can call inject which will then satisfy all of the field and all the fields that this activity would have had.

Activity Component Modules (25:35)

Next I wanted to go into our activity component modules. You notice we had our activity component and it had two different modules: activity and bundle.

First I’m going to talk about the activity module. It’s something that you’ll see in most applications. Then I’ll talk about our bundle module, which is my favorite part of the New York Times application.

Our activity module provides a few different things. We used to use EventBuses and recently, or not recently, but we moved to RxJava. One of the things that we provide is Publish Subjects, which we use as mini Buses, so that we can listen for changes from one of you to another. That is nice when you’re trying to have two or three views that may be in different panes, and you want them to talk to each other. You can use one of these subjects that has a Bus.

We also built something for accessibility, specifically a Reactive Text Resizer. We have some class you can register with so that when users resize the text, it’ll resize all of the views within whatever your view is. That is important.

We’re a news application that is on the phone and we have a startling amount of users that can barely read one letter on a phone. We have to go all different sizes of what things look like. And finally, we have a SnackBar Util which is an injectable class that we retain an activity in, so that we can make Snackbars without passing the activity in. Let me go through each of those.

First, we have two @Provides methods that back our font resizing. We’ll create a PublishSubject, which you can think of as a listener that you can subscribe to and then propagate values from:


Font Resizing
@Provides @ScopeActivity @FontBus
PublishSubject<Integer> provideFontChangeBus() {
    return PublishSubject.create();
}

@Provides @ScopeActivity
FontResizer provideFontResize( @FontBus PublishSubject<Integer> fontBus) {
    return new FontResizer(fontBus);
}

We take this PublishSubject which we typed as our @FontBus Publish Subject, and we will inject it into the provider for our FontResizer, so that our FontResizer will then be able to use this external object to notify subscribers that a font change event has happened.

First, let’s look at our FontResizer:


@Inject
FontResizer fontResizer;

private void registerForFontResizing(View itemView) {
    fontResizer.registerResize(itemView);
}

You could inject a FontResizer, register a view, and when a setting is being pulled to resize something within our app, this view will be one of the ones that gets resized. That’s great for most of our use cases of resizing things.

We have certain presenters that want to listen to font change events:


@Inject
public SectionPresenter(@FontBus PublishSubject<Integer> fontBus) {
    fontBus.subscribe(fontSize -> handleFontHasChanged());
}

For example, they can save that event to shared preferences. Whatever that font value was we’re able to have a section presenter where it’s constructor can inject that FontBus. Then we can subscribe to those font changes and handle them our own way.

What’s important to see here is that we can inject just the FontBus into both our FontResizer and into other classes. Now without Dagger, when I used a program before, I would’ve passed the FontResizer to this presenter and then this presenter can, from the FontResizer, get a reference to that listener. And Dagger, keeping everything in modules has taught me more to decompose my objects and to separate all of the object creation. Which let’s us do things like this, of just injecting the things that we need.

Another nice example of something we have in our activity module is we have something called, a SnackBarUtil:


@ScopeActivity
public class SnackbarUtil {
    @Inject Activity activity;
public Snackbar makeSnackbar(String txt, int duration) {
    return Snackbar.make(...);}
}

In some presenter class:
public void onError(Throwable error) {
    snackbarUtil.makeSnackbar(SaveHandler.SAVE_ERROR, SHORT).show();
}

Because our activity module is specific to each activity and we can pass a reference of that activity to our module. We’ll then be able to inject our activity into other classes.

What we can do is create a SnackBarUtil that has an injectable activity, so that, from some other area of our code we can then call makeSnackbar, without passing the activity in every single time.

This is good and bad. This is good because if your presenter’s a few levels down it makes it easier not to have to pass through that activity context everywhere. But this is bad because it makes it much easier to leak your activity context.

Because I’m injecting an activity context here if this SnackBarUtil had listeners going to application scoped classes this activity instance would leak.

Just a word of warning, if you’re going to be injecting an activity somewhere, treat that class that has that injectable activity as a presenter and unbind that view or that activity from that class. That’s the safest way to really work with these injectable activities.

Bundle modules (30:15)

The last thing I want to talk about within our module structure is our bundle module. This is my favorite part of our code base.

Bundle management is hard. For one thing, you need to pass it from one activity to the next. That should be enough, in my mind, but you also have to do frag args. You have to pass it to each fragment. You need to figure out how to safe state. And we were trying to figure out, how can we leverage Dagger to solve some of these problems?

We think we reached a solution. First, we’re going to create a BundleService:


public class BundleService {
    private final Bundle data;

public BundleService(Bundle savedState, Bundle intentExtras) {
    data = new Bundle();

    if (savedState != null) {
        data.putAll(savedState);
    }
    if (intentExtras != null) {
        data.putAll(intentExtras);
    }
}

This BundleService can take two Bundles, a savedState and an intentExtras. We’ll add all of those extras to the same bundle.


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    bundleService = new BundleService(savedInstanceState,
getIntent().getExtras());

//Never have to remember to save instance state again!
protected void onSaveInstanceState(Bundle outState) {
    outState.putAll(bundleService.getAll());

From all of our activities or from our base activity we can then retain this BundleService. What this does is allow our BundleService to have both our saved state and our intent values. We can then, in our on SaveInstanceState pass them all back, so that they’ll all get serialized correctly.

But we could also do a few other things. We can bind that BundleService to our bundle module:


@Provides
@ScopeActivity
public BundleService provideBundleService(Activity context)
{
return ((Bundler) context).getBundleService();
}

``

What we're doing here is we are injecting our activity into a `@Provides` method. From that activity, we're going to cast it to this interface that we have `Bundler`, which will allow us to get a reference of this `BundleService`. Now we'll be able to inject this `BundleService` into views and fragments that are below our activity.

That would be nice, but we actually took it one step further:

```java

Provide Individualized Intent Values
@Provides
@ScopeActivity
@AssetId
public Long provideArticleId(BundleService bundleService) {
    return bundleService.get(ArticleActivity.ASSET_ID);
}

Rather than injecting the whole BundleService, we can inject this BundleService into other @Provides methods and provide from here exact intent values that were passed into our activity. This is something we use within to show articles. We, from our BundleService, take one of our intent values and provide it from Dagger.


@Inject
public CommentPresenter(@AssetId String assetId){
//fetch comments for current article
}

Because we are providing it from Dagger, we can then, into our common presenter, directly into it’s constructor, pass as an injectable value that intent extra. That intent extra, if you do rotation, it’ll be the saved one, not the one that was being passed in.

This all becomes seamless because now your Bundle module is managing all of your intents. Looking at how we used to do this before, we had our article activity. Then we passed the intent value to our article fragment, to our comment fragment, to our comment view, and finally to our comment presenter. There were a lot of areas that we could have made a mistake. Rather, because we’re all on the same component, we’ll inject locally what we need.

Testing (32:54)

The third part of my take will be testing and how we handle testing. That is something that we scoured the internet for and really had to look at how other people are doing it. Luckily, there are a few solutions of how to test with Dagger 2.

Dagger 2 lost the ability to override modules. Before what you were able to do is have a test module. It would override anything from your application module, and you can then use those overridden methods.

We can’t quite do that with Dagger 2, but what we can do is start falling back to just Java Inheritance and overriding things when we’re instantiating them.

Before we get to that. Let me show you the simple version of testing:


@Mock
AppPreferences prefs;
@Before public void setUp() {
    inboxPref = new InboxPreferences(prefs);
}
@Test
public void testGetUserChannelPreferencesEmpty() {
    when(prefs.getPreference(IUSER_CHANNELS,emptySet())) .thenReturn(null);
    assertThat(inboxPref.getUserChannel()).isEmpty();
}

The simplest version of testing is when you’re not actually going to be using Dagger. You have classes. You have an InboxPreferences class. In your source, you may be using Dagger to inject the dependencies, but with a test, you can pass in those dependencies like you did to any other object and verify whether something happens.

Dagger is not needed for this, but what you will need to do is pass in all of the dependencies that your objects are going to need. The majority of our tests are written this way.

It’s similar to true unit testing, where you’re mocking all of your dependencies and testing just that single object, what it’s behavior is. That’s pretty good. But there are times when you’re going to be testing with Dagger. By that I mean, you want to have that real inject-able singleton. Where you want to inject a clock instance or something else that you don’t want to have to mock every method that exists.

To do that we create a base test case that all of our unit tests will extend:


public abstract class BaseTestCase extends TestCase {

protected TestComponent getTestComponent() {
    final ApplicationModule applicationModule = getApplicationModule();
    return Dagger2Helper.buildComponent(
        TestComponent.class,
        applicationModule));}

That returns a test component, and it’ll also have a getter for getting the application module. We have this TestComponent:


@Singleton
@Component(modules = {TestModule.class,ApplicationModule.class,
FlavorModule.class, TypeModule.class, AnalyticsModule.class,
EcommModule.class, PushModule.class})
public interface TestComponent {
    void inject(WebViewUtilTest test);

The test component is very similar to our application component. The only difference is that we are going to be having those inject methods for all of our test classes, rather than for all of our activities and views and services. This is because the tests are what you’re going to be passing to Dagger to register.

Finally, we’ll add another module because this is a separate component. We’re adding a test module that’s going to have test constants and other things that we use with our tests.

Now let’s look at a test:


public class WebViewUtilTest extends BaseTestCase {
    @Inject NetworkStatus networkStatus;
    @Inject WebViewUtil webViewUtil;

protected ApplicationModule getApplicationModule() {
    return new ApplicationModule(application) {
        protected NetworkStatus provideNetworkStatus() {
            return mock(NetworkStatus.class);
        }
    };
}

``

Here we have a `WebViewUtilTest`, and we are injecting two things into it. We want to inject the `NetworkStatus`, and we want to inject the object under test or `webViewUtil`.

What we want to do is have a mock version of our `NetworkStatus` and have the actual `webViewUtil` as it would be provided from Dagger. To do this, we will, remember that `getApplication` module method on our base test case, we override that method. We will make a new instance of our application module. And the same way that you can override methods in say, a listener or something else that you're implementing, you can override any of those `Provide` methods that were in your module.

For anyone wondering, should you have `Provides` methods or not? Yes. You should. Because then you'll be able to override them. Now that we have this overridden `Provides` method, those two members that we have, the `NetworkStatus` is going to be a Mockito Enhanced or a mock network status, while that `WebViewUtil` will be the singleton that every other class is getting.

Let's look at a very simple test:

```java

public class WebViewUtilTest extends BaseTestCase {
@Inject NetworkStatus networkStatus;
@Inject WebViewUtil webViewUtil;



@Test
public void testNoValueOnOffline() throws Exception {
    when(networkStatus.isInternetConnected()).thenReturn(false);
    webViewUtil.getIntentLauncher().subscribe(intent -> {fail("intent was launched");});}

When our mock dot is internet connected as called, we want a return, false. And then we can put our WebViewUtil under test to validate that some subscribe method was not called when it was not supposed to be called. Here we’re using both a combination of injecting using Dagger and injecting with Dagger mocks that are coming from this overridden application module implementation.

There are a few gotchas. You have to have Provides methods. You can’t override something that you don’t have a Provides method for. Unfortunately, we actually started with Dagger 2 without testing. We only had Provides methods for external dependencies. We didn’t have the constructor, but then we moved to having Provides methods for all of our dependencies.

Secondly, this has to be a module that you’re explicitly passing in to Dagger. If you have modules that have empty constructors, Dagger does not make you declare them when you’re declaring your component, but if you’re overriding something you need to declare it because you’re going to be declaring another implementation of this module to passed into your test component.

That was how we do a unit testing that works with both robolectric and base unit testing.

Functional/ Espresso testing (37:34)

Next I wanted to quickly touch on functional and Espresso testing. This is very similar to how we did unit testing. We create a component with overridden providers.

Now, because Espresso testing, we’re really running end to end, these’ll be mostly No-Op of only the global stuff. All of our analytics get turned off, our AB manager gets turned off, and we also do our mock network and disk layers that we’ll be using for our Espresso test.

We’ll then create a functional test runner which will use a custom functional test app. But, otherwise our test will run end to end. Let’s take a look at it:


public class NYTFunctionalTestsApp extends NYTApplication {

ComponentFactory componentFactory(Application context) {
return new ComponentFactory() {
protected DaggerApplicationComponent.Builder componentBuilder(Application context) {
return super.componentBuilder(context)
    .applicationModule(new ApplicationModule(NYTFunctionalTestsApp.this) {
        protected ABManager provideABManager() {
            return new NoOpABManager();
}

We have a NYTFunctionalTestsApp, which’ll extend our regular application class. It’ll override that ComponentFactory. Within this ComponentFactory we’re going to return a component builder that’ll have an application module which will be our implementation of an application module that has some overridden classes, rather than the real one that our source was passing in.

That’s why that ComponentFactory method within our app Component Factory was packaged local. We can override it here and pass in a component, a module which will have mocks.


public class NYTFunctionalTestsRunner extends AndroidJUnitRunner {
    @Override
    public Application newApplication(ClassLoader cl,String className, Context context)
        { return newApplication(NYTFunctionalTestsApp.class, context);
    }
}

Next we have a custom functional test runner. We extend just the regular AndroidJUnitRunner and the only thing that we’re doing, is saying, hey, use our NYTFunctionalTestsApp, rather than using the regular NYT application. Once we have this, we will register this in Gradle.

I don’t have a slide for this, but there’s a single line that you’ll add to your Gradle file that’ll tell it to use your functional test runner, rather than the default one. But, other than that, our tests are going to run the same way that any other Espresso test runs.


@RunWith(AndroidJUnit4.class)
public class MainScreenTests {

    @Test
    public void openMenuAndCheckItems() {
        mainScreen
            .openMenuDialog()
            .assertMenuDialogContainsItemWithText(R.string.dialog_menu_font_resize)
            .assertMenuDialogContainsItemWithText(R.string.action_settings);
}

This is as small as one of our tests is. We do run with AndroidJUnit4, which will use our test runners that we registered in our build.Gradle file. We’ll write an Espresso test that’ll delegate to some screen that will run just like any normal Espresso test.

And that’s it for our post, thank you.

Resources

About the content

This talk was delivered live in July 2016 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

Mike Nakhimovich

Mike’s an Android architect at the New York Times who adores reactive programming and performance tuning. In his free time, he enjoys writing about Android architecture, checking his email compulsively to see if he’s been featured in Android Weekly, and stretching out by the pool with a nice trace file.

4 design patterns for a RESTless mobile integration »

close