使用Dagger 2 + Kotlin + ViewModel注入ViewModel

eas*_*ese 14 android dependency-injection kotlin dagger dagger-2

class SlideshowViewModel : ViewModel() {

@Inject lateinit var mediaItemRepository : MediaItemRepository

fun init() {
    What goes here?
}
Run Code Online (Sandbox Code Playgroud)

所以我正在尝试学习Dagger2,这样我就可以让我的应用程序更加可测试.问题是,我已经集成了Kotlin并正在研究Android Architectural组件.我知道构造函数注入是优选的,但这是不可能的ViewModel.相反,我可以使用lateinit以便注射,但我不知道如何注射.

我是否需要创建一个Componentfor SlideshowViewModel,然后注入它?或者我使用该Application组件?

gradle这个:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

kapt { 
    generateStubs = true
}
dependencies {
    compile "com.google.dagger:dagger:2.8"
    annotationProcessor "com.google.dagger:dagger-compiler:2.8"
    provided 'javax.annotation:jsr250-api:1.0'
    compile 'javax.inject:javax.inject:1'
}
Run Code Online (Sandbox Code Playgroud)

应用组件

@ApplicationScope
@Component (modules = PersistenceModule.class)
public interface ApplicationComponent {

    void injectBaseApplication(BaseApplication baseApplication);
}
Run Code Online (Sandbox Code Playgroud)

BaseApplication

    private static ApplicationComponent component;

    @Override
    public void onCreate() {
        super.onCreate();

        component = DaggerApplicationComponent
                .builder()
                .contextModule(new ContextModule(this))
                .build();
        component.injectBaseApplication(this);
    }

    public static ApplicationComponent getComponent() {
        return component;
    }
Run Code Online (Sandbox Code Playgroud)

Igo*_*lov 8

您可以为ViewModel启用构造函数注入.您可以查看Google示例,了解如何在Java中执行此操作.(更新:看起来他们将项目转换为Kotlin,因此此URL不再有效)

以下是如何在Kotlin中做类似的事情:

添加ViewModelKey批注:

import android.arch.lifecycle.ViewModel

import java.lang.annotation.Documented
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

import dagger.MapKey
import kotlin.reflect.KClass

@Suppress("DEPRECATED_JAVA_ANNOTATION")
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)
Run Code Online (Sandbox Code Playgroud)

添加ViewModelFactory:

import android.arch.lifecycle.ViewModel
import android.arch.lifecycle.ViewModelProvider

import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton

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

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        var creator: Provider<out ViewModel>? = creators[modelClass]

        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                    break
                }
            }
        }

        if (creator == null) {
            throw IllegalArgumentException("unknown model class " + modelClass)
        }

        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

添加ViewModelModule:

import dagger.Module
import android.arch.lifecycle.ViewModel
import dagger.multibindings.IntoMap
import dagger.Binds
import android.arch.lifecycle.ViewModelProvider
import com.bubelov.coins.ui.viewmodel.EditPlaceViewModel

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(EditPlaceViewModel::class) // PROVIDE YOUR OWN MODELS HERE
    internal abstract fun bindEditPlaceViewModel(editPlaceViewModel: EditPlaceViewModel): ViewModel

    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
Run Code Online (Sandbox Code Playgroud)

在组件中注册ViewModelModule

在您的活动中注入ViewModelProvider.Factory:

@Inject lateinit var modelFactory: ViewModelProvider.Factory
private lateinit var model: EditPlaceViewModel
Run Code Online (Sandbox Code Playgroud)

将modelFactory传递给每个ViewModelProviders.of方法:

model = ViewModelProviders.of(this, modelFactory)[EditPlaceViewModel::class.java]
Run Code Online (Sandbox Code Playgroud)

以下是包含所有必需更改的示例提交:支持视图模型的构造函数注入

  • 如果“ViewModelFactory”上的范围是“Singleton”,这是否意味着“ViewModelModule”中所有 ViewModel 的范围也将是“Singleton”? (2认同)

azi*_*ian 6

假设你有一个Repository可以被 Dagger 注入的MyViewModel类和一个依赖于Repository定义的类:


    class Repository @Inject constructor() {
       ...
    }

    class MyViewModel @Inject constructor(private val repository: Repository) : ViewModel() {
        ...
    }

现在您可以创建您的ViewModelProvider.Factory实现:

    class MyViewModelFactory @Inject constructor(private val myViewModelProvider: Provider<MyViewModel>) : ViewModelProvider.Factory {

      @Suppress("UNCHECKED_CAST")
      override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return myViewModelProvider.get() as T
      }

    }
Run Code Online (Sandbox Code Playgroud)

Dagger 设置看起来并不复杂:


    @Component(modules = [MyModule::class])
    interface MyComponent {
      fun inject(activity: MainActivity)
    }

    @Module
    abstract class MyModule {
      @Binds
      abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
    }

这是实际注入发生的活动类(也可能是片段):


    class MainActivity : AppCompatActivity() {

      @Inject
      lateinit var factory: ViewModelProvider.Factory
      lateinit var viewModel: MyViewModel

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

        // retrieve the component from application class
        val component = MyApplication.getComponent()
        component.inject(this)

        viewModel = ViewModelProviders.of(this, factory).get(MyViewModel::class.java)
      }

    }


joh*_*crq 4

不。您创建一个组件,在其中声明(使用)您的 viewModel。它通常是一个活动/片段。viewModel 有依赖项(mediaitemrepository),所以你需要一个工厂。像这样的东西:

    class MainViewModelFactory (
            val repository: IExerciseRepository): ViewModelProvider.Factory {

        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(p0: Class<T>?): T {
            return MainViewModel(repository) as T
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后是匕首部分(活动模块)

    @Provides
    @ActivityScope
    fun providesViewModelFactory(
            exerciseRepos: IExerciseRepository
    ) = MainViewModelFactory(exerciseRepos)

    @Provides
    @ActivityScope
    fun provideViewModel(
            viewModelFactory: MainViewModelFactory
    ): MainViewModel {
        return ViewModelProviders
                .of(act, viewModelFactory)
                .get(MainViewModel::class.java)
    }
Run Code Online (Sandbox Code Playgroud)