( x-post от / r / androiddev )
Я просто хотел бы предвосхитить это, сказав, что это не пост "что лучше"; это строго вопрос о том, как я могу построить что-то, используя Dagger (и как я это сделал в Kodein, чтобы проиллюстрировать проблему).
Я уже несколько лет использую Kodein в нескольких рабочих проектах, и мне было так легко с ним работать, что я больше никогда не смотрю на Dagger. Я начал новый личный проект, и я подумал, что дам Dagger еще один шанс.
Для простоты у меня есть 3 модуля (это обычное настольное приложение, а не Android);
- Приложение
- общий
- Google
app
содержит один класс App
:
class App(
private val api: GoogleApi,
private val argParser: ArgParser
) {
fun run() {
while(true) {
api.login(argParser.username, argParser.password);
}
}
}
common
содержит один класс ArgParser
(реализация не важна)
google
содержит несколько классов:
class GoogleApi(
driveProvider: () -> Drive
) {
private val drive by lazy {
driveProvider()
}
fun login(username: String, password: String) {
drive.login() // not real call
}
}
internal class CredentialRetriever(
private val transport: NetHttpTransport,
private val jsonFactory: JacksonFactory
) {
fun retrieveCredentials() = ...
}
Зависимости для google
:
dependencies {
implementation "com.google.api-client:google-api-client:$googleApiVersion"
implementation "com.google.oauth-client:google-oauth-client-jetty:$googleApiVersion"
implementation "com.google.apis:google-api-services-drive:v3-rev110-$googleApiVersion"
}
Я специально использую implementation
, потому что я не хочу, чтобы кто-либо напрямую использовал базовые библиотеки Google.
Чтобы заставить это работать в Кодейне, я делаю следующее в main
:
fun main(args: Array<String>) {
val kodein = Kodein {
import(commonModule(args = args))
import(googleModule)
import(appModule)
bind<App>() with singleton {
App(
api = instance(),
argParser = instance()
)
}
}
kodein.direct.instance<App>().run()
}
затем в google
:
val googleModule = Kodein.Module("Google") {
bind<CredentialRetriever>() with provider {
CredentialRetriever(jsonFactory = instance(), transport = instance())
}
bind<Drive>() with provider {
Drive.Builder(
instance(),
instance(),
instance<CredentialRetriever>().retrieveCredentials()
).setApplicationName("Worker").build()
}
bind<GoogleApi>() with singleton {
GoogleApi(drive = provider())
}
bind<JacksonFactory>() with provider {
JacksonFactory.getDefaultInstance()
}
bind<NetHttpTransport>() with provider{
GoogleNetHttpTransport.newTrustedTransport()
}
}
и, наконец, common
:
fun commonModule(args: Array<String>) = Kodein.Module("Common") {
bind<ArgParser>() with singleton { ArgParser(args = args) }
}
Я попытался реализовать это в Dagger, но не смог заставить его работать. Моей первой попыткой было получить Component
in app
, в котором использовались модули из common
и google
. Это не сработало, потому что сгенерированный код ссылался на классы, которые не были представлены из google
(например, Drive
). Я мог бы исправить это, сделав их api
зависимостями, но я не хочу показывать их:
// CredentialRetriever and GoogleApi were updated to have @Inject constructors
// GoogleApi also got an @Singleton
@Module
object GoogleModule {
@Provides
internal fun drive(
transport: NetHttpTransport,
jsonFactory: JacksonFactory,
credentialRetriever: CredentialRetreiver
): Drive =
Drive.Builder(
transport,
jsonFactory,
credentialRetriever.retrieveCredentials()
).setApplicationName("Worker").build()
@Provides
internal fun jsonFactory(): JacksonFactory =
JacksonFactory.getDefaultInstance()
@Provides
internal fun netHttpTransport(): NetHttpTransport =
GoogleNetHttpTransport.newTrustedTransport()
}
Далее я попытался создать компонент для каждого модуля (то есть модуля gradle):
// in google module
@Singleton
@Component(modules = [GoogleModule::class])
interface GoogleComponent {
fun googleApi(): GoogleApi
}
// in common module
@Singleton
@Component(modules = [CommonModule::class])
interface CommonComponent {
fun argParser(): ArgParser
}
Затем в app
началось веселье:
// results in "AppComponent (unscoped) cannot depend on scoped components:"
@Component(dependencies = [CommonComponent::class, GoogleComponent::class])
interface AppComponent {
fun app(): App
}
ОК, так что давайте сделаем так:
// results in "This @Singleton component cannot depend on scoped components:"
@Singleton
@Component(dependencies = [CommonComponent::class ,GoogleComponent::class])
interface AppComponent {
fun app(): App
}
РЕДАКТИРОВАТЬ : попытался заставить AppComponent
использовать пользовательскую область:
// results in "AppComponent depends on more than one scoped component:"
@AppScope
@Component(dependencies = [CommonComponent::class ,GoogleComponent::class])
interface AppComponent {
fun app(): App
}
Как я могу добиться этого в Dagger? Я прочитал документы, думаю, что я их немного понимаю, но понятия не имею, что делать дальше.