31

I have a ViewModel named SharedViewModel:

public class SharedViewModel<T> extends ViewModel {

    private final MutableLiveData<T> selected = new MutableLiveData<>();


    public void select(T item) {
        selected.setValue(item);
    }

    public LiveData<T> getSelected() {
        return selected;
    }
}

I've implemented it based on SharedViewModel example on the Google's Arch ViewModel reference page:

https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing_data_between_fragments

It is very common that two or more fragments in an activity need to communicate with each other. This is never trivial as both fragments need to define some interface description and the owner activity must bind the two together. Moreover, both fragments must handle the case where the other fragment is not yet created or not visible.

I have two fragments, called ListFragment and DetailFragment.

Until now I used these two fragments inside an activity called MasterActivity, and everything worked well.

I got the ViewModel in ListFragment, selected the value to use it on DetailFragment.

mStepSelectorViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);

However, now, in certain cases, I need that ListFragment (a layout to a different device configuration) will be added to a different activity, called DetailActivity. Is there a way to do that similarly to the above example?

Hadas Kaminsky
  • 1,285
  • 1
  • 16
  • 39
alexpfx
  • 6,412
  • 12
  • 52
  • 88
  • 12
    You are fighting the framework. If it goes to a different activity, called DetailActivity, then have a ViewModel for your Detail Activity. ViewModels are not just “magic objects that can outlive a config change”. They are the glue between your views and your model. If you have a different activity, your view is now different, therefore it warrants a new view model. If you separate your concerns like google expects you to do, then the “repository” that has access to the data can/will be shared by these view models, but they are different entities. – Martin Marconcini Jun 19 '17 at 22:58
  • 3
    If you are passing to a new Activity, shouldn't you just go for `Parcelable` with your object? – OneCricketeer Jun 19 '17 at 22:59
  • Implement an interface to listen to what list item is selected in your activities includes ListFragment. And then in the ListFragment, get the interface in onAttach event. Pass the selected item to DetailFragment in MasterActivity using findFragmentByTag and pass it into DetailActivity in other activities. – sunghun Oct 01 '17 at 22:20
  • 1
    It appears Google is working on a solution. https://github.com/googlesamples/android-architecture-components/issues/29 and https://issuetracker.google.com/issues/64988610 – paul Oct 25 '17 at 21:53
  • 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

6 Answers6

23

A little late but you can accomplish this using a shared ViewModelStore. Fragments and activities implement the ViewModelStoreOwner interface. In those cases fragments have a store per instance and activities save it in a static member (I guess so it can survive configuration changes).

Getting back to the shared ViewModelStore, let say for example that you want it to be your Application instance. You need your application to implement ViewModelStoreOwner.

class MyApp: Application(), ViewModelStoreOwner {
    private val appViewModelStore: ViewModelStore by lazy {
        ViewModelStore()
    }

    override fun getViewModelStore(): ViewModelStore {
        return appViewModelStore
    }
}

Then in the cases when you know that you need to share ViewModels between activity boundaries you do something like this.

val viewModel = ViewModelProvider(myApp, viewModelFactory).get(CustomViewModel::class.java)

So now it will use the Store defined in your app. That way you can share ViewModels.

Very important. Because in this example the ViewModels live in your application instance they won't be destroyed when the fragment/activity that uses them gets destroyed. So you will have to link them to the lifecycle of the last fragment/activity that will use them, or manually destroy them.

mikehc
  • 999
  • 8
  • 22
  • Wow! I never though about this. Is there anything I should look after to if I do this approach? – Archie G. Quiñones Feb 27 '19 at 06:21
  • any gotchas I need to know? – Archie G. Quiñones Feb 27 '19 at 06:30
  • Just that depending on where you decide to have the `ViewModelStore` you will need to manually clear the store to avoid memory leaks. If you follow the example (store it in the App instance) in my answer you will need to do that. – mikehc Feb 27 '19 at 22:25
  • @mikehc thank you. I put ViewModelStore in App instance like you answer. I have two activities A and B. A is never destroyed (unless close app) and B is opened and closed frequently. So how I can clear ViewModelStore? I did in onDestroy method of B `(application as MyApplication).viewModelStore.clear()` but when I start B activity again viewModel doesn't work – leotesta Mar 14 '19 at 00:37
  • 1
    @leotesta it sounds like the problem might lay in your ViewModelFactory. But all of it depends on your implementation. I recommend you to create a new question with that issue in specific when you share a little bit of your implementation. – mikehc Mar 14 '19 at 18:20
  • @mikehc - how to use ViewModelStoreOwner of Fragment or Activity? My ViewModel class belongs to Fragment and not to Application class. – Mangesh Kadam Jun 26 '19 at 17:55
  • @MangeshKadam depending on what you use in `ViewModelProviders.of()` you will use the fragment or the activity's View Model Store. If you pass the fragment as parameter you will use the fragment store, and viceversa. Just a note, the ViewModels (by it self) don't have hard constraints about where can they be stored. They can be stored in any ViewModelStore, being that in the Fragment, Activity or any other class like Application. – mikehc Jun 26 '19 at 21:21
  • I have to create instance of Fragment to pass as parameter. Is this good option? – Mangesh Kadam Jun 27 '19 at 11:41
  • You usually pass the instance of the fragment or activity that's going to use the ViewModel. If you want to share between boundaries you need to use something else. – mikehc Jun 28 '19 at 17:18
  • @mikehc As a hint, you need to clear `ViewModelStore` at the end, so `ViewModel` will be cleared as well – Farshad Aug 21 '19 at 06:47
