使用匕首2 kotlin ViewModel注入ViewModel


class SlideshowViewModel : ViewModel() {
@Inject lateinit var mediaItemRepository : MediaItemRepository
fun init() {
    What goes here?
}

因此,我正在尝试学习Dagger2,以便我可以使我的应用程序更具测试。问题是,我已经整合了Kotlin并正在研究Android架构组件。我知道构造函数注入是可取的,但是ViewModel不可能。相反,我可以使用 lateinit进行注入,但我不知所措地弄清楚如何注入。

我需要为SlideshowViewModel创建Component,然后注入它吗?还是我使用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'
}

应用程序组件

@ApplicationScope
@Component (modules = PersistenceModule.class)
public interface ApplicationComponent {
    void injectBaseApplication(BaseApplication baseApplication);
}

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

您可以为您的ViewModels启用构造函数注入。您可以查看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>)

添加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)
        }
    }
}

添加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
}

在组件中注册您的ViewModelModule

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

@Inject lateinit var modelFactory: ViewModelProvider.Factory
private lateinit var model: EditPlaceViewModel

将您的模量传递给每个ViewModelProviders.of方法:

model = ViewModelProviders.of(this, modelFactory)[EditPlaceViewModel::class.java]

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

假设您有一个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
      }
    }

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

no。您可以在声明(使用(视图模型的情况下创建一个组件。通常是活动/碎片。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
        }
    }

