Kodein vs Dagger - Can't Get Dagger Working w/ Multiple Modules

后端 未结 1 777
清酒与你
清酒与你 2021-01-15 00:01

(x-post from /r/androiddev)

I would just like to preface this by saying that this is not a \"which is better\" post; this is strictly a question about how I can buil

相关标签:
1条回答
  • 2021-01-15 00:48

    I took the liberty of changing your example a bit to a) remove unnecessary details and b) simplify the setup.

    Given 3 modules with the following classes:

    // ----->> app <<-----
    class App @Inject constructor(
            private val api: AbstractApi,
            private val argParser: ArgParser
    )
    
    // ----->> google <<-----
    // expose a public interface
    interface AbstractApi
    
    // have our internal implementation
    internal class GoogleApi @Inject constructor(
            private val argParser: ArgParser
    ) : AbstractApi
    
    // ----->> common <<-----
    
    // expose some common class
    interface ArgParser
    

    So we need to bind an implementation for ArgParser in both google as well as app. I used ArgParser as an example here how we could pass arguments to our API. GoogleApi is completely internal to make sure nothing leaks. We only expose the interface AbstractApi.

    I made GoogleApi internal to remove the Gradle complexity with implementation / api. The behavior is the same, maybe even a bit more strict: We have some class in our module that we can't expose. This way we have compiler validation as well.

    We can hide all our implementation details behind a component that we add to google to create our GoogleApi implementation for the interface.

    // ----->> google
    @Component(modules = [ApiModules::class])
    interface ApiComponent {
        // has a provision method for our API
        fun api(): AbstractApi
    
        @Component.Factory
        interface Factory {
            // factory method to bind additional args that we need to supply
            fun create(@BindsInstance parser: ArgParser): ApiComponent
        }
    }
    
    @Module
    internal interface ApiModules {
        @Binds
        fun bindApi(googleApi: GoogleApi): AbstractApi
    
    }
    

    We don't use a scope here, because the scope should be handled wherever this component gets used. ArgParser is an example for an argument that we may need to supply to create the object. We could use a @Component.Builder instead of the factory, too.

    Dagger will generate the component within the same module (google), so there won't be any issues about referenced code. All we have to do is retrieve the API in our app module:

    // ----->> app
    @Component(modules = [AppModule::class])
    interface AppComponent {
        fun app(): App
    }
    
    @Module
    class AppModule {
    
        @Provides
        fun provideParser(): ArgParser = object : ArgParser {} // just bind a dummy implementation
    
        @Provides
        fun provideApi(argParser: ArgParser): AbstractApi {
            return DaggerApiComponent.factory().create(argParser).api()
        }
    }
    

    We can now use the component factory to create an instance from our module. If we need a scope we can add it as usual on the @Provides method.

    This setup should completely hide any detail from the app module behind the public interface. The generated code resides within the same module.


    Why not expose a @Module? A @Subcomponent?

    As reported, adding a module to a component will generate the factory code within that component as well, which will try to use non-referenced classes. The same would apply to a subcomponent.

    Why not use a Component Dependency?

    Since there is no scope on the component we might as well add it as a component dependency, but we wouldn't be able to add a scope then. Also we'd have a harder time passing in arguments, since we'd have to supply them when creating the component.

    0 讨论(0)
提交回复
热议问题