Search code examples
androiddagger-2dynamic-feature-moduledagger-hilt

Android Dynamic Feature modules with Dagger Hilt


I have built a Dynamic feature module sample with Fragments, sub components and dependent components based on plaid app, if you wish to check out here is the link. Now, i'm trying to convert it to Dagger Hilt using the official android document.

In core module which is the library module, app module and dynamic feature modules depend on

@Singleton
@Component(modules = [CoreModule::class])
interface CoreComponent {

    /*
        Provision methods to provide dependencies below to components that depends on
        CoreComponent
     */
    fun coreDependency(): CoreDependency

    fun coreCameraDependency(): CoreCameraDependency

    fun corePhotoDependency(): CorePhotoDependency

    fun coreActivityDependency(): CoreActivityDependency

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance application: Application): CoreComponent
    }

}

and it's module

@Module(includes = [CoreProvideModule::class])
abstract class CoreModule {
    @Binds
    abstract fun bindContext(application: Application): Context
}

@Module
object CoreProvideModule {

    @Singleton
    @Provides
    fun provideCoreDependency(application: Application) = CoreDependency(application)

    @ActivityScope
    @Provides
    fun provideCoreActivityDependency(context: Context) = CoreActivityDependency(context)

    @Provides
    fun provideCoreCameraDependency(): CoreCameraDependency = CoreCameraDependency()

    @Provides
    fun provideCorePhotoDependency(): CorePhotoDependency = CorePhotoDependency()

}

How is CoreComponent migrated? Do provision methods still stay and i only change

@Singleton
@DefineComponent

or

@Singleton
@DefineComponent(parent = ApplicationComponent.class)

for CoreModule i guess i only change

@EntryPoint
@InstallIn(CoreComponent::class)

or is this for adding provision methods in CoreComponent?

How do i create sub-component in app module?

If anyone has a sample with dynamic feature fragments and hilt, or tutorial to build, it would be more than welcome. I'm just working on it at the moment, if i figure it out i would post an answer