然后dagger部分(活动模块(

    @Provides
    @ActivityScope
    fun providesViewModelFactory(
            exerciseRepos: IExerciseRepository
    ) = MainViewModelFactory(exerciseRepos)
    @Provides
    @ActivityScope
    fun provideViewModel(
            viewModelFactory: MainViewModelFactory
    ): MainViewModel {
        return ViewModelProviders
                .of(act, viewModelFactory)
                .get(MainViewModel::class.java)
    }

请参阅我在学习dagger kotlin

时创建的回购

本质上,您需要一个viewModelfactory实例到UI层,您可以使用它来创建ViewModel。

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

    @SuppressWarnings("Unchecked")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        var creator = creators[modelClass]
        if (creator == null) {
            for (entry in creators) {
                if (modelClass.isAssignableFrom(entry.key)) {
                    creator = entry.value
                    break
                }
            }
        }
        if (creator == null) throw IllegalArgumentException("Unknown model class" + modelClass)
        try {
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

您的ViewModelModule应该看起来像(这是您存储所有ViewModels的地方(。

@Module
abstract class ViewModelModule {
    @AppScope
    @Binds
    @IntoMap
    @ViewModelKey(YourViewModel::class)
    abstract fun bindsYourViewModel(yourViewModel: YourViewModel): ViewModel
    // Factory
    @AppScope
    @Binds abstract fun bindViewModelFactory(vmFactory: ViewModelFactory): ViewModelProvider.Factory
}

然后创建一个dagger地图键

@Documented
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

然后在您的UI层上,注入工厂并使用ViewModelProviders实例化您的ViewModel

class YourActivity : BaseActivity() {
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    lateinit var yourViewModel: YourViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        ...
        (application as App).component.inject(this)
    }
    override fun onStart() {
        super.onStart()
        yourViewModel = ViewModelProviders.of(this, viewModelFactory).get(YourViewModel::class.java)
        // you can now use your viewmodels properties and methods
        yourViewModel.methodName() 
        yourViewModel.list.observe(this, { ... })
    }

您在组件上公开了ViewModel:

@Singleton
@Component(modules={...})
public interface SingletonComponent {
    BrandsViewModel brandsViewModel();
}

现在,您可以在ViewModeLfactory内部的组件上访问此方法:

// @Inject
BrandsViewModel brandsViewModel;
...
brandsViewModel = new ViewModelProvider(this, new ViewModelProvider.Factory() {
    @Override
    public <T extends ViewModel> create(Class<T> modelClazz) {
        if(modelClazz == BrandsViewModel.class) {
            return singletonComponent.brandsViewModel();
        }
        throw new IllegalArgumentException("Unexpected class: [" + modelClazz + "]");
    }).get(BrandsViewModel.class);

所有这些都可以用kotlin来简化并隐藏:

inline fun <reified T: ViewModel> AppCompatActivity.createViewModel(crossinline factory: () -> T): T = T::class.java.let { clazz ->
    ViewModelProvider(this, object: ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if(modelClass == clazz) {
                @Suppress("UNCHECKED_CAST")
                return factory() as T
            }
            throw IllegalArgumentException("Unexpected argument: $modelClass")
        }
    }).get(clazz)
}

现在让您做

brandsViewModel = createViewModel { singletonComponent.brandsViewModel() }

现在BrandsViewModel可以从dagger接收其参数:

class BrandsViewModel @Inject constructor(
    private val appContext: Context,
    /* other deps */
): ViewModel() {
    ...
}

,如果Provider<BrandsViewModel>暴露在dagger

的情况下,目的可能更干净
interface SingletonComponent {
    fun brandsViewModel(): Provider<BrandsViewModel>
}
brandsViewModel = createViewModel { singletonComponent.brandsViewModel().get() }

尝试以下代码:

@Provides
@Singleton
fun provideRepository(): Repository {
    return Repository(DataSource())
}

我编写了一个库,该库应该使此库更加简单,更清洁,不需要多键或工厂的样板,同时还可以在运行时进一步参数ViewModel参数:https://github.com/radutopor/viewmodelfactory

@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {
    val greeting = MutableLiveData<String>()
    init {
        val user = repository.getUser(userId)
        greeting.value = "Hello, $user.name"
    }    
}

在视图中:

class UserActivity : AppCompatActivity() {
    @Inject
    lateinit var userViewModelFactory2: UserViewModelFactory2
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        appComponent.inject(this)
        val userId = intent.getIntExtra("USER_ID", -1)
        val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
            .get(UserViewModel::class.java)
        viewModel.greeting.observe(this, Observer { greetingText ->
            greetingTextView.text = greetingText
        })
    }
}

这是我使用反射的解决方案。

为简单起见,假设您有AppComponent

@AppScope
@Component(modules = [AppModule::class])
interface AppComponent {
    fun getAppContext(): Context
    fun getRepository(): Repository
    fun inject(someViewModel: SomeViewModel)
class App : Application() {
    companion object {
        lateinit var appComponent: AppComponent
            private set
    }
    ...
}
fun appComponent() = App.appComponent

您需要注入SomeViewModel

class SomeViewModel: ViewModel() {
    @Inject
    lateinit var repository: Repository
}

创建自定义懒惰属性委托

inline fun <reified T: ViewModel> Fragment.viewModel(component: Any?) = lazy {
    val vm = ViewModelProvider(this).get(T::class.java)
    component?.let {
        val m = component.javaClass.getMethod("inject", T::class.java)
        m.invoke(component, vm)
    }
    vm
}

并使用它

class SomeFragment: Fragment() {
    private val vm: SomeViewModel by viewModel(appComponent())
    ...
}

在下面的解决方案的情况下,我发现我可以通过在init或ongreate方法中加入该行(不需要工厂,因此与ViewModel和Workmanager一起使用(来使用所需的注入。P>

Injector.getComponent().inject(this)

baseapplication

class BaseApplication : Application() {
    lateinit var applicationComponent: ApplicationComponent
    override fun onCreate() {
        super.onCreate()
        INSTANCE = this
        applicationComponent = DaggerApplicationComponent
            .builder()
            //Add your modules like you did in your question above
            .build()
    }
    companion object {
        private var INSTANCE: BaseApplication? = null
        @JvmStatic
        fun get(): BaseApplication= INSTANCE!!
    }
}

喷油器

class Injector private constructor() {
    companion object {
        @JvmStatic
        fun getComponent(): ApplicationComponent = BaseApplication.get().applicationComponent
    }
}

本质上,您使用静态方法访问applicationComponent。这样,您应该能够在组件中使用此行中注入任何类型的类方法:

Injector.getComponent().inject(this)

在您的情况下

init{
    Injector.getComponent().inject(this)
}

最新更新