Kodein vs Dagger-无法使用多个模块运行Dagger [英] Kodein vs Dagger - Can't Get Dagger Working w/ Multiple Modules

查看:135
本文介绍了Kodein vs Dagger-无法使用多个模块运行Dagger的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(/r/androiddev中的x-post )

我想以说这不是更好"的帖子作为开头.严格来说,这是一个关于如何使用Dagger构建东西(以及如何在Kodein中构建它以帮助说明问题)的问题.

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 build something using Dagger (and how I built it in Kodein to help illustrate the problem).

我已经在多个工作项目中使用Kodein几年了,但我发现它使用起来非常容易,以至于我再也没有看过Dagger了.我开始了一个新的个人项目,我想再给Dagger做个尝试.

I've been using Kodein for a few years now in several work projects, and I've found it to be so easy to work with, that I never look at Dagger anymore. I started a new personal project, and I thought I'd give Dagger another shot.

为简单起见,我有3个模块(这是一个常规的桌面应用程序,不是Android的应用程序);

To keep things simple, I have 3 modules (this is a regular desktop app not an Android one);

  1. 应用
  2. 常见
  3. 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(实现并不重要)

common contains a single class ArgParser (implementation isn't important)

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库.

I specifically use implementation because I don't want anyone using the underlying Google libraries directly.

要使其在Kodein中正常运行,请在main中执行以下操作:

To get this to work in Kodein, I do the following in 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中实现此功能,但无法使其正常工作.我的第一个尝试是在app中有一个Component,它依赖于commongoogle中的模块.这行不通,因为生成的代码引用了未从google公开的类(例如Drive).我可以通过使它们成为api依赖项来解决此问题,但我不想公开它们:

I tried implementing this in Dagger, and couldn't get it to work. My first attempt was to have a Component in app that relied on modules from common and google. This didn't work, because the generated code referenced classes that weren't exposed from google (like Drive). I could've fixed this by making them api dependencies, but I don't want to expose them:

// 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()
}

接下来,我尝试为每个模块(即渐变模块)制作一个组件:

Next I tried making a component per module (gradle module that is):

// 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
}

好的,让我们对其进行范围界定:

OK so let's make it scoped:

// results in "This @Singleton component cannot depend on scoped components:"

@Singleton
@Component(dependencies = [CommonComponent::class ,GoogleComponent::class])
interface AppComponent {
  fun app(): App
}

编辑:尝试使AppComponent使用自定义范围:

EDIT: tried making AppComponent use a custom scope:

// results in "AppComponent depends on more than one scoped component:"

@AppScope
@Component(dependencies = [CommonComponent::class ,GoogleComponent::class])
interface AppComponent {
  fun app(): App
}

如何在Dagger中实现呢?我已经看过这些文档,我想我对它们有些了解,但是我不知道下一步该怎么做.

How can I achieve this in Dagger? I've read the docs, I think I somewhat understand them, but I have no clue what to do next.

推荐答案

我可以自由地将示例更改为a)删除不必要的细节,b)简化设置.

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

给出3个具有以下类的模块:

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

因此,我们需要在googleapp中都绑定ArgParser的实现.我在这里以ArgParser为例,说明如何将参数传递给我们的API. GoogleApi完全是internal,以确保没有泄漏.我们只公开接口AbstractApi.

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.

我在内部实现了GoogleApi,以消除实现/api的Gradle复杂性.行为是相同的,甚至可能更严格:我们的模块中有一些我们无法公开的类.这样,我们也可以进行编译器验证.

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.

我们可以将所有实现的详细信息隐藏在添加到google的组件中,以为接口创建我们的GoogleApi实现.

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

}

我们在这里不使用范围,因为应该在使用此组件的任何地方处理范围. ArgParser是我们可能需要提供以创建对象的参数的示例.我们也可以使用@Component.Builder代替工厂.

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将在同一模块(google)中生成组件,因此不会出现有关引用代码的任何问题.我们要做的就是在我们的app模块中检索API:

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()
    }
}

我们现在可以使用组件工厂从我们的模块创建实例.如果需要示波器,可以照常在@Provides方法上添加它.

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.

此设置应将公共界面后面的app模块中的所有详细信息完全隐藏.生成的代码位于同一模块内.

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

据报道,向组件中添加模块也会在该组件内生成工厂代码,这将尝试使用未引用的类.子组件也是如此.

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.

这篇关于Kodein vs Dagger-无法使用多个模块运行Dagger的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