はじめに
Android Architecture Blueprintsのtodo-mvp-daggerで使用されているDagger2のAndroid拡張がよくわからないという方向けに、Android拡張がどのように動作するか解説する記事です。Dagger2についてある程度知っていることが前提となります。
前提
- Dagger 2.16
概要
Android拡張を使わない場合、ActivityやFragmentにMember Injectionするためには、ライフサイクルメソッド(それぞれ、onCreate, onAttach)で以下のような定型的なコードを書く必要があります。(以下のコードはDagger2 Users Guide/Androidから引用したものです。)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
}
しかし、Android拡張を使って以下の手順を行うと、上記の定型的なコードを書く必要がなくなります。
-
ApplicationComponent
の@Component.modules
にAndroidSupportInjectionModule
を追加する。 -
ActivityBindingModule
を作成し、ApplicationComponent
の@Component.modules
に設定する。 -
ActivityBindingModule
では、Member Injectionの対象となるActivityを戻り値とした引数なしメソッドを宣言する。そのメソッドには、@ContributeAndroidInjector
を付与する。これにより、ActivityにMember InjectionするためのSubComponent(=AndroidInjector<対象のActivity>
)とAndroidInjector<対象のActivity>.Factory
が自動生成される。このAndroidInjector<対象のActivity>.Factory
は1.のAndroidSupportInjectionModule
(厳密にはそれが保持しているAndroidInjectionModule
)のMap<Class, AndroidInjector.Factory>
にActivityのクラス名をキーとしてMultiBindingされる。 - Activityで
DaggerAppCompatActivity
を継承する。
これにより、Activityは以下のように振る舞います。
- Activityの
onCreate
でベースクラスDaggerAppCompatActivity
のAndroidInjection.inject(this)
が実行される。 -
AndroidInjection.inject(this)
で、Applicationが保持しているDispatchingAndroidInjector
のinject(this)
が実行される。 -
DispatchingAndroidInjector.inject
では、AndroidInjectionModule
によりDispatchingAndroidInjector
にInjectされたMap<Class, AndroidInjector.Factory>
から、ActivityのAndroidInjector.Factory
を取得する。 - 取得した
AndroidInjector.Factory
からActivityのAndroidInjector
を生成し、AndroidInjector.inject
を呼び出し、ActivityにMember Injectionする。
結果として、定型的なコードは不要となり、Activityは「Injectorは何であるか」、「InjectorがどのようにInjectするか」を知る必要がなくなります。
詳細
ここからは、Android Architecture Blueprintsのコードを確認しながら、解説していきます。(Android Architecture Blueprintsをgit clone
し、todo-mvp-daggerのbranchに切り替えるとtodo-mvp-daggerのコードを確認できます。)
Task一覧画面(tasksパッケージ)に関わる赤枠の部分を中心に見ていきます。
AppComponent
の@Component.modules
にAndroidSupportInjectionModule
を追加する。
AppComponent
の@Component.modules
にAndroidSupportInjectionModule
を追加します。
package com.example.android.architecture.blueprints.todoapp.di;
// 省略
import dagger.android.support.AndroidSupportInjectionModule;
@Singleton
@Component(modules = {/* 省略 */
AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<ToDoApplication> {
// 省略
}
AndroidSupportInjectionModule
の中身を確認すると次のようになっています。
package dagger.android.support;
import android.support.v4.app.Fragment;
import dagger.android.AndroidInjectionModule;
// 省略
@Beta
@Module(includes = AndroidInjectionModule.class)
public abstract class AndroidSupportInjectionModule {
@Multibinds
abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
supportFragmentInjectorFactories();
// 省略
}
@Module.includesしているAndroidInjectionModule
は次のようになっています。
package dagger.android;
// 省略
@Beta
@Module
public abstract class AndroidInjectionModule {
@Multibinds
abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
activityInjectorFactories();
@Multibinds
abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
fragmentInjectorFactories();
// 省略
}
AndroidInjectionSupportModule
とAndroidInjectionModule
を見ると、support.Fragment
やapp.Fragment
、app.Activity
のそれぞれについて、Map<Class, AndroidInjector.Factory>
の@Multibinds
の宣言をしています。
後述しますが、この宣言により、TodoApplication
が保持するDispatchingAndroidInjector
のMap<Class, AndroidInjector.Factory>
型フィールドに、後述のActivityBindingModule
で@ContributeAndroidInjector
により生成したAndroidInjector.Factory<TasksActivity>
がInjectされます。そして、Activity.onCreate
で実行されるDispatchingAndroidInjector.inject
で、injectされたMap<Class, AndroidInjector.Factory>
からTasksActivity
に対応するAndroidInjector.Factory
を取得し、AndroidInjector
を生成し、AndroidInjector.inject
でTasksActivity
に依存をInjectします。
ActivityBindingModule
を作成し、ApplicationComponent
の@Component.modules
に設定する。
ActivityBindingModule
を作成します(名前は何でも良いです)。今は中身は空です。
次の節で、ActivityにMember Injectionするための処理を記述します。
package com.example.android.architecture.blueprints.todoapp.di;
// 省略
@Module
public abstract class ActivityBindingModule {
// 省略(中身は後述します。)
}
ApplicationComponent
の@Component.modules
に設定します。
package com.example.android.architecture.blueprints.todoapp.di;
// 省略
import dagger.android.support.AndroidSupportInjectionModule;
@Singleton
@Component(modules = {/* 省略 */
ActivityBindingModule.class,
AndroidSupportInjectionModule.class})
public interface AppComponent extends AndroidInjector<ToDoApplication> {
// 省略
}
ActivityBindingModule
で、Member Injectionの対象となるActivityを戻り値とした引数なしメソッドを宣言する。メソッドには@ContributeAndroidInjector
を付与する。
ActivityBindingModule
でTasksActivity
を戻り値としたメソッドを宣言し、@ContributeAndroidInjector
をつけます。
package com.example.android.architecture.blueprints.todoapp.di;
// 省略
@Module
public abstract class ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector(modules = TasksModule.class)
abstract TasksActivity tasksActivity();
// 省略
}
これにより、以下のコードが自動生成されます。
-
ActivityBindingModule_TasksActivity.TasksActivitySubcomponent
: TasksActivityに依存をInjectするためのSubComponent(=AndroidInjector<TasksActivity>
)です。 -
ActivityBindingModule_TasksActivity
: TasksActivitySubComponent.Builderを、AndroidSupportInjectionModule
で宣言しているMap<Class, AndroidInjector.Factory>
にMultiBindingするModuleです。(BuilderはAndroidInjector.Factoryのサブインターフェイスです。)
package com.example.android.architecture.blueprints.todoapp.di;
// 省略
@Module(subcomponents = ActivityBindingModule_TasksActivity.TasksActivitySubcomponent.class)
public abstract class ActivityBindingModule_TasksActivity {
private ActivityBindingModule_TasksActivity() {}
@Binds
@IntoMap
@ActivityKey(TasksActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
TasksActivitySubcomponent.Builder builder);
@Subcomponent(modules = TasksModule.class)
@ActivityScoped
public interface TasksActivitySubcomponent extends AndroidInjector<TasksActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<TasksActivity> {}
}
}
これらの自動生成されたコードにより、AndroidInjectionModule
の@MultiBinds
で宣言しているMap<Class, AndroidInjector.Factory>
に、TasksActivity.class
をキーとしてAndroidInjector.Builder<TasksActivity>
(BuilderはAndroidInjector.Factoryのサブインターフェイス)がBindsされます。
補足として、上記のActivityBindingModule.tasksActivity()
の@ActivityScoped
は生成されるSubComponentのScopeになります。また、modules=TasksModule.java
はSubComponentにinstallするModuleです。
TasksModuleでは、ActivityBindingModuleでTasksActivityについて行ったのと同様に、TasksFragmentを戻り値とする引数なしメソッドを宣言し、@ContributeAndroidInjector
を付与します。
こうすることで、TasksActivityの時と同様に、AndroidInjectionModule
の@MultiBinds
で宣言しているMap<Class, AndroidInjector.Factory>
に、TasksFragment.class
をキーとしてAndroidInjector.Builder<TasksFragment>
がBindsされます。
package com.example.android.architecture.blueprints.todoapp.tasks;
// 省略
@Module
public abstract class TasksModule {
@FragmentScoped
@ContributesAndroidInjector
abstract TasksFragment tasksFragment();
@ActivityScoped
@Binds abstract TasksContract.Presenter taskPresenter(TasksPresenter presenter);
}
ActivityでDaggerAppCompatActivity
を継承する。
ActivityでDaggerAppCompatActivity
を継承します。
package com.example.android.architecture.blueprints.todoapp.tasks;
// 省略
public class TasksActivity extends DaggerAppCompatActivity { /*省略*/ }
DaggerAppCompatActivityを継承することにより、次の通り、onCreate
でAndroidInjection.inject(this)
が実行されるようになります。
package dagger.android.support;
// 省略
@Beta
public abstract class DaggerAppCompatActivity extends AppCompatActivity
implements HasFragmentInjector, HasSupportFragmentInjector {
// 省略
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
// 省略
}
AndroidInjection.inject(this)
を実行すると、TodoApplication
(DaggerApplication
を継承)を取得します。そして、TodoApplication
で保持している、activityInjector
(DispatchingAndroidInjector<Activity>
型)を取得します。そして、activityInjector
のinject(activity)
が実行されます。
package dagger.android;
// 省略
@Beta
public abstract class DaggerApplication extends Application
implements HasActivityInjector /* 省略 */ {
@Inject DispatchingAndroidInjector<Activity> activityInjector;
}
activityInjector
(DispatchingAndroidInjector<Activity>
型)のinject(activity)
では、AndroidInjectionModule
の@Multibinds
の宣言により、InjectされたinjectorFactories
から、TasksActivity
に対応する、AndroidInjector.Factoryを取得し、AndroidInjectorを生成し、TasksActivity
に依存をInjectします。
package dagger.android;
@Beta
public final class DispatchingAndroidInjector<T> implements AndroidInjector<T> {
// 省略
private final Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>>
injectorFactories;
@Inject
DispatchingAndroidInjector(
Map<Class<? extends T>, Provider<AndroidInjector.Factory<? extends T>>> injectorFactories) {
this.injectorFactories = injectorFactories;
}
// 省略
これで、TasksActivity
に依存がInjectされるまでの流れは終了です。
最後に
この記事ではAndroid拡張がどのような仕組みになっているかをざっくり解説しました。最初にAndroid拡張のコードを見たときは、何をしているかわからなかったのですが、自動生成されたコードも含めて一つ一つ見ていくと、Android拡張はDaggerの基本的な仕組み(Multibinding, SubComponent)を使って実現されていることがわかりました。