概要
Dagger2では2.10からdagger.androidが入り、2.11でそれがさらに使いやすくなりました。
ここでは生成されたコードを見て、何をしているのかの説明を書いておきます。
ついでにKotlinでのandroidサポートを使った導入の仕方も記述しておきます。
導入
Gradle
buildscript {
ext.dagger_version = "2.12"
・・・
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
・・・
dependencies {
・・・
kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
implementation "com.google.dagger:dagger:$dagger_version"
implementation "com.google.dagger:dagger-android:$dagger_version"
・・・
Module
以下が、Android Supportで追加されたModuleの書き方になります。
@Module
abstract class AndroidModule {
@ContributesAndroidInjector
abstract fun contributeMainActivity(): MainActivity
}
@ContributesAndroidInjector
が2.11からできたAPIです。
これにより、2.10で自分で記述していたコードを生成してくれます。
自動生成されたソースは後ほど説明します。
Component
@Singleton
@Component(modules = arrayOf(AndroidInjectionModule::class, AndroidModule::class))
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: App): Builder
fun build(): AppComponent
}
fun inject(app: App)
}
AndroidInjectionModule::class
がミソです。2.10から追加されました。
@BindsInstance
は、2.9から追加されました。自分でコンポーネントを構築するときに、これを指定したメソッドを呼び出すことで、引数をコンポーネントにInjectできるようになります。
どういうことかというと、@BindsInstance fun application(application: App): Builder
を書かないと例えば、以下のようにContextをInjectしようとしてもコンパイルエラーになります。
@Module
class AppModule {
@Provides
fun provideContext(application: App) = application.applicationContext
}
class MainDao @Inject constructor() {
@Inject
lateinit var context: Context
@BindsInstance fun application(application: App): Builder
を記述し、Applicationクラスで設定することで、ModuleでDIされた状態で利用できるようになるということです。
Application
AppComponentから自動生成されたクラスDaggerAppComponent
を使ってコンポーネント構築します。
class App : Application(), HasActivityInjector {
@Inject lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector() = dispatchingAndroidInjector
override fun onCreate() {
super.onCreate()
val objectGraph = DaggerAppComponent
.builder()
.application(this)
.build()
objectGraph.inject(this)
}
}
AppComponentで指定した@BindsInstance
のメソッドを使って、AppクラスをModuleで利用できるようにしています。(application(this)
の部分です。)
また、dagger.android.HasActivityInjector
インターフェイスが重要です。これは次のActivityのソースとともに説明します。
Activity
Androidサポートのおかげでdagger.android.AndroidInjection.inject(this)
だけを書けば、DIされるようになりました。
lass MainActivity : AppCompatActivity() {
@Inject
lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
・・・
ついでにその他のクラスも載せておきます。
class MainViewModel @Inject constructor() {
@Inject
lateinit var domain: MainDomain
・・・
dagger.android.AndroidInjection.inject(this)
が具体的に何をしているかというと、Applicationクラス(ここの場合はApp.ktクラス)でdagger.android.HasActivityInjector
が実装されていれば、dagger.android.DispatchingAndroidInjector
を使って引数のActivity(ここではMainActivity)をComponet(これは依存を解決するためのクラス。別名ObjectGraph。)に入れています。(@ContributesAndroidInjector
を書いていないとここでassertエラーになります。)
そして、Activityで@Inject
しているフィールドにInjectしてくれます。
もう少し具体的にいうとdagger.android.DispatchingAndroidInjector
が、MainActivityをキーにModuleで定義した@ContributesAndroidInjector
によって自動生成されたコードMainActivitySubcomponentBuilder
、さらに、Builderがインスタンスする同じく自動生成されたクラスMainActivitySubcomponentImpl
(dagger.android.AndroidInjector
の実装クラス)を生成し、このクラスがそれぞれのクラスをInjectをします。
説明した対応のコードの一部が以下です。
ActivityのAndroidInjection.inject(this)が呼ばれた先に以下のmaybeInjectメソッドがあります。
public boolean maybeInject(T instance) { // ① instance=MainActivity
Provider<AndroidInjector.Factory<? extends T>> factoryProvider =
injectorFactories.get(instance.getClass());
if (factoryProvider == null) {
return false;
}
@SuppressWarnings("unchecked")
AndroidInjector.Factory<T> factory = (AndroidInjector.Factory<T>) factoryProvider.get(); // ② factory=MainActivitySubcomponentBuilder
try {
AndroidInjector<T> injector =
checkNotNull(
factory.create(instance), // ③ new MainActivitySubcomponentImpl()
"%s.create(I) should not return null.",
factory.getClass().getCanonicalName());
injector.inject(instance); // ④
①の行の引数のT instance
は、MainActivityです。
②の行のAndroidInjector.Factory<T> factory
には、MainActivitySubcomponentBuilderが入ります。
③の行のfactory.create(instance)
でMainActivitySubcomponentImplがインスタンスされます。
④の行のinjector.inject(instance);
では、自動生成されたDaggerAppComponent
が保持するMembersInjector<MainActivity>
(これも自動生成されたMainActivity_MembersInjector
が実装クラス)を使って、MainActivityにDIすべきクラスを設定しています。
自動生成されるコード
@ContributesAndroidInjector
によって生成されたコードは以下です。
バージョン2.10では、これを自分で記述する必要がありました。
@Module(subcomponents = AndroidModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class AndroidModule_ContributeMainActivity {
private AndroidModule_ContributeMainActivity() {}
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
MainActivitySubcomponent.Builder builder);
@Subcomponent
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {} // implemenets AndroidInjector.Factory
}
}
@Binds
は、超簡単に説明すると以下の定型コードを書かないで済むようにするアノテーションです。
@Provides
AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(){
return new MainActivitySubcomponent.Builder()
}
実際は、MainActivitySubcomponent.Builder
もabstractなので必要なメソッドを実装しないといけませんが、そういうことです。
@IntoMap
と@ActivityKey
はセットで利用するものです。ここで指定したものが、dagger.android.DispatchingAndroidInjector
で保持されるMapになります。(このMapが最終的に重要になります)
@ActivityKey
で指定した値(この場合は、MainActivity.class)がキーになります。値は、メソッドの戻り値(この場合は、MainActivitySubcomponent.Builderになります。ちなみにこのBuilderはAndroidInjector.Factoryの実装になります。
以下がAppComponentから自動生成された対象の部分です。
・・・
private Provider<AndroidInjector.Factory<? extends Activity>> bindAndroidInjectorFactoryProvider;
・・・
@Override
public AppComponent build() {
if (application == null) {
throw new IllegalStateException(App.class.getCanonicalName() + " must be set");
}
return new DaggerAppComponent(this);
}
・・・
private DaggerAppComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.mainActivitySubcomponentBuilderProvider =
new dagger.internal.Factory<
AndroidModule_ContributeMainActivity.MainActivitySubcomponent.Builder>() {
@Override
public AndroidModule_ContributeMainActivity.MainActivitySubcomponent.Builder get() {
return new MainActivitySubcomponentBuilder();
}
}; // 参
this.bindAndroidInjectorFactoryProvider = (Provider) mainActivitySubcomponentBuilderProvider; // 弐
this.mapOfClassOfAndProviderOfFactoryOfProvider =
MapProviderFactory
.<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>builder(1)
.put(MainActivity.class, bindAndroidInjectorFactoryProvider)
.build(); // 壱 bindAndroidInjectorFactoryProvider=dagger.internal.Factory() implements Providerの匿名クラス。上記弐,参、参照
this.dispatchingAndroidInjectorProvider =
DispatchingAndroidInjector_Factory.create(mapOfClassOfAndProviderOfFactoryOfProvider);
this.appMembersInjector = App_MembersInjector.create(dispatchingAndroidInjectorProvider);
}
・・・
重要と説明した「MainActivity.classがキーで、値がMainActivitySubcomponent.Builderのmap」部分を具体的に見ていきます。
壱のmapの値に指定しているbindAndroidInjectorFactoryProviderは
弐を見るとmainActivitySubcomponentBuilderProviderになります。
さらにmainActivitySubcomponentBuilderProviderは、参でdagger.internal.Factory
の匿名クラスになっています。
dagger.internal.Factory
はjavax.inject.Provider
を継承していて、getで返すインスタンスは、MainActivitySubcomponentBuilder
クラスです。
MainActivitySubcomponentBuilder
の親クラスがMainActivitySubcomponent.Builder
になります。
ちなみにDaggerでは、javax.inject.Provider#get()
で取得できるクラスがInject可能であれば、ProviderをDI対象できます。
つまり、以下の定義は不要ということです。
@Provides
fun provideMainActivitySubcomponentBuilderProvider() = object: Provider<MainActivitySubcomponentBuilder> {
override fun get(): MainActivitySubcomponentBuilder {
return MainActivitySubcomponentBuilder()
}
}
なので、キーがMainActivityで、値がProvider<AndroidInjector.Factory<? extends Activity>>
が成立するのです。
続きです。DaggerAppComponent.javaの続きです。
private final class MainActivitySubcomponentBuilder
extends AndroidModule_ContributeMainActivity.MainActivitySubcomponent.Builder {
private MainActivity seedInstance;
@Override
public AndroidModule_ContributeMainActivity.MainActivitySubcomponent build() {
if (seedInstance == null) {
throw new IllegalStateException(MainActivity.class.getCanonicalName() + " must be set");
}
return new MainActivitySubcomponentImpl(this); // 「ア」
}
@Override
public void seedInstance(MainActivity arg0) {
this.seedInstance = Preconditions.checkNotNull(arg0);
}
}
private final class MainActivitySubcomponentImpl
implements AndroidModule_ContributeMainActivity.MainActivitySubcomponent {
private MembersInjector<MainViewModel> mainViewModelMembersInjector;
private Provider<MainViewModel> mainViewModelProvider;
private MembersInjector<MainActivity> mainActivityMembersInjector;
private MainActivitySubcomponentImpl(MainActivitySubcomponentBuilder builder) {
assert builder != null;
initialize(builder);
}
@SuppressWarnings("unchecked")
private void initialize(final MainActivitySubcomponentBuilder builder) { // 「イ」
this.mainViewModelMembersInjector =
MainViewModel_MembersInjector.create(MainDomain_Factory.create());
this.mainViewModelProvider = MainViewModel_Factory.create(mainViewModelMembersInjector);
this.mainActivityMembersInjector = MainActivity_MembersInjector.create(mainViewModelProvider);
}
@Override
public void inject(MainActivity arg0) {
mainActivityMembersInjector.injectMembers(arg0); // 「ウ」
}
}
MainActivitySubcomponent.Builderは「ア」でMainActivitySubcomponentImplをインスタンスしています。
これを実際にインスタンスしているのは、dagger.android.DispatchingAndroidInjector.java
で説明した③の部分factory.create(instance)
で、これは結局は、Activity#onCreate()
の最初で呼ぶAndroidInjection.inject(this)
から呼ばれることになります。
で、MainActivitySubcomponentImpl
がインスタンスされると「イ」のメソッドで@Inject
でDIしたいクラスのProviderを保持している~Injectorクラスを自動生成されたFactoryクラスでインスタンスして「ウ」で、渡ってきたActivityの@Inject
を記述したフィールドに設定しています。
「ウ」で利用されているMainActivity_MembersInjectorは以下です。
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
private final Provider<MainViewModel> viewModelProvider;
public MainActivity_MembersInjector(Provider<MainViewModel> viewModelProvider) {
assert viewModelProvider != null;
this.viewModelProvider = viewModelProvider;
}
public static MembersInjector<MainActivity> create(Provider<MainViewModel> viewModelProvider) {
return new MainActivity_MembersInjector(viewModelProvider);
}
@Override
public void injectMembers(MainActivity instance) {
if (instance == null) {
throw new NullPointerException("Cannot inject members into a null reference");
}
instance.viewModel = viewModelProvider.get();
}
}
上記、viewModelProviderのインスタンスは以下です。
public final class MainViewModel_Factory implements Factory<MainViewModel> {
private final MembersInjector<MainViewModel> mainViewModelMembersInjector;
public MainViewModel_Factory(MembersInjector<MainViewModel> mainViewModelMembersInjector) {
assert mainViewModelMembersInjector != null;
this.mainViewModelMembersInjector = mainViewModelMembersInjector;
}
@Override
public MainViewModel get() {
return MembersInjectors.injectMembers(mainViewModelMembersInjector, new MainViewModel());
}
public static Factory<MainViewModel> create(
MembersInjector<MainViewModel> mainViewModelMembersInjector) {
return new MainViewModel_Factory(mainViewModelMembersInjector);
}
}
@Singleton
次のようにすると@Singleton
になります。
@Module
class AppModule {
@Singleton
@Provides
fun provideMainDomain() = MainDomain()
}
@Singleton
class MainDomain @Inject constructor()
@Singleton
@Component(modules = arrayOf(AndroidInjectionModule::class, AndroidModule::class, AppModule::class))
interface AppComponent
・・・
使う側のコードはいつも通りです。
class MainViewModel @Inject constructor() {
@Inject
lateinit var domain: MainDomain
・・・
こうするとどうなるかを見てみましょう
まずは、付けなかった場合です。
・・・
private final class MainActivitySubcomponentImpl
implements AndroidModule_ContributeMainActivity.MainActivitySubcomponent {
・・・
private void initialize(final MainActivitySubcomponentBuilder builder) {
this.mainViewModelMembersInjector =
MainViewModel_MembersInjector.create(MainDomain_Factory.create()); // ここが違う
・・・
MainDomainのProviderクラスが以下です。このクラスは@Singleton
を付けた場合も存在します。
public final class MainDomain_Factory implements Factory<MainDomain> {
private static final MainDomain_Factory INSTANCE = new MainDomain_Factory();
@Override
public MainDomain get() {
return new MainDomain();
}
public static Factory<MainDomain> create() {
return INSTANCE;
}
}
MainViewModel_MembersInjector#create
に渡す引数が、MainDomain_Factoryで、それはget()メソッドでMainDomainのインスタンス化して返します。Factory=Providerで、getが呼ばれるたびにインスタンスされます。
次は、@Singleton
を付けた場合です。
MainViewModel_MembersInjector#create
に渡すProviderが違います。
this.mainViewModelMembersInjector =
MainViewModel_MembersInjector.create(DaggerAppComponent.this.provideMainDomainProvider); // ここが違う
public static AppComponent.Builder builder() {
return new Builder();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
・・・
this.provideMainDomainProvider =
DoubleCheck.provider(AppModule_ProvideMainDomainFactory.create(builder.appModule));
このDoubleCheck.provider()
メソッドでSingleton対象のMainDomainをinstance
フィールドで保持しておき、MainDomainProviderが呼ばれた時にそれを返すという仕組みです。
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
private DoubleCheck(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get(); // provider=AppModule_ProvideMainDomainFactory。ここでMainDomainがインスタンスされる
Object currentInstance = instance;
if (currentInstance != UNINITIALIZED && currentInstance != result) {
throw new IllegalStateException("Scoped provider was invoked recursively returning "
+ "different results: " + currentInstance + " & " + result + ". This is likely "
+ "due to a circular dependency.");
}
instance = result; // ここでMainDomainがキャッシャされるので呼ばれるたびに同じインスタンスが返る
provider = null;
}
}
}
return (T) result;
}
public static <T> Provider<T> provider(Provider<T> delegate) {
checkNotNull(delegate);
if (delegate instanceof DoubleCheck) {
return delegate;
}
return new DoubleCheck<T>(delegate); // App.ktから呼ばれる
}
ちなみにキャッシュしているDoubleCheckをインスタンスするのはApp.ktから呼ばれます。
activityなどは、AndroidInjection.inject(this)
の内部でprovideMainDomainProviderを使ってキャッシュからインスタンスを取得することになります。