Passing data between Fragments in Navigation Component is easy. Say going from A to B you just set arguments with SafeArgs and you are done.
But, it gets tricky when passing data from B back to A.
According to documentation, we can use SharedViewModel which is works well. But I am looking for better way of passing data back to A from B.
The problem of using SharedViewModel is, you have to create SharedViewModel for every fragment pair that you need to pass data.
Any suggestions? If any annotation-processing method you can think about, you are more than welcome to recommend.
If you do not want to use SharedViewModel way, you can follow the next approach:
1- Define a delegate for your Details Fragment. (This delegate have to implement Serializable or Parcelable:
interface DetailsFragmentDelegate: Serializable {
fun onSomething1(someData1: SomeData1)
fun onSomething2(someData2: SomeData2)
}
2- Add the delegate to your Details Fragment arguments in nav_graph.xml
3- Pass the delegate to your Details Fragment when navigating to its destination by your Base Fragment:
findNavController().navigate(
BaseFragmentDirections.actionBaseFragmentToDetailsFragment(
object: DetailsFragmentDelegate {
// override delegate methods
}
)
)
4- Get the delegate argument in Details Fragment and pass the data back wherever you need:
....
delegate.onSomething1(data1)
....
delegate.onSomething2(data2)
....
I am not sure whether there is a better way or not, but it's working...
You don't need to create a ViewModel per Fragment pair. What I am doing is creating a ViewModel per Fragment. Each ViewModel would have a map[Class[Fragment], Any] named mailBox.
Each Fragment will define a FragmentResult type which is different per Fragment class.
In the child Fragment onBackPressedHandler, before pop-up, fetch the parent ViewModel from the Activity and put your result in the mailBox for your class. You will need a ViewModel class for that. See below.
The parent Fragment needs to pass it's ViewModel.class to the child Fragment, before launching it.
When the Parent Fragment is re-started after popping up the child from the stack. Get the mailBox map from it's ViewModel, check if there is a key with value from the expected FragmentChild::class. If so, then cast to the desired type.
The parent Fragment ViewModel needs to save who was the last child it launched.
I am using an callback interface for this. So i have created an interface with some methods. I implemented that interface 'A' and then call if from 'B'. Very easy and works great.
Related
I Have Fragment A that calls Fragment B or Fragment C.
B and C uses a ViewModel provided by A.
For while, I'm passing the View as parameter on constructor:
FragmentB(val viewModel: ViewModel)
FragmentC(val viewModel: viewModel)
But this way, not is a good solution, because the app is crashing when try to reopen the fragment.
Could not instantiate the fragment
What the best way to do this?
I thought create a newInstance method and pass the viewModel as argument on Bundle, but how I would transform this viewModel in Parcelable or Serializable?
You are probably trying to create custom Fragment constructor which is not allowed in Android.
I assume your ViewModel was created inside of fragment A using method
ViewModelProviders.of(this)
ViewModel created this way should be used only inside of this fragment(It won't crash if you i.e. pass it through Object or Singleton but it's not supposed to be used that way).
Better solution is to put all of these fragments A, B, C inside one activity.
This way you can call
ViewModelProviders.of(requireActivity())
and get the same instance of the ViewModel in all of the fragments. So if you get the ViewModel using this method in your fragment A, put there some values and then replace fragment A with fragment B in the same activity, you can call ViewModelProviders.of(requireActivity()) again get access to the same instance of your ViewModel and retrieve the values.
Imagine that I have two fragments:
FirstFragment
SecondFragment
User will prompt some data in FirstFragment, and I'll like to share some of that data to SecondFragment. I'm using Android's ViewModel and Jetpack's Navigation. Which is the best way to pass data?
ViewModel
In the case of the viewModel, if I instantiate the viewModel like this:
MyViewModel viewModel = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
in every fragment, I could create a method called setSharedData and call it from FirstFragment, and create methods to getSpecificData and call them as needed in my SecondFragment.
Destination arguments
If I use Jetpack's Navigation I could use destination arguments with safe-args to share data, setting it in the actions between Fragments.
What option do you think is the best? Do you think those are different solutions that suit better for different situations? In that case, which method should I use in which situation?
I think the view model approach is useful when you want using the shared data multiple times but if you only need the shared data for initiating, i recommend to use the destination argument. Because persisting the data in the view model uses of memory and it isn't necessary.
MyApp need hold a User object in whole context,A,B,C activities'xml use this User object,when A edit User,I want B and C notifyChange,how to deal this problem with databinding,livedata and viewModel?
Formerly I make User.class extend BaseObservable,but POJO will be very troublesome and must not be a null,sometimes User maybe null such as not login.
Now I Change to use LiveData, make Pojo simple and not extend BaseObservable,but when A edit,B and C not work,I think i need ABC use same viewModel instance in memory,but this will cause viewModel's onClear() trigger manytimes.
Another way is to have one singleton repository to hold your user data and each viewModel can have that repository and share the same data between activities.
Based on this part of the documentation:
https://developer.android.com/topic/libraries/architecture/livedata#extend_livedata
The fact that LiveData objects are lifecycle-aware means that you can share them between multiple activities, fragments, and services. To keep the example simple, you can implement the LiveData class as a singleton as follows:
You can create a singleton for your view model like I did here:
companion object{
private lateinit var instance: ViewModelProfile
#MainThread
fun getInstance(userId: String): ViewModelProfile{
instance = if(::instance.isInitialized) instance else ViewModelProfile(userId)
return instance
}
}
Then I call it and get instance anywhere like this:
val profileVModel = ViewModelProfile.getInstance(user.uid)
If you want to share common ViewModel between ABC activities, then it is suggested to keep them as 3 fragments in a single Activity, create ViewModel of that Activity which can be shared among all three fragments A, B, and C.
Also what you are trying to achieve with activities is like this, suppose you have done some operation in activity A, if you want Activity B and C to get notified about them then they need to be running to get notified, which won't be happening, so instead you should use Intent or Bundle to pass needed information when the activity get started.
Updated
There are other ways as well to achieve similar kind of functionality like,
Event Bus
RxJava Subjects (refer this)
Android State by Evernote (refer this)
This will allow you to have application level access of state, which can be accessed by any Activity or Fragment
I'm needing a way to pass data from multiple fragments to an activity. This data can only be sent to the activity when I change the fragment tab.
To pass the data I am using the interface method, but I do not know in which function of the class fragment I can put the method that will take the text of the EditText and send to the activity through the interface.
Is it important to pass the data only when the fragment changes? If not, you could add a callback to the edit text that calls the interface method your activity implements. This answer has some good examples on how to do that.
I would like to ask if it's correct to share the same ViewModel between Fragment and its Activity.I have UserDetailActivity and UserDetailFragment. Can I use the same ViewModel to display detail data of a user in the UserDetailActivity and UserDetailFragment or is there better approach.
Yes you can pass ViewModal object from a Activity to Fragment or vice-versa by implementing Parcelable into ViewModal class and can share object with fragment setArguments() method.
I am not using MVVM but I think its the same with MVP, I use the same Presenter(ViewModel in your case) with my Activity and its child Fragment. It make since because a Fragment is literally a fragment of an Activity. There might be some special cases where you really want to separate viewModel of Fragment and Activity, but most of the time, they share. About the initialization, dont pass your viewmodel directly, you can use dagger and inject it.