Dagger 2:多模块项目,注入依赖项,但在运行时出现“lateinit 属性存储库尚未初始化”错误

Lee*_*eem 6 android kotlin dagger-2 kotlin-android-extensions

Dagger版本是2.25.2。

我有两个 Android 项目模块:coremodule 和appmodule 。

core模块中,我定义了 dagger CoreComponent

app模块中我有AppComponent匕首。

CoreComponet在核心项目模块中:

@Component(modules = [MyModule::class])
@CoreScope
interface CoreComponent {
   fun getMyRepository(): MyRepository
}
Run Code Online (Sandbox Code Playgroud)

在核心项目模块中,我有一个存储库类,它不属于任何 dagger 模块,但我@Inject在其构造函数旁边使用注释:

class MyRepository @Inject constructor() {
   ...
}
Run Code Online (Sandbox Code Playgroud)

我的应用程序组件:

@Component(modules = [AppModule::class], dependencies = [CoreComponent::class])
@featureScope
interface AppComponent {
    fun inject(activity: MainActivity)
}
Run Code Online (Sandbox Code Playgroud)

MainActivity

class MainActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val coreComponent = DaggerCoreComponent.builder().build()

        DaggerAppComponent
                  .builder()
                  .coreComponent(coreComponent)
                  .build()
                  .inject(this)
     }

}
Run Code Online (Sandbox Code Playgroud)

我的项目是MVVM架构,总的来说:

  • MainActivity主机MyFragment

  • MyFragment有参考MyViewModel

  • MyViewModel具有依赖性MyRepository(如上所述MyRepositorycore模块中)

这是MyViewModel

class MyViewModel : ViewModel() {
    // Runtime error: lateinit property repository has not been initialize
    @Inject
    lateinit var repository: MyRepository

    val data = repository.getData()

}
Run Code Online (Sandbox Code Playgroud)

MyViewModel在MyFragment中初始化:

class MyFragment : Fragment() {
   lateinit var viewModel: MyViewModel

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

当我运行我的应用程序时,它因运行时错误而崩溃:

kotlin.UninitializedPropertyAccessException: lateinit property repository has not been initialize
Run Code Online (Sandbox Code Playgroud)

该错误告诉我匕首依赖注入不适用于我的设置。那么,我想念什么呢?如何摆脱这个错误?

====更新=====

我试过 :

class MyViewModel @Inject constructor(private val repository: MyRepository): ViewModel() {
        val data = repository.getData()
    }
Run Code Online (Sandbox Code Playgroud)

现在,当我运行该应用程序时,我收到新错误:

Caused by: java.lang.InstantiationException: class foo.bar.MyViewModel has no zero argument constructor
Run Code Online (Sandbox Code Playgroud)

======更新2=====

现在,我创建了MyViewModelFactory

class MyViewModelFactory @Inject constructor(private val creators: Map<Class<out ViewModel>,
                                            @JvmSuppressWildcards Provider<ViewModel>>): ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = creators[modelClass] ?: creators.entries.firstOrNull {
            modelClass.isAssignableFrom(it.key)
        }?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

我将 MyFragment 更新为:

class MyFragment : Fragment() {
   lateinit var viewModel: MyViewModel
   @Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

   override fun onAttach(context: Context) {
    // inject app component in MyFragment
    super.onAttach(context)
    (context.applicationContext as MyApplication).appComponent.inject(this)
}

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // I pass `viewModelFactory` instance here, new error here at runtime, complaining viewModelFactory has not been initialized
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(MyViewModel::class.java)
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我运行我的应用程序,出现新错误:

kotlin.UninitializedPropertyAccessException: lateinit property viewModelFactory has not been initialized
Run Code Online (Sandbox Code Playgroud)

还缺少什么?

jsa*_*mol 3

为了注入依赖项 Dagger 必须是:

  • 负责创建对象,或者
  • 要求执行注入,就像在活动或片段中一样,由系统实例化:
DaggerAppComponent
    .builder()
    .coreComponent(coreComponent)
    .build()
    .inject(this)
Run Code Online (Sandbox Code Playgroud)

在您的第一种方法中,上述情况均不成立,MyViewModel会在 Dagger 的控制之外创建一个新实例:

viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
Run Code Online (Sandbox Code Playgroud)

因此依赖关系甚至没有被初始化。此外,即使您更手动地执行注入(例如在活动中),代码仍然会失败,因为您试图repository在对象的初始化过程中引用属性val data = repository.getData(),然后才有lateinit var机会设置。在这种情况下,lazy委托会派上用场:

class MyViewModel : ViewModel() {
    @Inject
    lateinit var repository: MyRepository

    val data by lazy { repository.getData() }

    ...
}
Run Code Online (Sandbox Code Playgroud)

然而,字段注入并不是执行 DI 的最理想方式,尤其是当可注入对象需要了解它时。您可以ViewModel使用构造注入将依赖项注入到 s 中,但它需要一些额外的设置。

问题在于 Android SDK 创建和管理视图模型的方式。它们是使用 a 创建的ViewModelProvider.Factory,默认的需要视图模型具有无参构造函数。因此,执行构造函数注入所需要做的主要是提供您的自定义ViewModelProvider.Factory

// injects the view model's `Provider` which is provided by Dagger, so the dependencies in the view model can be set
class MyViewModelFactory<VM : ViewModel> @Inject constructor(
    private val viewModelProvider: @JvmSuppressWildcards Provider<VM> 
) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel?> create(modelClass: Class<T>): T = 
         viewModelProvider.get() as T
}
Run Code Online (Sandbox Code Playgroud)

(有两种方法来实现自定义ViewModelProvider.Factory,第一种使用单例工厂来获取所有视图模型的地图Provider,后者(上面的方法)为每个视图模型创建一个工厂。我更喜欢第二种因为它不需要额外的样板并绑定 Dagger 模块中的每个视图模型。)

在视图模型中使用构造函数注入:

class MyViewModel @Inject constructor(private val repository: MyRepository): ViewModel() {
    val data = repository.getData()
}
Run Code Online (Sandbox Code Playgroud)

然后将工厂注入到您的活动或片段中并使用它来创建视图模型:

@Component(modules = [AppModule::class], dependencies = [CoreComponent::class])
@featureScope
interface AppComponent {
    fun inject(activity: MainActivity)
    fun inject(fragment: MyFragment)
}

class MyFragment : Fragment() {

   @Inject
   lateinit var viewModelFactory: MyViewModelFactory<MyViewModel>

   lateinit var viewModel: MyViewModel

   override fun onAttach(context: Context) {
      // you should create a `DaggerAppComponent` instance once, e.g. in a custom `Application` class and use it throughout all activities and fragments
      (context.applicationContext as MyApp).appComponent.inject(this)
      super.onAttach(context)
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProviders.of(this, viewModelFactory)[MyViewModel::class.java]
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)