Inside Activity you can get a ViewModel scoped to that Activity like this:
val viewModel = ViewModelProviders.of(this)[ViewModel::class.java] //deprecated way
val viewModel = ViewModelProvider(this)[ViewModel::class.java]
//or
val viewModel by viewModels<ViewModel>()
Similarly in a Fragment you can get a ViewModel by
//scoped to parent Activity
val viewModel: ViewModel by activityViewModels()
//scoped to parent Fragment
val viewModel: ViewModel by viewModels({ requireParentFragment() })
//scoped to navigation graph
val viewModel: ViewModel by navGraphViewModels(R.id.login_graph)
Now if I try to access a ViewModel inside Activity which is scoped to a navigation graph then there is no helper method so I have to do
val viewModel by lazy {
ViewModelProvider(findNavController(R.id.nav_host_fragment)
.getViewModelStoreOwner(R.id.your_graph).viewModelStore,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(ViewModel::class.java)
}
Is there a helper method that I have missed? Do I have to access it via navigation graph scope? Can I just use Activity scope to access the same ViewModel?
Related
i have 2 fragments on the screen, how can i notify the 2nd that something happened on the 1st ?
now I am using this solution:
companion object {
private val onFullScreenPressedEvent = SingleLiveEvent<Boolean>()
val onFullScreenPressed: LiveData<Boolean> = onFullScreenPressedEvent
}
and observe onFullScreenPressed from second fragment
Your best bet would be to use a ViewModel tied to the Activity of the Fragments (so shared between fragments), and modify the liveData there.
example Viewmodel
class MyActivityViewModel : ViewModel(){
private val onFullScreenPressedEvent = SingleLiveEvent<Boolean>()
val onFullScreenPressed: LiveData<Boolean> = onFullScreenPressedEvent
fun onFullScreen(){
onFullScreenPressedEvent.call()
}}
For example, by using implementation "androidx.fragment:fragment-ktx:1.4.0" you can do in fragment A
private val viewModelA: MyActivityViewModel by activityViewModels()
and call the code that will change the event viewModelA.onFullScreen()
Then in Fragment B
private val viewModelB: MyActivityViewModel by activityViewModels()
and observe the liveData you trigger in Fragment A viewModelB.onFullScreenPressed.observe(viewLifecycleOwner, myObserver)
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 viewModel on Fragment A, which I load in this way:
viewModel = ViewModelProvider(this, viewModelFactory).get(AFragmentVM::class.java)
then from fragment A, I go to fragment B. Is this possible to use the same viewmodel on fragment B? In Fragment B I tried (as in docs):
private val viewModel: AFragmentVM by activityViewModels()
but I get an exception while trying to use this ViewModel:
java.lang.RuntimeException: Cannot create an instance of class ...AFragmentVM
...
BFragment.getViewModel(Unknown Source:2)
BFragment.onCreateView(ChartFragment.kt:40)
...
Caused by: java.lang.NoSuchMethodException: ...AFragmentVM.<init> [class android.app.Application]
EDIT:
Based on #SebastienRieu and #IntelliJ Amiya answers, everything that I had to do was to create ViewModel on Fragment A in this way:
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(AFragmentVM::class.java)
Or:
viewModel = ViewModelProvider(let {activity}!!,viewModelFactory).get(AFragmentVM::class.java)
Then on Fragment B I could use:
private val viewModel: AFragmentVM by activityViewModels()
If the two fragments are in the same activity you should replace this :
viewModel = ViewModelProvider(this, viewModelFactory).get(AFragmentVM::class.java)
by this
viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(AFragmentVM::class.java)
and add a viewModel in the activity and initialize it in this activity like this :
viewModel = ViewModelProvider(this, viewModelFactory).get(AFragmentVM::class.java)
With requireActivity() when setup the VM in fragment you tell the fragment to use the activity shared ViewModel
Fragment A
viewModel = ViewModelProvider(let {activity}!!,viewModelFactory).get(AFragmentVM::class.java)
Fragment B
private lateinit var viewModel: AFragmentVM
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'm trying to share a ViewModel between my activity and my fragment. My ViewModel contains a report, which is a complex object I cannot serialize.
protected val viewModel: ReportViewModel by lazy {
val report = ...
ViewModelProviders.of(this, ReportViewModelFactory(report)).get(ReportViewModel::class.java)
}
Now I'm trying to access the viewmodel in a fragment, but I don't want to pass all the factory parameters again.
As stated by the ViewModelProvider.get documentation:
Returns an existing ViewModel or creates a new one in the scope
I want to access the ViewModel instance defined in the activity, so I tried the following but it logically crashes as the model doesn't have an empty constructor:
protected val viewModel: ReportViewModel by lazy {
ViewModelProviders.of(requireActivity()).get(ReportViewModel::class.java)
}
How one should access its "factorysed" ViewModels in a fragment? Should we pass the factory to the fragment?
Thanks!
A little late but I had this question myself. What I found is you can do the following:
In your activity override getDefaultViewModelProviderFactory() like so:
override fun getDefaultViewModelProviderFactory(): ReportViewModelFactory {
return ReportViewModelFactory(report)
}
now in your fragments you can do
requireActivity().getDefaultViewModelProviderFactory()
to get the factory.
Or simply instantiate your viewModel like:
private val viewModel: ReportViewModel by activityViewModels()