Lets say I have a fragment flow defined by my nav graph using the Navigation Components and the safe-args plugin.
How do I pass arguments to my nav host fragment?
what for you need to pass to nav host fragment?
you can use shared ViewModel to pass some data from one fragment to another.
for example if you need to share some data between fragments that are in the same navigation graph you can use trap scope ViewModel otherwise activity scope ViewModel.
activity scope (val viewModel: YourViewModel by activityViewModels())
or
navigation graph scope (val viewModel: YourViewModel by navGraphViewModels(R.id.desired_graph))
Related
I have a Fragment0 which contains a ViewPager2 that internally may contain 1 or more child Fragments.
I have created a SharedViewModel and defined it in the Parent fragment like so:
val viewModel by viewModels<SharedViewModel>()
I have also added definition for the viewModel in each of the child Fragments.
private val sharedViewModel by viewModels<SharedViewModel>(
ownerProducer = { requireParentFragment() }
)
I use the NavigationControl to navigate to Fragment4 from each of these Child Fragments viz. Fragment1, Fragment2 and Fragment3. My question is, how can I share the same view Model with Fragment4 which is not directly a child of the Fragment0. Can I use the same approach to share viewModel with Fragment4 as well?
Or is there a better way to handle such a usecase?
You can use by viewModels({requireParentFragment()}) only to share viewmodel with parent and child fragment.
It will not work with fragment4.
you can use by activityViewModels().
but it's not good architecture. i suggest you to create separate viewmodel for Fragment4 and share the data with navigation.
find more from here: https://developer.android.com/guide/fragments/communicate
I have a navigation as follows:
FragmentList -> FragmentDetailA -> FragmentDetailB -> FragmentDetailC
I use a viewModel for the detail FragmentDetailViewModel
private val detailViewModel: DetailViewModel by activityViewModels()
But if I go forward, and then back, the above fragmentDetails are changed. How to have a viewmodel assigned to the fragment without changing the others?
Solution:
First of all, change activityViewModels() for viewModels()
Then, the problem was with setFragmentResultListener. It was called before instantiating the new fragment, then the call was made on Fragment A and not on Fragment B. I decided to pass data between destinations with Bundle objects.
Thanks a lot
With this way that you initialized view model, view model depends on the parent activity you must use this way
private val detailViewModel: DetailViewModel by viewModels()
to your view model instance depends on your fragment instance.
It sounds like you want to link the viewmodel to the fragment.
ViewModelProvider(this,viewModelFactory).get(MyViewModel.class)
I am working on Android TV app
And I have a Fragment(main Fragment) and inside fragment have side menu, each menu Item creates new fragment(menu Fragment).
I have Viewmodel and in ViewModel I have config livedata which I am loading when I main Fragment is created.
And Menu Fragment's data is based on data I am getting from API calls and from config data
I have created single instance of viewmodel with the activity lifecycle.
But the problem is when I am navigating from one fragment to another for example from 1-2 fragment and as 1 fragment has already loaded data the livedata is not empty and navigating to 2nd fragment before fetching the second fragment data it observes/displays the livedata from first fragment and then it's own after it fetches it's data
I think each fragment should have it's own instance ,but I also need data which should be shared (the config) between the each instance of viewmodel ?
How can I make this?
private val viewModel: HomeViewModel by lazyViewModelActivityScope()
viewModel.fetch()
viewModel.configData.observe(viewLifecycleOwner, Observer { it ->
loadData(it)
})
it is inside MenuFragment
private val viewModel: HomeViewModel by lazyViewModelActivityScope()
viewModel.fetchMenuPage(menuItem)
viewModel.carouselsWithAssetsData.observe(viewLifecycleOwner, Observer { carouselWithAssets ->
carouselWithAssets.forEach { carouselWithAsset ->
mRowsAdapter.add(createCardRow(carouselWithAsset))
}
})
I got the answer of my question and I want to share it with others
I think generally this is something you'd manage with DI (injecting the same config into both ViewModels)
Or each Fragment would need to pass the activity ViewModel's data to the Fragment's ViewModel. Or you could have a global LiveData that manages itself (loading data in onActive()) and skip the activity ViewModel entirely
While Navigation component of JetPack looks pretty promising I got to a place where I could not find a way to implement something I wanted.
Let's take a look at a sample app screen:
The app has one main activity, a top toolbar, a bottom toolbar with fab attached.
There are 2 challenges that I am facing and I want to make them the right way.
1. I need to implement fragment transactions in order to allow replacing the fragment on the screen, based on the user interaction.
There are three ways I can think of and have this implemented:
the callbacks way. Having a interface onFragmentAction callback in fragment and have activity implement it. So basically when user presses a button in FragmentA I can call onFragmentAction with params so the activity will trigger and start for example transaction to replace it with FragmentB
implement Navigation component from JetPack. While I've tried it and seems pretty straightforward, I had a problem by not being able to retrieve the current fragment.
Use a shared ViewModel between fragment and activity, update it from the fragment and observe it in the activity. This would be a "replacement" of the callbacks
2. Since the FAB is in the parent activity, when pressed, I need to be able to interact with the current visible fragment and do an action. For instance, add a new item in a recyclerview inside the fragment. So basically a way to communicate between the activity and fragment
There are two ways I can think of how to make this
If not using Navigation then I can use findFragmentById and retrieve the current fragment and run a public method to trigger the action.
Using a shared 'ViewMode' between fragment and activity, update it from activity and observe it in the fragment.
So, as you can see, the recommended way to do navigation would be to use the new 'Navigation' architecture component, however, at the moment it lacks a way to retrieve the current fragment instance so I don't know how to communicate between the activity and fragment.
This could be achieved with shared ViewModel but here I have a missing piece: I understand that fragment to fragment communication can be made with a shared ViewModel. I think that this makes sense when the fragments have something in common for this, like a Master/Detail scenarion and sharing the same viewmodel is very useful.
But, then talking between activity and ALL fragments, how could a shared ViewModel be used? Each fragment needs its own complex ViewModel. Could it be a GeneralViewModel which gets instantiated in the activity and in all fragments, together with the regular fragment viewmodel, so have 2 viewmodels in each fragment.
Being able to talk between fragments and activity with a viewmodel will make the finding of active fragment unneeded as the viewmodel will provide the needed mechanism and also would allow to use Navigation component.
Any information is gladly received.
Later edit. Here is some sample code based on the comment bellow. Is this a solution for my question? Can this handle both changes between fragments and parent activity and it's on the recommended side.
private class GlobalViewModel ():ViewModel(){
var eventFromActivity:MutableLiveData<Event>
var eventFromFragment:MutableLiveData<Event>
fun setEventFromActivity(event:Event){
eventFromActivity.value = event
}
fun setEventFromFragment(event:Event){
eventFromFragment.value = event
}
}
Then in my activity
class HomeActivity: AppCompatActivity(){
onCreate{
viewModel = ViewModelProviders.of(this, factory)
.get(GlobalViewModel::class.java)
viewModel.eventsFromFragment.observe(){
//based on the Event values, could update toolbar title, could start
// new fragment, could show a dialog or snackbar
....
}
//when need to update the fragment do
viewModel.setEventFromActivity(event)
}
}
Then in all fragments have something like this
class FragmentA:Fragment(){
onViewCreated(){
viewModel = ViewModelProviders.of(this, factory)
.get(GlobalViewModel::class.java)
viewModel.eventsFromActivity.observe(){
// based on Event value, trigger a fun from the fragment
....
}
viewModelFragment = ViewModelProviders.of(this, factory)
.get(FragmentAViewModel::class.java)
viewModelFragment.some.observe(){
....
}
//when need to update the activity do
viewModel.setEventFromFragment(event)
}
}
I'm collaborating with ViewModel and fragments, and would like to retain my ViewModel for my fragment on rotation change. When passing my Fragment into ViewModelProviders.of() it does not get retained, but when I pass the Activity that the fragment belongs to, it is retained. So is passing the activity how it is supposed to be used?
Calling ViewModelProviders.of(this) in Fragment won't retain my ViewModel. Is that expected behavior?
class MainFragment : Fragment() {
private lateinit var viewModel: MainViewModel
fun OnXXXXXXXXX {
// This _will NOT_ retain ViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
// This _will_ retain ViewModel
viewModel = ViewModelProviders.of(activity).get(MainViewModel::class.java)
}
}
Yes, its expected behaviour, Look at this content
Fragments can share a ViewModel using their activity scope to handle this communication
If you want to share same ViewModel, use same context. For Example, multiple fragments on same activity:
ViewModelProviders.of(activity)