Tech notes for android-dagger

undefined

Posts

  1. Android: Dagger 2 and fragments
  2. Java: Dagger 2 Multibindings
  3. Android: Dagger 2 basic tutorial
  4. Java and Android: Dagger 2 introduction (including Component.Builder)

Android: Dagger 2 and fragments

We've already got Dagger 2 setup in the Activity:

Let's say you now want to alter MainActivity to start a new Fragment:

public class MainActivity extends AppCompatActivity {
    @Inject
    Drink drink;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        drink.taste();

        getSupportFragmentManager().beginTransaction()
                .add(R.id.container, new NewFragment(), "TAG")
                .commit();
    }
}

And you want the Fragment to resolve its dependencies from Dagger too:

public class NewFragment extends Fragment {

    @Inject
    Drink anotherDrink;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        AndroidSupportInjection.inject(this);
        return LayoutInflater.from(getContext()).inflate(R.layout.frag, null);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        androidDrink.taste();
    }
}

You have a problem since Dagger doesn't know about this Fragment. You must update your MainActivityModule to include it to your android injector:

@Module(includes = MainActivityModule.FragmentsModule.class)
public class MainActivityModule {

    @Provides
    Ingrediants provideIngredients(){
        return new Ingrediants() {
            @Override
            public List<String> getAll() {
                return Arrays.asList("GIN", "Orange Juice", "Coke");
            }
        };
    }

    @Module
    public static abstract class FragmentsModule {

        @ContributesAndroidInjector
        abstract NewFragment bindNewFragment();
    }
}

(Note the includes part and the new module that's telling the Android Injector about our new Fragment`)

But we must do more. We must alter MainActivity to give a fragment injector:

public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {

    @Inject
    public DispatchingAndroidInjector<Fragment> dispatchingAnroidInjector;

    @Inject
    Drink drink;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        drink.taste();

        getSupportFragmentManager().beginTransaction()
                .add(R.id.container, new NewFragment(), "TAG")
                .commit();
    }

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return dispatchingAnroidInjector;
    }
}

And now everything works.

android android-dagger

Java: Dagger 2 Multibindings

This follows on from our previous dagger tutorial:

We can provide dependencies in our Module that add to a map of dependencies:

    @Provides
    @Named("bands")
    @IntoMap
    @StringKey("favourite")
    public String providesFavouriteBand() {
        return "Half Man Half Biscuit";
    }

    @Provides
    @Named("bands")
    @IntoMap
    @StringKey("secondFavourite")
    public String providesSecondFavouriteBand() {
        return "The Beatles";
    }

We're adding two strings, with the keys favourite and secondFavourite, to a map of strings that is named bands.

Later on, we can access this map when we Inject dependencies:

    @Inject @Named("bands")
    Map<String, String> bands;

We can add to this map from different modules. And we can also add to a set too.

java java-dagger android-dagger android

Android: Dagger 2 basic tutorial

Say we have this dependency:

public interface Ingrediants {
    public List<String> getAll();
}

Let's make a module that provides the dependencies in our MainActivity:

@Module
public class MainActivityModule {

    @Provides
    Ingrediants provideIngredients(){
        return new Ingrediants() {
            @Override
            public List<String> getAll() {
                return Arrays.asList("GIN", "Orange Juice", "Coke");
            }
        };
    }
}

And let's include that when Android's injector is used in MainActivity:

@Module
public abstract class ActivityBuilder {

    @ContributesAndroidInjector(modules = MainActivityModule.class)
    abstract MainActivity bindMainActivity();

}

And let's make a component for that:

@Component(modules = {AndroidInjectionModule.class, ActivityBuilder.class})
public interface BarComponent {

  void inject(MyApplication myApplication);
}

And let's inject that into our Application:

public class MyApplication extends Application implements HasActivityInjector  {

    @Inject
    DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return activityDispatchingAndroidInjector;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerBarComponent.builder()
        .build()
        .inject(this);
    }
}

We can now use our MainActivty to @Inject a dependency:

public class MainActivity extends AppCompatActivity {

    @Inject
    Drink drink;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        drink.taste();
    }

}

And what is Drink? We'll it's a class that gets its dependency from our DrinkModule by the Component:

public class Drink {

    @Inject
    public Drink() {}

    @Inject
    Ingrediants ingrediants;

    public void taste() {
        Log.d("HI", "Our drink contains: " + ingrediants.getAll());
    }
}
android android-dagger java java-dagger

Java and Android: Dagger 2 introduction (including Component.Builder)

Let's work through an example of Dagger 2. I'm doing it this in an Android project but for simplicity we'll be treating the project like a normal Java project for the most part: there will be no Activity or Fragment specific code. First this this in our gradle.build file:

