Android LiveData - how to reuse the same ViewModel on different activities?
Solution 1
When you call ViewModelProviders.of(this)
, you actually create/retain a ViewModelStore
which is bound to this
, so different Activities have different ViewModelStore
and each ViewModelStore
creates a different instance of a ViewModel
using a given factory, so you can not have the same instance of a ViewModel
in different ViewModelStore
s.
But you can achieve this by passing a single instance of a custom ViewModel factory which acts as a singleton factory, so it will always pass the same instance of your ViewModel
among different activities.
For example:
public class SingletonNameViewModelFactory extends ViewModelProvider.NewInstanceFactory {
NameViewModel t;
public SingletonNameViewModelFactory() {
// t = provideNameViewModelSomeHowUsingDependencyInjection
}
@Override
public NameViewModel create(Class<NameViewModel> modelClass) {
return t;
}
}
So what you need is to make SingletonNameViewModelFactory
singleton (e.g. using Dagger) and use it like this:
mModel = ViewModelProviders.of(this,myFactory).get(NameViewModel.class);
Note:
Preserving ViewModel
s among different scopes is an anti-pattern. It's highly recommended to preserve your data-layer objects (e.g. make your DataSource or Repository singleton) and retain your data between different scopes (Activities).
Read this article for details.
Solution 2
When getting the view model using the ViewModelProviders you are passing as lifecycle owner the MainActivity, this will give the view model for the that activity. In the second activity you will get a different instance of that ViewModel, this time for your second Activity. The second model will have a second live data.
What you can do is maintain the data in a different layer, like a repository, which may be a singleton and that way you can use the same view model.
public class NameViewModel extends ViewModel {
// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = DataRepository.getInstance().getCurrentName();
}
return mCurrentName;
}
}
//SingleTon
public class DataRepository
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<>();
}
return mCurrentName;
}
//Singleton code
...
}
Solution 3
Simply create the instance of your ViewModel, in this case NameViewModel
Your ViewModel Factory be like
class ViewModelFactory : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>) =
with(modelClass){
when {
isAssignableFrom(NameViewModel::class.java) -> NameViewModel.getInstance()
else -> throw IllegalArgumentException("Unknown viewModel class $modelClass")
}
} as T
companion object {
private var instance : ViewModelFactory? = null
fun getInstance() =
instance ?: synchronized(ViewModelFactory::class.java){
instance ?: ViewModelFactory().also { instance = it }
}
}
}
And your ViewModel
class NameViewModel : ViewModel() {
//your liveData objects and many more...
companion object {
private var instance : NameViewModel? = null
fun getInstance() =
instance ?: synchronized(NameViewModel::class.java){
instance ?: NameViewModel().also { instance = it }
}
}
}
Now you can use ViewModelProviders
to get the same instance of your ViewModel to use in any activity
ViewModelProviders.of(this, ViewModelFactory.getInstance()).get(NameViewModel::class.java)
OR
create an extension function for easier access
fun <T : ViewModel> AppCompatActivity.getViewModel(viewModelClass: Class<T>) =
ViewModelProviders.of(this, ViewModelFactory.getInstance()).get(viewModelClass)
user1209216
Updated on July 09, 2022Comments
-
user1209216 almost 2 years
Example ViewModel:
public class NameViewModel extends ViewModel { // Create a LiveData with a String private MutableLiveData<String> mCurrentName; public MutableLiveData<String> getCurrentName() { if (mCurrentName == null) { mCurrentName = new MutableLiveData<>(); } return mCurrentName; } }
Main activity:
mModel = ViewModelProviders.of(this).get(NameViewModel.class); // Create the observer which updates the UI. final Observer<String> nameObserver = textView::setText; // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. mModel.getCurrentName().observe(this, nameObserver);
I want to call
mModel.getCurrentName().setValue(anotherName);
in second activity and make MainActivity receive changes. Is that possible? -
EpicPandaForce over 6 yearsIf you're already caching data in a singleton data layer, then what's the point of ViewModel?
-
Saeed Masoumi over 6 years@EpicPandaForce, I mean no matter how your data-layer acts,
ViewModel
must notify about your data changes somehow, So caching your data-layer is one way to maintain your data in different scopes. -
user1209216 over 6 yearsWell, yes I think you are right, I should not try to do that. But, what about fragment, is that good practice to make child fragments observe the same view model? For example
ViewModelProviders.of(getActivity()).get(NameViewModel.class)
inside fragment. -
Saeed Masoumi over 6 years@user1209216 Yes, why not see this article for details developer.android.com/topic/libraries/architecture/…
-
Morteza Rastgoo almost 5 yearsHow to achieve this if we use DaggerViewModelFactory?
-
Samuel over 4 yearsI tried this solution but it does not work. Trying to update a TextView in a fragment from a second activity.
-
Samuel over 4 yearsCan you take a look at my submission: stackoverflow.com/questions/59887014/…
-
sum20156 over 3 yearsthis should be an accepted answer..without violating the android architecture pattern
-
Captain Allergy about 3 yearsWorks like a charm! Thank you
-
alierdogan7 about 3 yearsThis approach is recommended also here in this official tutorial:
LiveData in repositories: To avoid leaking ViewModels and callback hell, repositories can be observed