46

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?

ColdFire
  • 6,764
  • 6
  • 35
  • 51
user1209216
  • 7,404
  • 12
  • 60
  • 123
  • 3
    The "right" answer is that "if you want to share data between them, they shouldn't be different Activities, and you should be swapping out fragments instead". – EpicPandaForce Mar 19 '18 at 14:34
  • @EpicPandaForce perhaps, but that's not how the AndroidStudio master/detail template works, nor the Android Architecture Blueprints – Mark Jul 10 '18 at 06:09
  • @Mark that is a flaw of the Android Architecture Blueprints and the template, then. – EpicPandaForce Jan 25 '19 at 22:00
  • Please see this https://stackoverflow.com/questions/56521969/how-to-share-an-instance-of-livedata-in-android-app/56521970#56521970 – Levon Petrosyan Jun 10 '19 at 16:12

4 Answers4

57

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.

Saeed Masoumi
  • 8,746
  • 7
  • 57
  • 76
  • 3
    If you're already caching data in a singleton data layer, then what's the point of ViewModel? – EpicPandaForce Mar 19 '18 at 14:36
  • @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. – Saeed Masoumi Mar 19 '18 at 14:43
  • 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. – user1209216 Mar 20 '18 at 06:48
  • 1
    @user1209216 Yes, why not see this article for details https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing – Saeed Masoumi Mar 20 '18 at 09:19
27

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
...
}
  • 2
    This approach is recommended also [here](https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54) in this official tutorial: `LiveData in repositories: To avoid leaking ViewModels and callback hell, repositories can be observed` – alierdogan7 Jun 17 '21 at 09:43
4

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)
Amir Raza
  • 2,320
  • 1
  • 23
  • 32
  • I tried this solution but it does not work. Trying to update a TextView in a fragment from a second activity. – Samuel Jan 23 '20 at 20:49
1

ViewModel scope/lifecycle is tied to an activity simply because the ViewModelStoreOwner that you pass to the ViewModelProvider constructor happens to be the activity.

Since you get to provide the ViewModelStoreOwner, you can just as easily provide one that has a longer lifecycle, such as the application.

You can

  1. Provide your own subclass of Application and make it implement ViewModelStoreOwner (and have a ViewModelStore)
  2. In ViewModelProvider constructor calls, pass activity.application rather than activity.

This will cause the ViewModelProvider to interact with the application-level ViewModelStore, allowing you to create ViewModel instances that will have application scope.