Daggerがやってくれること (SubComponent編)

前回 Daggerがやってくれること という記事を書いた。
今回はその続きで、SubComponentを利用した時にDaggerがどのようなコードを生成するか見ていく。

TL;DR

  • SubComponent化によって、MembersInjectorは生成されなくなる
  • SubComponentは親Component内に実装クラスが生成される
  • injectまでの流れは、SubComponentを使わない場合とそんなに変わらない

今回も簡単なアプリを作ってみる。最終的な構成は次の通り。 f:id:ronnn:20190216205744p:plain

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に表示する。
敢えて MessageApiClientLetterApiClientインスタンス作成には、 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#provideMessageCreatorCreatorModule#provideUserCreator には、 @Singleton を指定している。また、 MessageModule#provideMessageApiLetterModule#provideLetterApi には @ActivityScope を指定している。

次に、何をどこにinjectするかを決めるために、Componentを作る。
今回は MainActivity1ComponentMainActivity2ComponentAppComponent の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できるようになったので、 MainActivity1MainActivity2 を以下のように変更する。

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#provideMessageCreatorCreatorModule#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#initializeMainActivity2ComponentImpl#initialize で使用される provideMessageCreatorProviderDaggerAppComponent#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内に実装クラスが生成される。