In kotlin how to use ViewModel With ViewModelProvider.AndroidViewModelFactory? - android

In my current project I use the next line:
mViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
For instance a ViewModel but in https://developer.android.com/reference/android/arch/lifecycle/ViewModelProviders.html#ViewModelProviders() recommend use ViewModelProvider.AndroidViewModelFactory because ViewModelProviders() was deprecated in API level 1.1.0.
any idea for this purpose?

If you had a simple ViewModel extending AndroidViewModel without any additional constructor parameters, its as follows
Extend AndroidViewModel without any additional constructor
parameters
class FooViewModel(application: Application) : AndroidViewModel(application) {}
Create View Model in Activity
val viewModel = ViewModelProvider(this).get(FooViewModel::class.java)
But if you had a ViewModel extending AndroidViewModel with any additional constructor parameters, its as follows
Extend AndroidViewModel with any additional constructor
parameters
class FooViewModel(application: Application, foo: Foo) : AndroidViewModel(application) {}
Create a new view model factory extending ViewModelProvider.AndroidViewModelFactory
class FooViewModelFactory(val application: Application, val foo: Foo): ViewModelProvider.AndroidViewModelFactory(application) {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FooViewModel(
application, foo
) as T
}
}
Create View Model in Activity
val viewModel = ViewModelProvider(this, FooViewModelFactory(application, foo)).get(FooViewModel::class.java)

EDIT: The original question is now irrelevant, as you should no longer use the ViewModelProviders utility class. Instead, you should create a ViewModelProvider instance like so:
val viewModel = ViewModelProvider(thisFragment).get(MyViewModel::class.java)
Original answer below.
ViewModelProviders is just a utility class with static methods, there's no need to instantiate it (there are no instance methods in it anyway), so the constructor being deprecated shouldn't be a concern.
The way you use it is by calling its appropriate of method for your use case, passing in a Fragment or Activity, and then calling get on the ViewModelProvider it returns:
val viewModel = ViewModelProviders.of(thisFragment).get(MyViewModel::class.java)
If you don't provide your own factory in the second parameter of the of method, AndroidViewModelFactory will be used by default. This implementation can either create ViewModel subclasses that have no constructor parameters, or ones that extend AndroidViewModel, like such:
class MyViewModel(application: Application) : AndroidViewModel(application) {
// use application
}

you can try this code
ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(UserViewModel::class.java)

You can use AndroidViewModelFactory like this:
mViewModel = ViewModelProvider(this,
ViewModelProvider.AndroidViewModelFactory(application))
.get(MainViewModel::class.java)

So, it is quite simple:
// This is the deprecated way to create a viewModel.
viewModel = ViewModelProviders.of(owner).get(MyViewModel::class.java)
//This is the deprecated way to create a viewModel with a factory.
viewModel = ViewModelProviders.of(owner, mViewModelFactory).get(MyViewModel::class.java)
// This is the new way to create a viewModel.
viewModel = ViewModelProvider(owner).get(MyViewModel::class.java)
//This is the new way to create a viewModel with a factory.
viewModel = ViewModelProvider(owner, mViewModelFactory).get(MyViewModel::class.java)

Open build.gradle(Module:~.app)
Edit appcompat version to 1.3.0-alpha02
implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'

Related

Is it possible to inject a conditional class( based on a parameter from the previous fragment) into a view model?

I'm using Hilt. I want to inject a subclass of Foo into my hilt view model.
All subclasses of Foo depend on different class that is already using an #Inject constructor and can be injected into view models, activities, etc. But not into my subclass, so I'm using EntryPoints to inject them.
Also, which subclass gets injected depends upon a property I'm getting from the previous fragment via the SavedStateHandle Hilt provides the view model.
Is it possible to create a Factory (or another solution) that somehow gets the param from the previous fragment and injects the correct Foo object?
I have a solution that doesn't use Hilt to get the Foo object, it just instantiates the right object using a conditional on the param. This solution is not testable and I don't like it.
// in the view model I would like to do this
//
// #Inject
// lateinit var thatFooINeed: Foo
//
// But thatFooINeed could be the Foo with Dependency1 or Dependency2
// It depends on the param sent from the previous fragment
interface Foo {
fun doThis()
fun doThat()
}
class Bar1(context: Context): Foo {
private val dependencyInterface =
EntryPoints.get(context, DependencyInterface::class.java)
val dependency1: Dependency1 = dependencyInterface.getDependency1()
// override doThis() and doThat() and use ^ dependency
...
}
class Bar2(context: Context): Foo {
private val dependencyInterface =
EntryPoints.get(context, DependencyInterface::class.java)
val dependency2: Dependency2 = dependencyInterface.getDependency2()
// override doThis() and doThat() and use ^ dependency
...
}
#EntryPoint
#InstallIn(SingletonComponent::class)
interface DependenciesInterface {
fun getDependency1(): Dependency1
fun getDependency2(): Dependency2
}
class Dependency1 #Inject constructor(val yetAnotherDep: ButWhosCounting)
class Dependency2 #Inject constructor(val yetAnotherDep: ButWhosCounting)```
You have to adopt #AssistedInject, assisted injection will work as factory for your case. Inject the factory and then use the factory to lazily create the instance of the interface.

Cannot create an instance of class com.example.event.ui.main.EventsViewModel

