Daggerがやってくれること (SubComponent編)
前回 Daggerがやってくれること という記事を書いた。
今回はその続きで、SubComponentを利用した時にDaggerがどのようなコードを生成するか見ていく。
TL;DR
- SubComponent化によって、MembersInjectorは生成されなくなる
- SubComponentは親Component内に実装クラスが生成される
- injectまでの流れは、SubComponentを使わない場合とそんなに変わらない
今回も簡単なアプリを作ってみる。最終的な構成は次の通り。
Daggerを使ってDIする前の各Activityはこんな感じ。
class MainActivity1 : AppCompatActivity() { private val messageApi: MessageApi = MessageApiClient(MessageCreator()) private lateinit var messageTextView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_1) messageTextView = findViewById(R.id.message_text_view) findViewById<Button>(R.id.message_button).setOnClickListener { val message = messageApi.getMessage() messageTextView.text = message } findViewById<Button>(R.id.next_button).setOnClickListener { val intent = MainActivity2.createIntent(this@MainActivity1) startActivity(intent) } } }
MainActivity1
では、MessageButtonを押すと、 MessageApi
を介して文字列を取得し、TextViewに表示する。NextButtonを押すと、 MainActivity2
に遷移する。
class MainActivity2 : AppCompatActivity() { companion object { fun createIntent(context: Context): Intent = Intent(context, MainActivity2::class.java) } private val letterApi: LetterApi = LetterApiClient(MessageCreator(), UserCreator()) private lateinit var messageTextView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main_2) messageTextView = findViewById(R.id.message_text_view) findViewById<Button>(R.id.message_button).setOnClickListener { val letter = letterApi.getLetter() messageTextView.text = letter } findViewById<Button>(R.id.back_button).setOnClickListener { finish() } } }
MainActivity2
では、MessageButtonを押すと、 LetterApi
を介して文字列を取得し、TextViewに表示する。
敢えて MessageApiClient
と LetterApiClient
のインスタンス作成には、 MessageCreator
が必要である構成にした。これは、 Retrofit を使用したApiClientでは、 Retrofit
インスタンスを共通で必要とするため、それを想定したためである。
これらのActivityにDaggerを使って、DIしていく。
まず、injectしたいインスタンスをどのようにインスタンス化するかを決めるためにModuleを作っていく。
@Module class CreatorModule { @Provides @Singleton fun provideMessageCreator(): MessageCreator = MessageCreator() @Provides @Singleton fun provideUserCreator(): UserCreator = UserCreator() }
@Module class MessageModule { @Provides @ActivityScope fun provideMessageApi(messageCreator: MessageCreator): MessageApi = MessageApiClient(messageCreator) }
@Module class LetterModule { @Provides @ActivityScope fun provideLetterApi(messageCreator: MessageCreator, userCreator: UserCreator): LetterApi = LetterApiClient(messageCreator, userCreator) }
今回 CreatorModule#provideMessageCreator
と CreatorModule#provideUserCreator
には、 @Singleton
を指定している。また、 MessageModule#provideMessageApi
と LetterModule#provideLetterApi
には @ActivityScope
を指定している。
次に、何をどこにinjectするかを決めるために、Componentを作る。
今回は MainActivity1Component
と MainActivity2Component
を AppComponent
のSubComponentとした。また、これらのSubComponentを束ねるために ActivityModule
を作成している。
@Subcomponent(modules = [MessageModule::class]) @ActivityScope interface MainActivity1Component { fun inject(mainActivity1: MainActivity1) @Subcomponent.Builder interface Builder { fun build(): MainActivity1Component } }
@Subcomponent(modules = [LetterModule::class]) @ActivityScope interface MainActivity2Component { fun inject(mainActivity2: MainActivity2) @Subcomponent.Builder interface Builder { fun build(): MainActivity2Component } }
@Module(subcomponents = [MainActivity1Component::class, MainActivity2Component::class]) class ActivityModule
@Component(modules = [CreatorModule::class, ActivityModule::class]) @Singleton interface AppComponent { fun mainActivity1ComponentBuilder(): MainActivity1Component.Builder fun mainActivity2ComponentBuilder(): MainActivity2Component.Builder }
これでようやく、各Activityのメンバーにinjectできるようになったので、 MainActivity1
と MainActivity2
を以下のように変更する。
class MainActivity1 : AppCompatActivity() { @Inject lateinit var messageApi: MessageApi private lateinit var messageTextView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (application as App).appComponent .mainActivity1ComponentBuilder() .build() .inject(this) setContentView(R.layout.activity_main_1) messageTextView = findViewById(R.id.message_text_view) findViewById<Button>(R.id.message_button).setOnClickListener { val message = messageApi.getMessage() messageTextView.text = message } findViewById<Button>(R.id.next_button).setOnClickListener { val intent = MainActivity2.createIntent(this@MainActivity1) startActivity(intent) } } }
class MainActivity2 : AppCompatActivity() { companion object { fun createIntent(context: Context): Intent = Intent(context, MainActivity2::class.java) } @Inject lateinit var letterApi: LetterApi private lateinit var messageTextView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) (application as App).appComponent .mainActivity2ComponentBuilder() .build() .inject(this) setContentView(R.layout.activity_main_2) messageTextView = findViewById(R.id.message_text_view) findViewById<Button>(R.id.message_button).setOnClickListener { val letter = letterApi.getLetter() messageTextView.text = letter } findViewById<Button>(R.id.back_button).setOnClickListener { finish() } } }
class App : Application() { lateinit var appComponent: AppComponent override fun onCreate() { super.onCreate() appComponent = DaggerAppComponent.create() } }
さて、ここからDaggerがどのようにDIをしているか、生成されたコードを見ていく。
まずは、 DaggerAppComponent
を見てみる。
// DaggerAppComponent private Provider<MessageCreator> provideMessageCreatorProvider; private Provider<UserCreator> provideUserCreatorProvider; private DaggerAppComponent(CreatorModule creatorModuleParam) { initialize(creatorModuleParam); } public static AppComponent create() { return new Builder().build(); } @SuppressWarnings("unchecked") private void initialize(final CreatorModule creatorModuleParam) { this.provideMessageCreatorProvider = DoubleCheck.provider(CreatorModule_ProvideMessageCreatorFactory.create(creatorModuleParam)); this.provideUserCreatorProvider = DoubleCheck.provider(CreatorModule_ProvideUserCreatorFactory.create(creatorModuleParam)); } public static final class Builder { private CreatorModule creatorModule; public AppComponent build() { if (creatorModule == null) { this.creatorModule = new CreatorModule(); } return new DaggerAppComponent(creatorModule); } }
DaggerAppComponent#create
を呼んだ時の動きは、 前記事で書いたものと変わらない。
CreatorModule#provideMessageCreator
と CreatorModule#provideUserCreator
をSingletonスコープで定義したため、 DoubleCheck<CreatorModule_ProvideMessageCreatorFactory>
と DoubleCheck<CreatorModule_ProvideUserCreatorFactory>
がインスタンス化されていることがわかる。
次に、 MainActivity1
内の
// MainActivity1 (application as App).appComponent .mainActivity1ComponentBuilder() .build() .inject(this)
で何が起きているか見ていく。
まず、 .mainActivity1ComponentBuilder()
の部分。
// DaggerAppComponent @Override public MainActivity1Component.Builder mainActivity1ComponentBuilder() { return new MainActivity1ComponentBuilder(); }
AppComponent#mainActivity1ComponentBuilder
を介して、 MainActivity1ComponentBuilder
のインスタンスを生成している。
次に .build()
の部分。
// DaggerAppComponent private final class MainActivity1ComponentBuilder implements MainActivity1Component.Builder { private MessageModule messageModule; @Override public MainActivity1Component build() { if (messageModule == null) { this.messageModule = new MessageModule(); } return new MainActivity1ComponentImpl(messageModule); } } private final class MainActivity1ComponentImpl implements MainActivity1Component { private Provider<MessageApi> provideMessageApiProvider; private MainActivity1ComponentImpl(MessageModule messageModuleParam) { initialize(messageModuleParam); } @SuppressWarnings("unchecked") private void initialize(final MessageModule messageModuleParam) { this.provideMessageApiProvider = DoubleCheck.provider( MessageModule_ProvideMessageApiFactory.create( messageModuleParam, DaggerAppComponent.this.provideMessageCreatorProvider)); } }
MessageModule
をインスタンス化し、 MainActivity1ComponentImpl
を返している。
MainActivity1ComponentImpl#initialize
では、DoubleCheck<MessageModule_ProvideMessageApiFactory>
がインスタンス化されている。 MessageModule#provideMessageApi
は @ActivityScope
を指定したため、このタイミングで Factoryクラスがインスタンス化され、 DoubleCheck
でwrapされている。
次に .inject(this)
の部分。
// DaggerAppComponent private final class MainActivity1ComponentImpl implements MainActivity1Component { private Provider<MessageApi> provideMessageApiProvider; @Override public void inject(MainActivity1 mainActivity1) { injectMainActivity1(mainActivity1); } private MainActivity1 injectMainActivity1(MainActivity1 instance) { MainActivity1_MembersInjector.injectMessageApi(instance, provideMessageApiProvider.get()); return instance; } }
このタイミングで、 MainActivity1ComponentImpl#injectMainActivity1
によって、MainActivity
のメンバーにinjectが行われる。この際に、 provideMessageApiProvider.get()
によって、 DoubleCheck<MessageModule_ProvideMessageApiFactory>
を介して MessageApiClient
が初めてインスタンス化される。
MainActivity2
も同様の流れでinjectが行われる。
異なる部分は、 MainActivity2ComponentImpl
をインスタンス化した際に、 LetterModule_ProvideLetterApiFactory
をインスタンス化するための引数が増えるくらいである。
また、 MainActivity1ComponentImpl#initialize
と MainActivity2ComponentImpl#initialize
で使用される provideMessageCreatorProvider
は DaggerAppComponent#create
時に生成された DoubleCheck<CreatorModule_ProvideMessageCreatorFactory>
であるため、 それぞれのActivityで使用している MessageCreator
は同一のインスタンス(Singletonスコープで定義したため)であることに注意したい。
今回Daggerによって生成されたクラスは、以下の5つである。
- DaggerAppComponent
- CreatorModule_ProvideMessageCreatorFactory
- CreatorModule_ProvideUserCreatorFactory
- MessageModule_ProvideMessageApiFactory
- LetterModule_ProvideLetterApiFactory
前の記事 では、ActivityごとにMembersInjectorが生成されていたが、SubComponentにinject処理を移したため、無くなっていることがわかる。また、SubComponentは親Componentが依存するModuleに依存するため、親Component内に実装クラスが生成される。