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.
Related
My app has an activity on which many fragments are placed for different pages.
Some fragments have a recyclerView that data given via a viewModel.
Now, I would first create an instance of the viewModel in each fragment and put the data in the recyclerView using observer and LiveData, but this caused LiveData observer is being triggered two times.
After that, the idea came to me to create a static instance of the viewModel in the activity class to solve this problem and use it in different fragments. It worked, and LiveData observer triggered once.
Now my question is, is it right to use a static instance of viewModel in MainActivity (and use it from fragments)?
If not, then what should I do to solve the problem(Trigger the observer twice)?
I've creating a test activity that updates some text in my MyViewModel.
I'd like to observe these changes in a Fragment, but when I use
MyViewModel myViewModel = new ViewModelProvider(this).get(MyViewModel.class);
it gives me a different instance of MyViewModel than that used in the activity, which results in my onChanged() callback in the fragment not being called.
Only when I modify that same fragment code to
HomeViewModel homeViewModel = new ViewModelProvider(getActivity()).get(HomeViewModel.class);
does the fragment get the same instance of MyViewModel as the activity - so onChanged() is successfully called.
However, I'm not sure if using getActivity() as the ViewModelStoreOwner is the proper way of doing things as I haven't seen this in any examples anywhere. I'm wondering if there might be a better ViewModelStoreOwner I should be using in this instance?
I'm wondering if there might be a better ViewModelStoreOwner I should
be using in this instance?
You should use activity instance for sharing the same instance among fragments in the same activity.
Both Activity and Fragment implements their own ViewModelStoreOwner interface and implements the getViewModelStore() method. getViewModelStore() provide the ViewModelStore instance which is used to store the viewmodel objects, created by the ViewModelProvider.
Note: ComponentActivity implements the ViewModelStoreOwner interface and FragmentActivity (parent of AppCompatActivity) inherits the implementation.
So both Activity and Fragment have specific implementation for the ViewModelStoreOwner interface methods and store the viewmodel instance as per the lifecycle of the objects(including the configuration changes).
Since fragments belong to activity get the same activity instance so using the getActivity() will result in using the ViewModelStoreOwner object of the activity. To share the objects among fragments, simply use the activity instance for creating ViewModelProvider which will use the same ViewModelStoreOwner in all fragments hence will return the persisted object of viewmodel (if created before).
Having an Activity which does as little as possible has become a "best practice" for some time now, so the scenario of an Activity and a Fragment which need access to the same ViewModel instance may not be covered by many guides.
But "there is no rule without an exception", and your scenario is similar to the one where an Activity has two Fragments which need to share data.
In this case, one uses the Activity scope for the ViewModel to make sure every component will have access to the same instance. See also the section "Share data between fragments" in the View Model Overview at developer.android.com
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.
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.
I wonder the difference between two ways of transfering data from activity to fragment.
One is using getArgument() and setArgument(). I can transfer data using these methods at Fragment's contruction time.
Another is using getActivity() method. Like this way
((HostActivity)getActivity()).getXXX()
After declaring getter method of data Fragment may use, call this method in fragment through getActivity() and Type casting.
I think second one is easier and convenient. Because get/setArgument() can be called only Fragment's contruction time.
So, How to apply these 2 way to sending and getting data between Activity and Fragment?
A Fragment represents a behavior or a portion of user interface in an
Activity. You can combine multiple fragments in a single activity to
build a multi-pane UI and reuse a fragment in multiple activities.
Because fragment can reuse in multiple activity, if you use getActivity() with type casting, you must check instanceOf activity before call method. And each of activity use that fragment, you must implement method getXXX().
Use newInstance method in fragment, you only pass require parameter for it.
If you create fragment for individual activity, you can apply 2 ways transfer data.
The fragment has an independent lifecycle from activity with specific threads, functions and handlers. So you can use getters/setters Activity variables like a global variables and bundle data (arguments) to independent fragment variables.