19

There are two Fragments: ParentFragment and ChildFragment. ChildFragment has been added to a view of the ParentFragment.

Now using Dagger2 for Android has the ParentFragmentModule with a method:

@Provides
fun provideViewModel(fragment: ParentFragment, myViewModelFactory: MyViewModelFactory): MyViewModel {
    return ViewModelProviders.of(fragment, myViewModelFactory).get(MyViewModelImpl::class.java)
}

Where MyViewModelFactory, MyViewModel, MyViewModelImpl are simple ViewModel logic created in the app.

And the ChildFragmentModule has the method:

@Provides
fun provideViewModel(fragment: ChildFragment, myViewModelFactory: MyViewModelFactory): MyViewModel {
    return ViewModelProviders.of(fragment, myViewModelFactory).get(MyViewModelImpl::class.java)
}

This obviously is creating two separate instances of the ViewModel as they receive two different fragment instances.

How do we make it return the same instance so that the data can be shared between both the Parent and Child fragments?

I have tried passing the ParentFragment instead of ChildFragment in the ChildFragmentModule, but that leads to Dagger dependancy injection error.

Abu Yousuf
  • 5,729
  • 3
  • 31
  • 50
mhtmalpani
  • 913
  • 1
  • 8
  • 11

5 Answers5

26

Create your ViewModel with Activity scope. Then all Fragment within that Activity will get same ViewModel instance.

Check official ViewModelProviders reference. You can create ViewModel with both Activity and Fragment scope.

ViewModelProvider of (FragmentActivity activity)

Creates a ViewModelProvider, which retains ViewModels while a scope of given Activity is alive. More detailed explanation is in ViewModel.

and

ViewModelProvider of (Fragment fragment)

Creates a ViewModelProvider, which retains ViewModels while a scope of given fragment is alive. More detailed explanation is in ViewModel.

Sample code for creating ViewModel

From Activity:

 movieListViewModel = ViewModelProviders.of(this).get(MovieListViewModel.class);

From Fragment:

 movieListViewModel = ViewModelProviders.of(getActivity()).get(MovieListViewModel.class);
Abu Yousuf
  • 5,729
  • 3
  • 31
  • 50
  • 4
    Thank you. This works smooth! Just for the knowledge, the Parent and Child fragment scopes are alive at the same time. How can achieve the same thing without using the Activity? – mhtmalpani May 26 '18 at 14:46
  • How to share movieListViewModel to different module? Is that possible – Fazal Hussain Aug 28 '18 at 12:35
  • 23
    What about single activity approach? It won't be good to hold all the shared view models linked to one activity. – A. Kazarovets Oct 30 '18 at 12:58
  • just a modification to avoid NullPointerException movieListViewModel = ViewModelProviders.of(requireActivity()).get(MovieListViewModel.class); – Zain Jan 16 '19 at 16:46
  • To add on this is it possible to get the view model for a separate activity to send data to the child fragment of the MainActivity? – Victor Okech Dec 26 '20 at 10:32
20

Using Fragment-ktx we can do as In **ParentFragment**

 private val viewModel: DemoViewModel by viewModels()

And

In ChildFragment

 private val viewModel: DemoViewModel by viewModels(
    ownerProducer = { requireParentFragment() }
)

Doing this we can get same instance of ViewModel in Parent Fragment and ChildFragment

add dependencies in app -> build.gralde

implementation 'androidx.fragment:fragment-ktx:1.1.0
Alok Mishra
  • 1,904
  • 1
  • 17
  • 18
3

Implement fragment-ktx in your app -> build.gradle:

implementation 'androidx.fragment:fragment-ktx:1.2.5

If you are using Navigation Component (https://developer.android.com/guide/navigation)

In the parentFragment, you can get the viewModel like this:

private val viewModel by viewModels<DemoViewModel>()

And then when you need that viewModel in the childFragment you can get it this way:

private val viewModel by viewModels<DemoViewModel>({requireGrandParentFragment()})

the requireGrandParentFragment() is a custom extension of Fragment:

fun Fragment.requireGrandParentFragment() = this.requireParentFragment().requireParentFragment()

If you are not using Navigation Component

you can access it like this:

private val viewModel by viewModels<DemoViewModel>({requireParentFragment()})
Ndriçim Sadiku
  • 151
  • 1
  • 5
2

I have got a same problem tried above all solution but not working in my scenario come up with a other solution using requireParentFragment() return NavHostFragment in child fragment solve it by using

private val viewModel: childViewModel by viewModels(
            ownerProducer = { requireParentFragment().childFragmentManager.primaryNavigationFragment!! }
    )

in Parent Fragmet use this

private val viewModel: MyOrdersVM by viewModels()
Abdul Wahab
  • 411
  • 6
  • 15
0

ViewModelProvider constructor has owner paramter

ViewModelProvider(
    ViewModelStoreOwner owner,
    ViewModelProvider.Factory factory
)

Reference on the official docs.

This parameters is responsible for scope where ViewModel instance would be remain the same. You need to pass the same reference on ViewModelStoreOwner to have the same instance of ViewModel

In case of sharing ViewModel among two fragments that depends on each other as parent - child, you need to get ViewModel instance like this:

At parent fragment:

viewModel = ViewModelProvider(
  this, 
  viewModelFactory
).get(ProfileViewModel::class.java) 

At child fragment:

viewModel = ViewModelProvider(
  requireParentFragment(), 
  viewModelFactory
).get(ProfileViewModel::class.java) 

This resource might be helpful to learn more.

Joe Dow
  • 583
  • 1
  • 3
  • 12