Solution

  • I finally figured it out.

    For an app structure

    FeatureCamera  FeaturePhotos  (Dynamic Feature Modules)  
    |         |    |
    |         ----App
    |              |
    core(android-library)
    

    Camera dynamic feature module dependencies from core module, Photo dynamic feature module dependencies from app.

    First create a CoreModule in library module

    @InstallIn(ApplicationComponent::class)
    @Module
    class CoreModule {
    
        @Singleton
        @Provides
        fun provideCoreDependency(application: Application) = CoreDependency(application)
    
        @Provides
        fun provideCoreActivityDependency(context: Application) = CoreActivityDependency(context)
    
        @Provides
        fun provideCoreCameraDependency(): CoreCameraDependency = CoreCameraDependency()
    
        @Provides
        fun provideCorePhotoDependency(): CorePhotoDependency = CorePhotoDependency()
    }
    

    An interface with @EntryPoint is required to with provision methods defined in this interface, if you don't define a method for that dependency you cannot inject it even though there is a @Provides method for it. These are mock dependencies that take application or context as only parameter.

    @EntryPoint
    @InstallIn(ApplicationComponent::class)
    interface CoreComponent {
    
        /*
            Provision methods to provide dependencies to components that depend on this component
         */
        fun coreDependency(): CoreDependency
    
        fun coreActivityDependency(): CoreActivityDependency
    
        fun coreCameraDependency(): CoreCameraDependency
    
        fun corePhotoDependency(): CorePhotoDependency
        
    }
    

    In camera dynamic feature module, create another module for the dependency based inside of this dynamic feature module.

    @InstallIn(FragmentComponent::class)
    @Module(includes = [CameraBindModule::class])
    class CameraModule {
    
        @Provides
        fun provideCameraObject(context: Context) = CameraObject(context)
    }
    
    @InstallIn(FragmentComponent::class)
    @Module
    abstract class CameraBindModule {
        @Binds
        abstract fun bindContext(application: Application): Context
    }
    

    And component to inject dependencies to Fragments or Activities in this DFM.

    @Component(
            dependencies = [CoreComponent::class],
            modules = [CameraModule::class]
    )
    interface CameraComponent {
    
        fun inject(cameraFragment1: CameraFragment1)
        fun inject(cameraFragment2: CameraFragment2)
    
    
        fun inject(cameraActivity: CameraActivity)
    
        @Component.Factory
        interface Factory {
            fun create(coreComponent: CoreComponent, @BindsInstance application: Application): CameraComponent
        }
    
    }
    
    If injected to Activity call in `onCreate()`
    
    
          DaggerCameraComponent.factory().create(
                    EntryPointAccessors.fromApplication(
                            applicationContext,
                            CoreComponent::class.java
                    ),
                    application
            )
                    .inject(this)
    
    For injecting to Fragment call in `onCreate()`
    
        DaggerCameraComponent.factory().create(
                EntryPointAccessors.fromApplication(
                        requireActivity().applicationContext,
                        CoreComponent::class.java
                ),
                requireActivity().application
        )
                .inject(this)
    
    The trick is here to get dependency interface annotated with `@EntryPoint`
    using `EntryPointAccessors.fromApplication()`
    
    Also created Activity based dependencies in app module
    
    **MainActivityModule.kt**
    
        @InstallIn(ActivityComponent::class)
        @Module(includes = [MainActivityBindModule::class])
        class MainActivityModule {
        
            @Provides
            fun provideToastMaker(application: Application) = ToastMaker(application)
        
            @ActivityScoped
            @Provides
            fun provideMainActivityObject(context: Context) = MainActivityObject(context)
        
        }
        
        @InstallIn(ActivityComponent::class)
        @Module
        abstract class MainActivityBindModule {
        
            @Binds
            abstract fun bindContext(application: Application): Context
        
        }
    
    And only intend to inject these dependencies to Photos dynamic feature module so named it as `PhotoDependencies`
    
        @EntryPoint
        @InstallIn(ActivityComponent::class)
        interface PhotoModuleDependencies {
        
            fun toastMaker(): ToastMaker
        
            fun mainActivityObject(): MainActivityObject
        }
    
    
    In Photos dynamic feature module create dagger module named `PhotoModule`
    
        @InstallIn(FragmentComponent::class)
        @Module(includes = [PhotoBindModule::class])
        class PhotoModule {
        
            @Provides
            fun providePhotoObject(application: Application): PhotoObject = PhotoObject(application)
        
        }
        
        @InstallIn(FragmentComponent::class)
        @Module
        abstract class PhotoBindModule {
            @Binds
            abstract fun bindContext(application: Application): Context
        }
    
    And component 
    
        @Component(
                dependencies = [PhotoModuleDependencies::class],
                modules = [PhotoModule::class]
        )
        interface PhotoComponent {
        
            fun inject(photosFragment1: PhotoFragment1)
            fun inject(photosFragment2: PhotoFragment2)
            
            @Component.Factory
            interface Factory {
                fun create(photoModuleDependencies: PhotoModuleDependencies,
                           @BindsInstance application: Application): PhotoComponent
            }
        }
    
    
    And inject to fragments with
    
    
        DaggerPhotoComponent.factory().create(
                EntryPointAccessors.fromActivity(
                        requireActivity(),
                        PhotoModuleDependencies::class.java
                ),
                requireActivity().application
        )
                .inject(this)
    
    The trick here is to get `EntryPointAccessors.fromActivity` instead of fromApplication.
    
    You can check out [this link][1] if you wish to experiment yourself.
    
    If you wish to add `ViewModel` to dynamic feature modules with hilt you can check out my answer [here][2].
    
    
      [1]: https://github.com/SmartToolFactory/Dagger2-Tutorials/tree/master/Tutorial10-1DFM-DaggerHilt
      [2]: https://stackoverflow.com/questions/63671489/how-to-create-viewmodel-in-dynamic-feature-module-with-dagger-hilt