Search code examples
androiddagger-2dynamic-feature-module

Cannot inject object provided in AppModule to Activity in a dynamic feature module with Dagger2


With the set up below i'm not be able to inject a Singleton object to a Activity inside a dynamic feature module. I can inject to a subComponent, MainActivity, but not to an Activity that's in dynamic feature module.

@Component(modules = [AppModule::class])
interface AppComponent {

    fun inject(application: Application)

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

    // Types that can be retrieved from the graph
    fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
}

My AppModule

@Module(includes = [AppProviderModule::class])
abstract class AppModule {

    @Binds
    abstract fun bindContext(application: Application): Context
}

@Module
object AppProviderModule {

    @Provides
    @Singleton
    fun provideSharedPreferences(application: Application): SharedPreferences {
        return application.getSharedPreferences("PrefName", Context.MODE_PRIVATE)
    }
}

Dynamic Feature Module GalleryComponent

@GalleryScope
@Component(
        dependencies = [AppComponent::class],
        modules = [GalleryModule::class])
interface GalleryComponent {
    fun inject(galleryActivity: GalleryActivity)
}

And MyApplication

open class MyApplication : Application() {

    // Instance of the AppComponent that will be used by all the Activities in the project
    val appComponent: AppComponent by lazy {
        initializeComponent()
    }

    open fun initializeComponent(): AppComponent {
        // Creates an instance of AppComponent using its Factory constructor
        // We pass the applicationContext that will be used as Application
        return DaggerAppComponent.factory().create(this).apply {
            inject(this@MyApplication)
        }
    }
}

Activity in dynamic feature module, when only inject GalleryViewer and DummyDependency is injected from GalleryModule it works fine

class GalleryActivity : AppCompatActivity() {

    @Inject
    lateinit var sharedPreferences: SharedPreferences

    @Inject
    lateinit var galleryViewer: GalleryViewer

    @Inject
    lateinit var dummyDependency: DummyDependency

    override fun onCreate(savedInstanceState: Bundle?) {

        DaggerGalleryComponent.builder()
                .appComponent((application as MyApplication).appComponent)
                .build()
                .inject(this)

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_gallery)
}

When i try to inject SharedPreferences or any dependency that does not depend on any arguments like context or application from AppModule i get error

error: [Dagger/MissingBinding] android.content.SharedPreferences cannot be provided without an @Provides-annotated method.

Solution

  • The error here is not including provision method for dependencies in AppComponent and not building dynamic feature component properly.

    @Singleton
    @Component(modules = [AppModule::class])
    interface AppComponent {
    
        /**
         * 🔥🔥🔥 This method is required to get this object from a class that uses this component
         * as dependent component
         */
        fun provideSharedPreferences(): SharedPreferences
    
        fun inject(application: Application)
    
        @Component.Factory
        interface Factory {
            fun create(@BindsInstance application: Application): AppComponent
        }
    
        // Types that can be retrieved from the graph
        fun mainActivityComponentFactory(): MainActivitySubComponent.Factory
    }
    

    In dynamic feature

    @GalleryScope
    @Component(
            dependencies = [AppComponent::class],
            modules = [GalleryModule::class])
    interface GalleryComponent {
    
        fun inject(galleryActivity: GalleryActivity)
    
        // Alternative1 With Builder
        @Component.Builder
        interface Builder {
    
            fun build(): GalleryComponent
    
            @BindsInstance
            fun application(application: Application): Builder
            fun galleryModule(module: GalleryModule): Builder
    
            fun appComponent(appComponent: AppComponent): Builder
    
        }
    
        // Alternative2 With Factory
        @Component.Factory
        interface Factory {
    
            fun create(appComponent: AppComponent,
                       galleryModule: GalleryModule,
                       @BindsInstance application: Application): GalleryComponent
    
    
        }
    }
    

    You must either use Builder or Factory, with hilt none of this might be necessary in the future, however it does not support dynamic feature yet, i rather factory pattern since they deprecated Builder pattern.

    inside Activity onCreate initialize injection

    private fun initInjections() {
    
        // Alternative1 With Builder
        DaggerGalleryComponent.builder()
                .appComponent((application as MyApplication).appComponent)
                .application(application)
                .galleryModule(GalleryModule())
                .build()
                .inject(this)
    
        // Alternative2 With Factory
        DaggerGalleryComponent
                .factory()
                .create((application as MyApplication).appComponent, GalleryModule(), application)
                .inject(this)
    
    }
    

    You should choose the same pattern used inside feature component.