(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
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.
@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.
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.