もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

Dagger 2.11 でAndroid Supportを使ってDIする

個人で出してるアプリでDagger使ってるところでDaggerが提供しているAndroid向けのモジュールを使うようにしたときのめも。

公式

google.github.io

  • 基本的には公式のドキュメントの通りなのですが、詰まったポイントがあったので、自分の手順としてまとめておきます。

準備

 compile 'com.google.dagger:dagger:2.11'
 compile 'com.google.dagger:dagger-android:2.11'
 compile 'com.google.dagger:dagger-android-support:2.11'
  • ApplicationComponentにAndroidInjectionModule.classを追加します。SupportLibraryを使う場合はAndroidSupportInjectionModule.classになります。
@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ApplicationModule.class
})
public interface ApplicationComponent {
    void inject(App application);
}
  • Applicationクラスを継承してHasActivityInjectorを実装しておきます
public class App extends MultiDexApplication implements HasActivityInjector {

    @Inject
    DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

    @Override
    public void onCreate() {
        // ここは諸々の準備が終わってビルドしてDaggerApplicationComponentが作られた後に書く。
        DaggerApplicationComponent.builder()
            .applicationModule(new ApplicationModule(this))
            .build()
            .inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
    }
}

ActivityにDIする

  • HogeActivityにDIしたい場合

  • AndroidInjectorを継承したSubcomponentを作成します。ここではそのActivityに必要なModuleの定義を行います。

@Subcomponent(modules = {
        ActivityModule.class, // Activity内でDIするインスタンスを提供するモジュール
        HogeActivityViewModelModule.class, // Activity内でDIするインスタンスを提供するモジュール
        FugaFragmentModule.class //  Activityの下にFragmentがいる場合。後述。
})
public interface HogeActivitySubcomponent extends AndroidInjector<HogeActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<HogeActivity> {
        // Moduleがコンストラクタで引数を取る場合、abstractでBuilderを返すメソッドを定義して
        // seedInstanceでModuleのインスタンスを作って生やしたメソッドに渡してあげます。
        public abstract Builder activityModule(ActivityModule module);
            
        @Override
        public void seedInstance(HogeActivity instance) {
            activityModule(new ActivityModule(instance));
        }
    }
}
  • 次にSubcomponentに対応するModuleを作成します。
@Module(subcomponents = HogeActivitySubcomponent.class) // 上で定義したSubcomponentを指定
public abstract class HogeActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(HogeActivity.class) 
    abstract AndroidInjector.Factory<? extends Activity> bindHogeActivityInjectorFactory(HogeActivitySubcomponent.Builder builder);
 }
  • もしSubcomponentが単に依存関係を定義するだけだったら、Subcomponentを作らずにContributesAndroidInjectorアノテーションを使うこともできます。
@Module
public abstract class HogeActivityModule {
    @ContributesAndroidInjector(modules = {
        ActivityModule.class,
        HogeActivityViewModelModule.class,
        FugaFragmentModule.class
    })
    abstract HogeActivity contributeHogeActivityInjector();
}
  • ModuleまでできたらApplicationComponentにModuleを追加します。
@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ApplicationModule.class,   // アプリケーションスコープでDIするModule
        HogeActivityModule.class // 作成したActivityのModule
})
public interface ApplicationComponent {
    void inject(App application);
}
  • 最後にActivityのonCreateでInjectしてあげれば、Activity内のInjectアノテーションがついたフィールドにModuleからDIされます。
public class HogeActivity extends AppCompatActivity {

    @Inject
    HogeActivityViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        ...
    }
}

FragmentにDIする

  • さきほどのHogeActivityにFugaFragmentをぶら下げてFragmentにDIします。

  • HogeActivityにHasSupportFragmentInjectorを実装します。(普通のFragmentを使う場合はHasFragmentInjector)

public class HogeActivity extends AppCompatActivity implements HasSupportFragmentInjector {

    @Inject
    DispatchingAndroidInjector<Fragment> fragmentInjector;
    
    // 略
    
    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return fragmentInjector;
    }
}
  • Activityと同様にSubcomponentとModuleを定義していきます。
