Подмодули с Dagger и AndroidInjectors - PullRequest
3 голосов
/ 05 июля 2019

Некоторое время я был Dagger с предустановленным стилем Android Injector, и теперь решил попробовать новые методы. До сих пор я в основном заявлял о AppComponent так:

@Singleton
@Component(
        modules = [ApplicationModule::class,
            NetModule::class,
            ApiModule::class,
            AnalyticsModule::class,
            DbModule::class,
            RepositoryModule::class,
            InteractorModule::class]
)
interface ApplicationComponent {
    fun inject(app: MyApp)
    fun plus(controllerModule: ControllerModule): ControllerComponent
}

Тогда я бы вставил свой Activities / Fragments / Services / Dialogs так:

class MyActivity : AppCompatActivity() {
...
    val component by lazy {
            (application as MyApp)
                    .applicationComponent
                    .plus(
                            ControllerModule(this)
                    )
        }

    override fun inject() {
            component.inject(this)
        }
...
}

В основном у меня был один компонент приложения верхнего уровня с модулями обхода приложений, затем компонент уровня активности (ControllerComponent) с экземплярами для каждого действия, общий для всех действий.

Теперь, когда я переключился на новые методы, я создаю свой компонент следующим образом:

@Singleton
@Component(
        modules = [
            AndroidSupportInjectionModule::class,
            AppModule::class,
            NetModule::class,
            ApiModule::class,
            AnalyticsModule::class,
            DbModule::class,
            RepositoryModule::class,
            InteractorModule::class
        ]
)
interface AppComponent : AndroidInjector<SoulpicksApp> {

    @Component.Builder
    interface Builder {
        fun build(): AppComponent
        @BindsInstance
        fun application(application: SoulpicksApp): Builder
    }

}

Расширение моего приложения DaggerApplication:

open class MyApp : DaggerApplication() {


    override fun applicationInjector(): AndroidInjector<out DaggerApplication> = DaggerAppComponent.builder().application(this).build()

}

А мои Activities / Fragments расширяются DaggerAppCompatActivty / DaggerFragment соответственно:

class MyActivity : DaggerAppCompatActivity() {

...
}

Я понимаю, что это должно автоматически подключить все зависимости действий при условии, что Dagger настроен правильно. Однако я не объявил свой ControllerModule / Component, поэтому, конечно, при запуске моего приложения я получаю:

e: /Users/user/dev/my-android/app/build/generated/source/kapt/devDebug/com/myapp/android/di/activity/ActivityBinder_ContributesMyActivity.java:28: error: @Subcomponent.Builder is missing setters for required modules or subcomponents: [com.myapp.android.di.controller.ControllerModule]

Я понимаю, что ранее я создавал этот компонент для каждого Activity, используя метод plus () и вводя его явно (чего я здесь и стараюсь избегать), как я могу это сделать сейчас?

Кроме того, у меня есть некоторые BottomSheetDialogFragments и JobServiceIntents в моем приложении, и нет эквивалента DaggerBottomSheedDialogFragments / DaggerJobServiceIntents для расширения, как я могу обойти это?

ControllerModule:

@Module
class ControllerModule(val activity: androidx.fragment.app.FragmentActivity) {

    @Provides
    @ControllerScope
    fun context(): Context = activity

    @Provides
    @ControllerScope
    fun activity() = activity

    @Provides
    @ControllerScope
    fun layoutInflater() = activity.layoutInflater

    @Provides
    @ControllerScope
    fun fragmentManager(): androidx.fragment.app.FragmentManager = activity.supportFragmentManager

    @Provides
    @ControllerScope
    fun provideNavigationController(activity: androidx.fragment.app.FragmentActivity, analyticsManager: AnalyticsCompositeManager) = NavigationController(activity, analyticsManager)

    @Provides
    @ControllerScope
    fun providePackageUtils(activity: androidx.fragment.app.FragmentActivity) : PackageUtils = PackageUtilsImpl(activity)
}

Изменения после ответа @luis_cortes:

пакет io.soulpicks.android.di.activity

@Module
abstract class ActivityBinder {

    @ControllerScope
    @ContributesAndroidInjector(modules = [ControllerModule::class])
    abstract fun constributesSplashActivity(): SplashActivity

    @ControllerScope
    @ContributesAndroidInjector(modules = [ControllerModule::class])
    abstract fun contributesDashboardActivity(): DashboardActivity

    ....


}

ControllerModule:

@Module(includes = [ViewContainerModule::class])
class ControllerModule {