I try to create instance of ViewModel, but get an error.
java.lang.RuntimeException: Cannot create an instance of class com.example.event.ui.main.EventsViewModel
my code here to get instance:
viewModel = ViewModelProvider(this).get(EventsViewModel::class.java)
view model class
class EventsViewModel #Inject constructor(private val eventApi: EventApi): BaseViewModel() {
val getEventsData = MutableLiveData<Response<List<Event>>>()
fun getEventsData() {
uiScope.launch {
getEventsData.value = eventApi.getEvents()
}
}
}
Here, your ViewModelProvider is not aware of how to create EventsViewModel. If your ViewModel constructor was an empty constructor without any dependencies, it would work. However, if there is dependency or a parameter in your ViewModel constructor you need to provided a custom ViewModelProviderFactory.
If you want to check how/why custom view model factory is created you can check this codelab:
https://developer.android.com/codelabs/kotlin-android-training-view-model#7
If you want to employ dagger for injecting dependencies into your ViewModel, you can find an example in my sample repo:
https://github.com/hakanbagci/truemvvm

Android Dagger 2 ViewModel injection

I am using Dagger 2 with Kotlin. I`ve provided a viewmodel(CarViewModel) injection on the following way. Everywhere is written that it has to be done via ViewModelFactory injection. I want to ask if my way to directly inject viewmodel is correct? Here is my AppModule:
#Module
class AppModule {
#Provides
#Singleton
fun getContext(application: Application): Context = application.applicationContext
#Provides
#Singleton
fun getDb(context: Context): MyDatabase = MyDatabase.getInstance(context)
#Provides
fun injectViewModel(application: Application): CarViewModel=
ViewModelProvider.AndroidViewModelFactory.getInstance(application).create(CarViewModel::class.java)
}
Than injected in activity:
#Inject
lateinit var carViewModel: CarViewModel
No, your way of injecting a view model is not quite correct. The main reason is that the view model isn't associated with an activity, so it won't be reused when the activity is recreated, instead it will be recreated as well. The proper way of creating a view model is using ViewModelProvider, not the ViewModelProvider.Factory directly:
// `this` refers to the activity or fragment
viewModel = ViewModelProviders.of(this)[MyViewModel::class.java]
or:
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
if you use the newest alpha version (ViewModelProviders.of() is deprecated).
If you want to inject a view model and also be able to inject into view model (using constructor injection), you have to create your implementation of the ViewModelProvider.Factory interface and use it to create view models with non-empty constructors.
you only have to inject viewmodel factory & bind viewmodel and then you can obtain view model via viewmodel factory.
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val carViewModel: CarViewModel by viewModels{viewModelFactory}

How to inject a ViewModel with Koin in Kotlin?

How do we inject ViewModel with dependency using Koin?
So For Example I have a ViewModel thats like this:
class SomeViewModel(val someDependency: SomeDependency, val anotherDependency: AnotherDependency): ViewModel()
Now the official docs here, states that to provide a ViewModel we could do something like:
val myModule : Module = applicationContext {
// ViewModel instance of MyViewModel
// get() will resolve Repository instance
viewModel { SomeViewModel(get(), get()) }
// Single instance of SomeDependency
single<SomeDependency> { SomeDependency() }
// Single instance of AnotherDependency
single<AnotherDependency> { AnotherDependency() }
}
Then to inject it, we can do something like:
class MyActivity : AppCompatActivity(){
// Lazy inject SomeViewModel
val model : SomeViewModel by viewModel()
override fun onCreate() {
super.onCreate()
// or also direct retrieve instance
val model : SomeViewModel= getViewModel()
}
}
The confusing part for me is that, normally you will need a ViewModelFactory to provide the ViewModel with Dependencies. Where is the ViewModelFactory here? is it no longer needed?
Hello viewmodel() it's a Domain Specific Language (DSL) keywords that help creating a ViewModel instance.
At this [link][1] of official documentation you can find more info
The viewModel keyword helps declaring a factory instance of ViewModel.
This instance will be handled by internal ViewModelFactory and
reattach ViewModel instance if needed.
this example of koin version 2.0 [1]: https://insert-koin.io/docs/2.0/documentation/koin-android/index.html#_viewmodel_dsl
// Given some classes
class Controller(val service : BusinessService)
class BusinessService()
// just declare it
val myModule = module {
single { Controller(get()) }
single { BusinessService() }
}

how to inject dependency outside activity or fragment in Kodein or Koin?

want to initialize interface in a non activity or fragment class with Kodein DI Android
sample shows only hot to use Kodein inside activity, but not on the other parts
class MainViewModel() : KodeinAware{
override val kodein by closestKodein()
val repository : Repository by instance()
}
in activity it works, but in other classes it shows error.
I want to initialize interface inside another class
closestKodein only works in Android Context aware classes (such as fragments & activities).
To use it outside of these classes, you need an Android context.
The android documentation clearly states:
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
[...]
If the ViewModel needs the Application context, for example to find a system service, it can extend the AndroidViewModel class and have a constructor that receives the Application in the constructor, since Application class extends Context.
Therefore, to access Kodein from a ViewModel:
class MainViewModel(app: Application) : ApplicationViewModel(app), KodeinAware {
override val kodein = app.kodein
val repository : Repository by instance()
}
Simpy pass a context or activity as param
override val kodein by closestKodein(context)
More info https://kodein.org/Kodein-DI/?5.0/android#_getting_a_kodein_object
Use it in any place. appKodein is global function.
val dataLayer: DataLayer = appKodein().instance()
override val kodein by kodein(activity!!)
class ReportViewModel(context: Context):ViewModel() ,KodeinAware
{
override val kodein by kodein(context)
val reportRepository:ReportRepository by instance()
}
My answer

Categories

Resources