5

Well, I created a library for this purpose named Vita, You can share ViewModels between activities and even fragments with different host activity:

val myViewModel = vita.with(VitaOwner.Multiple(this)).getViewModel<MyViewModel>()

The created ViewModel in this way stay alive until its last LifeCycleOwner is destroyed.

Also you can create ViewModels with application scope:

val myViewModel = vita.with(VitaOwner.None).getViewModel<MyViewModel>()

And this type of ViewModel will be cleared when user closes app

Give it a try and kindly let me know your feedback: https://github.com/FarshadTahmasbi/Vita

Farshad
  • 3,074
  • 2
  • 30
  • 44
4

you can use factory to make viewmodel and this factor will return single object of view model.. As:

class ViewModelFactory() : ViewModelProvider.Factory {

override fun create(modelClass: Class): T {
    if (modelClass.isAssignableFrom(UserProfileViewModel::class.java)) {
    val key = "UserProfileViewModel"
    if(hashMapViewModel.containsKey(key)){
        return getViewModel(key) as T
    } else {
        addViewModel(key, UserProfileViewModel())
        return getViewModel(key) as T
    }
    }
    throw IllegalArgumentException("Unknown ViewModel class")
}

companion object {
    val hashMapViewModel = HashMap<String, ViewModel>()
    fun addViewModel(key: String, viewModel: ViewModel){
        hashMapViewModel.put(key, viewModel)
    }
    fun getViewModel(key: String): ViewModel? {
        return hashMapViewModel[key]
    }
}
}

In Activity:

viewModelFactory = Injection.provideViewModelFactory(this)

// Initialize Product View Model
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(
UserProfileViewModel::class.java)`

This will provide only single object of UserProfileViewModel which you can share between Activities.

Munish Thakur
  • 926
  • 1
  • 8
  • 25
  • What about this line "Injection.provideViewModelFactory(this)", how to make it work? – K.Os Dec 22 '17 at 18:59
  • Injection is custom class which is responsible to provide instance of ViewModelFactory. Here is class: object Injection { fun provideViewModelFactory(context: Context): ViewModelFactory { return ViewModelFactory() } } – Munish Thakur Dec 23 '17 at 18:12
  • See the comment here by herriojr https://github.com/googlesamples/android-architecture-components/issues/29 why this may not be the solution. – Henry Jul 10 '18 at 10:37
2

I think we still get confused with the MVVM framework on Android. For another activity, do not get confused because it must necessarily be the same, why?

This makes sense if it has the same logic (even if the logic could still be abstract in other useful classes), or if the view in the XML is almost identical.

Let's take a quick example:

I create a ViewModel called vmA, and an activity called A and I need the user's data, I will go to insert the repository in vmA of the User.

Now, I need another activity that needs to read user data, I create another ViewModel called vmB and in it I will call the user repository. As described, the repository is always the same.

Another way already suggested is to create N instances of the same ViewModel with the implementation of the Factory.

AlexPad
  • 10,364
  • 3
  • 38
  • 48
2

If you want a ViewModel that is shared by all your activities (as opposed to some), then why not store what you want stored in that ViewModel inside your Application class?

The trend presented at the last Google I/O seems to be to abandon the concept of Activities in favor of single-activity apps that have a lot of Fragments. ViewModels are the way to remove the great number of interfaces the activity of an interface formerly had to implement. Thus this aproach no longer makes for giant and unmaintainable activities.

Marcus Wolschon
  • 2,550
  • 2
  • 22
  • 28
  • I agree with you about reducing the number of activities in an application, although there are still scenarios where you need an activity. For instance settings activity (with settings fragment) and you want to share a viewmodel between two viewmodels after a setting change – Anton Makov Sep 25 '19 at 10:33
  • No, settings are persistent. ViewModels used outside settings should update themself as the underlying data changes. Settings don't change temporary data like a half-typed blog posting that is not sent or saved yet. They change permanent settings. That would be the job of Livedata returned by Room or by a wrapper around a property set – Marcus Wolschon Sep 25 '19 at 12:30
  • I understand your point of view, it makes sense to have a wrapper around the property set. The only thing I still don't understand is how do you connect between this wrapper and the viewmodel that needed to be changed. At the end you will need to use ViewModelProviders.of(<>, viewModelFactory) to get the same instance of viewmodel. – Anton Makov Sep 25 '19 at 12:45
  • Simple. https://developer.android.com/reference/android/content/SharedPreferences.html#registerOnSharedPreferenceChangeListener(android.content.SharedPreferences.OnSharedPreferenceChangeListener) – Marcus Wolschon Sep 26 '19 at 10:25
  • Thanks, I eventually ended with using this instaed https://android.jlelse.eu/android-observe-shared-preferences-as-livedata-27e25e7d3172 . I think if you end up needing to use different viewmodels from different activities there might be a flow in your application design. – Anton Makov Sep 26 '19 at 18:27
-3

Here's a link

Hope it helps you. O(∩_∩)O~

In addition:

1) The inspiration for the code came from smart pointer in c++.

2) It will be auto cleared when no activities or fragments references ShareViewModel. The ShareViewModel # onShareCleared() function will be called at the same time! You don't need to destroy them manually!

3) If you use dagger2 to inject the ViewModelFactory for share the viewmodel
between two activities (maybe three), Here's sample

hansey li
  • 17
  • 4