I'm using Jetpack Navigation and Hilt in my project and I want to share ViewModel between only two Fragments, like this:
Fragment A: use ViewModel A
In Fragment A navigate to Fragment B: use ViewModel B
In Fragment B navigate to Fragment C: use instance of ViewModel B
If from C back to B, back to A then ViewModel B will be destroyed.
How to config ViewModel B like that?
UPDATE: I found a way to use Hilt Custom Scope, but I don't know how to implement it yet.
Thanks in advance.
You can use activityViewModels() with the ViewModel following the host activity's lifecycle.
class BFragment: Fragment() {
// Using the activityViewModels() Kotlin property delegate from the
// fragment-ktx artifact to retrieve the ViewModel in the activity scope
private val viewModel: BViewModel by activityViewModels()
}
class CFragment : Fragment() {
private val viewModel: CViewModel by viewModels()
private val shareViewModel: BViewModel by activityViewModels()
}
Or if fragment C is a child fragment of B, you can customize the viewmodel's lifecycle as follows:
class BFragment: Fragment() {
// Using the viewModels() Kotlin property delegate from the fragment-ktx
// artifact to retrieve the ViewModel
private val viewModel: BViewModel by viewModels()
}
class CFragment: Fragment() {
// Using the viewModels() Kotlin property delegate from the fragment-ktx
// artifact to retrieve the ViewModel using the parent fragment's scope
private val shareViewModel: BViewModel by viewModels({requireParentFragment()})
private val viewModel: CViewModel by viewModels()
}
More information: Communicating with fragments
You can use navGraphViewModels. Create a nested graph with Fragment B & C, both will share the same navGraphViewModel
<navigation android:id="#+id/nav_graph_a"
app:startDestination="#id/dest_a">
<fragment android:id="#+id/dest_a"/>
<navigation android:id="#+id/nav_graph_b_c"
app:startDestination="#id/dest_b">
<fragment android:id="#+id/dest_b"/>
<fragment android:id="#+id/dest_c"/>
</navigation>
</navigation>
https://developer.android.com/guide/navigation/navigation-programmatic
https://medium.com/sprinthub/a-step-by-step-guide-on-how-to-use-nav-graph-scoped-viewmodels-cf82de4545ed
I have a parent Fragment A with Fragment B and Fragment C inside a ViewPager. I want to share Fragment's A ViewModel within Fragment B and Fragment C.
This was my approach which seemed intuitive at first:
In parent Fragment A which contains ViewPager, I create my viewModel like this:
val viewModel by viewModels<SharedViewModel>()
...
#HiltViewModel
class SharedViewModel #Inject constructor(
stateHandle: SavedStateHandle
) {
val myArgument = stateHandle.get<Boolean>("myArgument")
...
}
My ViewPager Fragment's B and C are trying to access same SharedViewModel instance like this:
private val sharedViewModel by viewModels<SharedViewModel>(
ownerProducer = { requireParentFragment() }
)
Everything works fine, except when the process is destroyed, my savedStateHandle in the SharedViewModel does not retain arguments passed by navigation component. Doing this in SharedViewModel returns null and savedStateHandle is empty.
val myArgument = stateHandle.get<Boolean>("myArgument")
Does someone know why is this happening? Using activityViewModels seems to fix this, but I do not fully understand why.
Iam trying to create shared view model between activity and the fragments.
In the activity :
val viewModel: SharedViewModel by viewModels()
And in fragments:
val viewModel: SharedViewModel by navGraphViewModels(R.id.activity_nav_graph) {
defaultViewModelProviderFactory
}
But 2 instances currently created one on the activity and one in the fragments
in your fragment should be
private val viewModel: SharedViewModel by activityViewModels()
I have created an instance of ViewModel in MainActivity and setup an observer. I want the observed data into one of the fragments of MainActivity's ViewPager. How can I get the required LiveData into the fragment.
Using AndroidX extension delegate
In the MainActivity:
private val activityViewModel: SomeViewModel by viewModels()
In the Fragement
private val activityViewModel: SomeViewModel by activityViewModels()
With ViewModelFactory, put the ViewModelFactory intance into closure
private val activityViewModel: SomeViewModel by viewModels{ viewModelFactory }
private val activityViewModel: SomeViewModel by activityViewModels{ viewModelFactory}
you can use shared ViewModel
Shared ViewModel between activity and fragments
for this you can define an object from activity's viewModel in your fragment with activity as viewModel's owner and apply changes on that's variables.
I have my Activity MainActivity.kt .
And and one ViewModel MainActivityViewModel.kt
And I want to observe my live data to my 3 different fragments.
class MainActivity{
lateinit var mainActivityViewModel: MainActivityViewModel
...
mainActivityViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel::class.java)
}
class MainFragmentOne{
lateinit var mainActivityViewModel: MainActivityViewModel
...
mainActivityViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel::class.java)
}
But my observer only work on activity not on the fragments.
Hey there you are doing everything greate except one thing you should use requireActivity() instead on this in your fragment class.
Make sure your all fragment are attached to your viewModel holding Activity.
class MainActivity{
lateinit var mainActivityViewModel: MainActivityViewModel
...
mainActivityViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel::class.java)
}
class MainFragmentOne{
lateinit var mainActivityViewModel: MainActivityViewModel
...
mainActivityViewModel = ViewModelProviders.of(requireActivity(), viewModelFactory).get(MainActivityViewModel::class.java)
}
This will help you solve your issue.
For further detail view this.
The ViewModelProviders.of has 2 different constructors:
of(Fragment fragment, ViewModelProvider.Factory factory)
Creates a ViewModelProvider, which retains ViewModels while a scope of
given fragment is alive.
of(FragmentActivity activity, ViewModelProvider.Factory factory)
Creates a ViewModelProvider, which retains ViewModels while a scope of
given Activity is alive.
Basically when you used this as the first parameter in your activity, you passed the context of the activity and created a viewmodel that will be alive in the scope of the activity, however your second this is the context to your fragment, meaning that the second ViewModel will be alive as long as your fragment is alive (only one fragment).
What instead you should be doing in your fragment is using the context of the activity, since activity is always alive when fragments are attached and swapped. You should change your fragments to:
class MainFragmentOne{
lateinit var mainActivityViewModel: MainActivityViewModel
...
mainActivityViewModel = ViewModelProviders.of(activity!!, viewModelFactory).get(MainActivityViewModel::class.java)
}
or you can use the requireActivity() method that was the previous answer.
To achieve what you are trying to do, you need three things. An activity/fragment that will post the value to the ViewModel, a ViewModel, and an activity/fragment that will retrieve the data from the ViewModel. Lets say your data is stored in an ArrayList, and you want to update and retrieve it from different fragments.
First, we have to implement a ViewModel. It contains the data you want to share between your activities/fragments. You declare the MutableLiveData as an ArrayList then initialize it.
class testviewmodel : ViewModel() {
val list: MutableLiveData<ArrayList<String>> = MutableLiveData()
init {
list.value = arrayListOf()
}
}
Our next step is to access and update the ArrayList using your activity:
val viewmodel = ViewModelProviders.of(this).get(testviewmodel::class.java)
// update the array in Viewmodel
viewmodel.list.postValue(yourarray)
If you are using a Fragment to update it, use this:
activity?.let {
val viewmodel = ViewModelProviders.of(it).get(testviewmodel::class.java)
// update the array in Viewmodel
viewmodel.list.postValue(yourarray)
}
Finally, to retrieve the data from the ViewModel in a fragment, put this inside your onViewCreated:
activity?.let {
val viewmodel = ViewModelProviders.of(it).get(Dbviewmodel::class.java)
observeInput(viewmodel)
}
Put this outside of your onViewCreated:
private fun observeInput(viewmodel: testviewmodel ) {
viewmodel.list.observe(viewLifecycleOwner, Observer {
it?.let {
if (it.size > 5) {
pos = it[5]
//grab it
Toast.makeText(context,pos,Toast.LENGTH_LONG).show()
//display grabbed data
}
}
})
}
Take a look at this docs for more information about ViewModels
Good Luck! I hope this helps :)
That's because you are using the fragment 'this' instance, and not the activity.
Replace
mainActivityViewModel = ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel::class.java)
With
activity?.let { mainActivityViewModel = ViewModelProviders.of(it, viewModelFactory).get(MainActivityViewModel::class.java) }