Android LiveData - how to reuse the same ViewModel on different activities?

31,422

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 ViewModelStores.

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 ViewModels 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.

enter image description here

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)
Share:
31,422
user1209216
Author by

user1209216

Updated on July 09, 2022

Comments

  • user1209216
    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
    EpicPandaForce over 6 years
    If you're already caching data in a singleton data layer, then what's the point of ViewModel?
  • Saeed Masoumi
    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
    user1209216 over 6 years
    Well, 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
    Saeed Masoumi over 6 years
    @user1209216 Yes, why not see this article for details developer.android.com/topic/libraries/architecture/…
  • Morteza Rastgoo
    Morteza Rastgoo almost 5 years
    How to achieve this if we use DaggerViewModelFactory?
  • Samuel
    Samuel over 4 years
    I tried this solution but it does not work. Trying to update a TextView in a fragment from a second activity.
  • Samuel
    Samuel over 4 years
    Can you take a look at my submission: stackoverflow.com/questions/59887014/…
  • sum20156
    sum20156 over 3 years
    this should be an accepted answer..without violating the android architecture pattern
  • Captain Allergy
    Captain Allergy about 3 years
    Works like a charm! Thank you
  • alierdogan7
    alierdogan7 about 3 years
    This approach is recommended also here in this official tutorial: LiveData in repositories: To avoid leaking ViewModels and callback hell, repositories can be observed