Inject ViewModel using Dagger 2 + Kotlin + ViewModel

后端 未结 6 1360
-上瘾入骨i
-上瘾入骨i 2021-02-07 07:07
class SlideshowViewModel : ViewModel() {

@Inject lateinit var mediaItemRepository : MediaItemRepository

fun init() {
    What goes here?
}

So I\'m tr

相关标签:
6条回答
  • 2021-02-07 07:32

    Assuming you have a Repository class that can be injected by Dagger and a MyViewModel class that has a dependency on Repository defined as such:

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

    Now you can create your ViewModelProvider.Factory implementation:

        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 setup does not look too complicated:

    
        @Component(modules = [MyModule::class])
        interface MyComponent {
          fun inject(activity: MainActivity)
        }
    
        @Module
        abstract class MyModule {
          @Binds
          abstract fun bindsViewModelFactory(factory: MyViewModelFactory): ViewModelProvider.Factory
        }
    
    

    Here's the activity class (might be fragment as well), where the actual injection takes place:

    
        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)
          }
    
        }
    
    
    0 讨论(0)
  • 2021-02-07 07:36

    I wrote a library that should make this more straightforward and way cleaner, no multibindings or factory boilerplate needed, while also giving the ability to further parametrise the ViewModel at runtime: 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"
        }    
    }
    

    In the view:

    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
            })
        }
    }
    
    0 讨论(0)
  • 2021-02-07 07:43

    No. You create a component where you are declaring (using) your viewModel. It is normally an activity/fragment. The viewModel has dependencies (mediaitemrepository), so you need a factory. Something like this:

        class MainViewModelFactory (
                val repository: IExerciseRepository): ViewModelProvider.Factory {
    
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel?> create(p0: Class<T>?): T {
                return MainViewModel(repository) as T
            }
        }
    

    Then the dagger part (activity module)

        @Provides
        @ActivityScope
        fun providesViewModelFactory(
                exerciseRepos: IExerciseRepository
        ) = MainViewModelFactory(exerciseRepos)
    
        @Provides
        @ActivityScope
        fun provideViewModel(
                viewModelFactory: MainViewModelFactory
        ): MainViewModel {
            return ViewModelProviders
                    .of(act, viewModelFactory)
                    .get(MainViewModel::class.java)
        }
    
    0 讨论(0)
  • 2021-02-07 07:44

    Try with below code :

    @Provides
    @Singleton
    fun provideRepository(): Repository {
        return Repository(DataSource())
    }
    
    0 讨论(0)
  • 2021-02-07 07:54

    Refer to a repo I created when I was learning dagger+kotlin

    Essentially you need a ViewModelFactory instance to the UI layer, you use that to create a 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)
            }
        }
    }
    

    Your ViewModelModule should look like (this is where you store all 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
    }
    

    Then create a dagger map key

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

    Then on your UI layer, inject the factory and instantiate your viewmodel using ViewModelProviders

    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, { ... })
    
        }
    
    0 讨论(0)
  • 2021-02-07 07:57

    You can enable constructor injection for your ViewModels. You can check out Google samples to see how to do it in Java. (Update: looks like they converted the project to Kotlin so this URL no longer works)

    Here is how to do a similar thing in Kotlin:

    Add ViewModelKey annotation:

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

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

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

    Register your ViewModelModule in your component

    Inject ViewModelProvider.Factory in your activity:

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

    Pass your modelFactory to each ViewModelProviders.of method:

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

    Here is the sample commit which contains all of the required changes: Support constructor injection for view models

    0 讨论(0)
提交回复
热议问题