23

I have a complex screen in my project which I'm breaking in more than one fragment. I'm trying to follow the MVVM architecture for these classes, so which fragment has its own ViewModel and Contract class.

The issue is that all the ViewModels needs the same object instance (let's call it Book) to do Room transactions.

Does it have a correct way to share data (or LiveData) between ViewModels? I know the concept of Shared ViewModel but I don't know if I can apply it to this case. I also thought about using MediatorLiveData but didn't get a good approach on it too.

I'm thinking about having a class (let's call BookObservableProvider) with a LiveData<Book> (or Rx Subject<Book>) where each ViewModel injects the same instance and load/update always the same value.

Is it a good approach?

Phantômaxx
  • 37,901
  • 21
  • 84
  • 115
Igor Escodro
  • 1,307
  • 2
  • 16
  • 30
  • Use single ViewModel per activity, so your fragments will automatically share the same viewmodel. – Roshaan Farrukh Feb 08 '19 at 08:11
  • @RoshaanFarrukh The intention of split the classes is to not inflate the ViewModel which will have even more methods in the future. – Igor Escodro Feb 08 '19 at 08:55
  • Have you found a good way? Currently I'm fetching data through repository in one of the SharedViewModels, then I'm observing this data in my activity. In the observe function call I'm also updating it to the other ViewModel instances, which I'm also accessing in all of my fragments. – nulldroid Mar 18 '20 at 13:53
  • @IgorEscodro I think I have the same question here: https://stackoverflow.com/q/60796010/8258130 Have u solved yours? Pls share – iadcialim24 Mar 22 '20 at 04:48
  • @nulldroid Ya. that could work too! can u check my post here if this crosses ur mind https://stackoverflow.com/q/60796010/8258130 – iadcialim24 Mar 22 '20 at 04:49

4 Answers4

0

You should share those data between fragments/activities (maybe using Intents for activities) , and than handle those data by the other ViewModel

0

The answer is as usual, it depends. If the reason behind your question is Room access, then it is recmended to have a DataRepository class that handles all Database access and you just pass that repository singleton to each AndroidViewModel.

mRepository = ((MainApp) application).getRepository();

In MainApp:

public DataRepository getRepository() {
    return DataRepository.getInstance(getDatabase(), mAppExecutors);
}

And the Repository:

public class DataRepository {

    private static DataRepository sInstance;
    private MediatorLiveData<String> mObservableString;

    private DataRepository(final AppDatabase database, final AppExecutors executors) {
        mObservableString.addSource(database.myDao().loadString(),
            mString -> {
                if (database.getDatabaseCreated().getValue() != null) {
                    mObservableString.postValue(mString);
                }
            });
    }

    public static DataRepository getInstance(final AppDatabase database, final AppExecutors executors) {
        if (sInstance == null) {
            synchronized (DataRepository.class) {
                if (sInstance == null) {
                    sInstance = new DataRepository(database, executors);
                }
            }
        }
        return sInstance;
    }
    
    // and then your access methods
    
    public LiveData<String> getString() {
        return mObservableString;
    }

In the repository it is recommended to have a MediatorLivedata if you want to change the reference (source). Otherwise a normal LiveData does the job.

Regarding ViewModels:

In theory each fragment gets it's own Viewmodel. And if you get it by using requireActivity() as reference, you can get each ViewModel everywhere and have it therefore shared.

As an example:

    viewModelA = new ViewModelProvider(requireActivity()).get(ViewModelA.class);
    viewModelB = new ViewModelProvider(requireActivity()).get(ViewModelB.class);

This you could call in every Fragment and get the same ViewModel instances. If the DataRepository setup seems overkill to you, make one ViewModel with Room access and load it from every Fragment.

Tobi
  • 858
  • 7
  • 15
0

I am facing the same issue. But if you don't have different view models for the various fragments or the design does not necessitate using different view models for the various fragments you can just share one fragment between the entire activity( all the other fragment) and they will all share the same data instances.

follow this link for more https://developer.android.com/guide/fragments/communicate

what you need to do is make sure all the fragments initiate the view model(main view model) with the same context.

public class FilterFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
            // Update the selected filters UI
        });
    }
}

take note of this

viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);

requireActivity() makes sure all fragments call the context of the host activity.

you can't share data with activities this way though since the view model instance is destroyed when the activity is destroyed

DmitryArc
  • 4,757
  • 2
  • 37
  • 42
The Codepreneur
  • 236
  • 3
  • 12
-1

In my personal opinion your approach is not bad for this situation, but if want to try something else, I can advise you RxBus method. Here is a great article about it. With this approach you can simply publish data in activity, which holds fragments, and then listen to this particular event in all your fragments.

Something like :

//Activity
RxBus.publish(RxEvent.EventOnBookProvide(bookObject)

and

//Fragment
RxBus.listen(RxEvent.EventOnBookProvide::class.java).subscribe {
        useObject(it)
    }

And don't forget to use Disposable and .dispose() it in onDestroy() if using Activity and onDestroyView() if using fragment.

Ruslan
  • 355
  • 1
  • 14