@Subcomponent(modules = {
        FugaFragmentViewModelModule.class
})
public interface FugaFragmentSubcomponent extends AndroidInjector<FugaFragment> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<FugaFragment> {
    }
@Module(subcomponents = FugaFragmentSubcomponent.class)
public abstract class FugaFragmentModule {
    @Binds
    @IntoMap
    @FragmentKey(FugaFragment.class)
    abstract AndroidInjector.Factory<? extends Fragment> 
 bindFugaFragmentInjectorFactory(FugaFragmentSubcomponent.Builder builder);
}
  • Activityと同様に ContributesAndroidInjector を使うこともできます。

  • ModuleまでできたらHogeActivityComponentにModuleを追加して依存関係を繋げます。

  • 最後にFragmentにInjectしてあげます。Fragmentの場合はonAttachでDIします。

public class FugaFragment extends Fragment {

    @Inject
    FugaFragmentViewModel viewModel;

    @Override
    public void onAttach(Context context) {
        // SupportライブラリのFragmentの場合はAndroidSupportInjection
        // 普通のFragmentの場合はAndroidInjection
        AndroidSupportInjection.inject(this);
        super.onAttach(context);
        ...
    }
}
  • これでbuildするとDaggerApplicationComponentが生成されるので、上記のAppクラスのonCreateでinjectしてあげればOKです。

追記: DaggerApplication・Activity・Fragmentを使ってもっと楽に書く

  • Activity/Fragmentを作るたびにAndroidInjection.injectをライフサイクルの中で書くのはちょっとだるいのですが、その辺が定義済みのクラスが用意されています。これを使うと以下のように書き換えることができます。
// ApplicationComponent にAndroidInjectorを継承させる
@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ApplicationModule.class,
        HogeActivityModule.class
})
public interface ApplicationComponent extends AndroidInjector<App> {}
// HasActivityInjectorを外してDaggerApplicationを継承するようにする
public class App extends DaggerApplication {

    @Override
    public void onCreate() {
        super.onCreate();
        // 自前でinjectしてたところは消す
    }

    @Override
    protected AndroidInjector<App> applicationInjector() {
        // DaggerApplicationに生えているapplicationInjectorを実装する。
        // Injectは親クラスでやってくれる
        return DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this))
                .build();
    }
}
// HasSupportFragmentInjectorを外してDaggerAppCompatActivityを継承するようにする
public class HogeActivity extends DaggerAppCompatActivity {

       // fragmentInjectorは親クラスが持ってるので消す
//    @Inject
//    DispatchingAndroidInjector<Fragment> fragmentInjector;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Injectは親クラスでやってくれるので消す
//        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        ...
    }
    
//    @Override
//    public AndroidInjector<Fragment> supportFragmentInjector() {
//        return fragmentInjector;
//    }
}
// DaggerFragmentを継承するようにする
public class FugaFragment extends DaggerFragment {

    @Override
    public void onAttach(Context context) {
        // Injectは親クラスでやってくれるので消す
//        AndroidSupportInjection.inject(this);
        super.onAttach(context);
        ...
    }
}

おわり

  • 前は結構頑張ってApplicaiton -> Activity -> Fragmentの依存関係を繋げてたのですが、その辺は楽に書けるようになったのかなーと思います。あとはもうちょい生成されたコードを眺めてどんな感じになったのか把握した方がよいですね。
  • 実際に自分のアプリで書いたのはこちら。テストちゃんと書けてないのでDIの恩恵を完全に享受してるとはいえないのですが、ちょいちょい追加していきます。

https://github.com/rei-m/android_hyakuninisshu/pull/86

  • 余談ですが、新しいライブラリとか設計を試すときに簡単なTODOアプリとかだといまいち使用感がつかめないので、実際にリリースしたそこそこのアプリで試すようにしてます。次はArchitecture Components使ってオレオレViewModelを置き換える予定。仕事でAndroidから離れてだいぶキャッチアップが遅れてるのでほどほどに追いかけていく気持ち。

Androidアプリ開発 74のアンチパターン

Androidアプリ開発 74のアンチパターン