    @Provides
    @ControllerScope
    fun context(activity: DaggerAppCompatActivity): Context = activity.applicationContext

    @Provides
    @ControllerScope
    fun layoutInflater(activity: DaggerAppCompatActivity) : LayoutInflater = activity.layoutInflater

    @Provides
    @ControllerScope
    fun fragmentManager(activity: DaggerAppCompatActivity): FragmentManager = activity.supportFragmentManager

    @Provides
    @ControllerScope
    fun navigationController(activity: DaggerAppCompatActivity, analyticsManager: AnalyticsCompositeManager): NavigationController = NavigationController(activity, analyticsManager)

    @Provides
    @ControllerScope
    fun providePackageUtils(activity: DaggerAppCompatActivity): PackageUtils = PackageUtilsImpl(activity)
}

Ошибка:

e: /Users/kelmer/dev/myapp-android/app/build/tmp/kapt3/stubs/devDebug/io/myapp/android/di/application/AppComponent.java:8: error: [Dagger/MissingBinding] com.myapp.android.managers.PackageUtils cannot be provided without an @Provides-annotated method.
public abstract interface AppComponent extends dagger.android.AndroidInjector<com.myapp.android.MyApp> {
                ^
      com.myapp.android.managers.PackageUtils is injected at
          com.myapp.android.ui.invite.SendInviteViewModel(packageUtils, …)
      com.myapp.android.ui.invite.SendInviteViewModel is injected at
          com.myapp.android.di.viewmodel.ViewModelModule.sendInviteViewModel$app_devDebug(sendInviteViewModel)
      java.util.Map<java.lang.Class<? extends androidx.lifecycle.ViewModel>,javax.inject.Provider<androidx.lifecycle.ViewModel>> is injected at
          com.myapp.android.di.viewmodel.MyappViewModelFactory(viewModels)
      com.myapp.android.di.viewmodel.MyappViewModelFactory is injected at
          com.myapp.android.di.viewmodel.ViewModelModule.bindViewModelFactory$app_devDebug(factoryMyapp)
      androidx.lifecycle.ViewModelProvider.Factory is injected at
          com.myapp.android.base.BaseActivity.viewModelFactory
      com.myapp.android.ui.splash.SplashActivity is injected at
          dagger.android.AndroidInjector.inject(T) [com.myapp.android.di.application.AppComponent → com.myapp.android.di.activity.ActivityBinder_ConstributesSplashActivity.SplashActivitySubcomponent]
  It is also requested at:
      com.myapp.android.ui.dashboard.friends.contact.ContactSyncViewModel(…, packageUtils, …)
      com.myapp.android.views.appchoosedialog.AppChooserViewModel(packageUtils)
  The following other entry points also depend on it:
      dagger.android.AndroidInjector.inject(T) [com.myapp.android.di.application.AppComponent → com.myapp.android.di.activity.ActivityBinder_ContributesDashboardActivity.DashboardActivitySubcomponent]

https://github.com/kelmer44/mvvm-base

branch master - текущая реализация (без dagger-android) ветка chore/dagger-rework моя попытка

1 Ответ

1 голос
/ 08 июля 2019

Обновление

Мне удалось заставить пример проекта работать с некоторыми незначительными изменениями в исходном решении. Я обновил ответ ниже для потомков и отметил шаги, которые являются новыми или изменились жирным шрифтом.

Примечание: Были некоторые очень небольшие изменения, необходимые для ControllerModule в примере проекта, которые также были необходимы, но ответы ниже - это то, что будет наиболее применимо ко всем остальным на сайте, поэтому я решил опустить их здесь.


  • Создайте файл с именем MainActivityModule со следующим:
@Module
abstract class MainActivityModule {
   @Binds @ControllerScope
   abstract fun bindsActivity(mainActivity: MainActivity): FragmentActivity
}
  • Добавьте это к модулю, установленному на вашем AppComponent:
@ContributesAndroidInjector([ControllerModule::class, MainActivityModule::class])
@ControllerScope 
abstract fun contributesMyActivity(): MyActivity
  • Удалите val activity: androidx.fragment.app.FragmentActivity как свойство в конструкторе для ControllerModule

  • Измените @Provides методы на FragmentActivity следующим образом:

@Provides
@ControllerScope
fun providesContext(activity: FragmentActivity): Context = activity

Для других ваших классов это просто зависит от того, контролируете ли вы, когда и как они создаются. Если вы это сделаете, просто используйте обычный инжектор конструктора.

...