annotationProcessor 'com.google.dagger:dagger-compiler:2.20'
implementation 'com.google.dagger:dagger:2.20'

We have a Drink class. This has a taste method. The taste method prints out the ingredients in the drink. The Ingredients are held as a instance method in the class. Normally we would pass the ingredients in a constructor parameters. But instead we use the @Inject annotation on a member field. Later Dagger will inject an implmentation of Ingredients here. (We must also annotate Drink with @Inject to keep Dagger happy when creating Drink.)

public class Drink {

    public static interface Ingredients {
        List<String> getAll();
    }

    @Inject
    public Drink() {}

    @Inject
    Ingrediants ingredients;

    public void taste() {
        System.out.println("Our drink contains: " + ingredients.getAll());
    }
}

We now use a Dagger Module to define our dependencies. In our case, our dependency is Ingredient. We make a plain class and annotate it with @Module. In that class we have a method that's annotated with @Provides and that method returns our Ingredients. The name of method could actually be anything - the only thing that matters is the return type.

(Note: we are explicity detailing our ingredients implementation in the module. Later we'll learn how to do this in a more dynamic way)

We must have a Component class. This let's us begin our dependency injection. You need to create an interface. And this is annotated with @Component. And the parameters to that annotation is our Module that we created above. Inside our component we have an inject method. The paramter is the type of class which we will use our dependency injection.

@Component(modules = DrinkModule.class)
interface BarComponent {

  void inject(MainActivity activity);

  @Module
  public class DrinkModule {

    @Provides public Ingredients providesIngredients() {
      return new Ingrediants() {
          @Override
          public List<String> getAll() {
              return Arrays.asList("GIN", "Orange Juice", "Coke");
          }
      };
    }
  }
}

We've made our module with the dependencies that lives inside our component. So we can now use the component. Dagger automatically creates a class DaggerBarComponent that's based on our BarComponent class. We then call the builder() method that returns a builer. We're not currently doing anything special so we call 'build()'. And we've built our component, we call the inject method -- passing in our current class.

At the point, any instance methods in the current class that have the @Inject annotation will be initiated by Dagger. And importantly its dependency's will be resolved. For instance, we have @Inject Drink drink and Dagger will create Drink, look at all the instance methods, for example, @Inject Ingredients ingredient, and resolve those using Dagger's component and module.

@Inject Drink drink;
...
DaggerBarComponent
  .builder()
  .inject(this);
...
drink.taste()

We previously hard-coded our Ingredients "GIN", "Orange Juice" and "Coke" in our module. We can instead pass those into our component. We do this using a dagger factory. We do this using an interface annnotated with @Component.Builder. We have a method that returns that interface. It also has an argument of, in our case, List<String> ingredeients. We must have an extra method called build that returns the component. The second thing we do is alter the providesIngredients method in our module. This method now takes in the List<String> ingredients and uses it.

@Component(modules = { BarComponent.DrinkModule.class })
interface BarComponent {

    @Component.Builder
    interface Builder {
        @BindsInstance Builder ingredients(List<String> ingredients);
        BarComponent build();
    }

    void inject(MainActivity activity);

    @Module
    class DrinkModule {

        @Provides
        public Drink.Ingredients providesIngredients(final List<String> ingredients) {
            return new Drink.Ingredients() {
                @Override
                public List<String> getAll() {
                    return ingredients;
                }
            };
        }

    }
}

Now when we build our module we can pass in the List<String> ingredients:

DaggerBarComponent
  .builder()
  .ingredients(Arrays.asList("Vodka", "Rum", "Coke"))
  .build()
  .inject(this);

You can also then use Android's build types to swap in or out the depedencies depending on the build type:

List<String> ingredients = null;
if(BuildConfig.BUILD_TYPE.equals("teeTotal")) {
    ingredients = Arrays.asList("Orange", "Soda", "Pineapple");
} else if(BuildConfig.BUILD_TYPE.equals("alcoholic")) {
    ingredients = Arrays.asList("Vodka", "Rum", "Coke");
}

DaggerBarComponent
  .builder()
  .ingredients(ingredients)
  .build()
  .inject(this);

drink.taste();

By specifying two build types in your app/build.gradle file:

...
android {
  ...
    buildTypes {
      ..
      teeTotal {
        initWith debug
      }
      alcoholic {
        initWith debug
      }
    }
  ...
}
...

And then using adb to either ./gradlew installAlcoholic or ./gradlew installTeeTotal. And then you can see the different output in logcat.

java java-dagger android android-dagger

Page 1 of 1
Click me