how not to destroy fragment with androidX - android

now ,I try androidX's navigation and bottom-navigationbar,when I use it like below
supportFragmentManager = getSupportFragmentManager();
navHostFragment =(NavHostFragment)supportFragmentManager.findFragmentById(R.id.frag_nav);
navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(navigation, navController);
I found an issue,everytime switch the bottomNavigationBar,the fragment will be recreate,all network task in target fragment will be redo,how to keep fragment's state when it switch in androidX?

I also had the same issue. I'm using BottomNavigationView with navigation architecture components and every time fragment is recreated. This is intended behaviour as you can see in Google issue tracker (https://issuetracker.google.com/issues/109856764).
So the right way to approach this is to save current view state in ViewModel and observe that state in every newly created fragment. You need to do all your network tasks in ViewModel.
Also take care of the way you fetch ViewModel inside fragment. If you do it like this viewModel = ViewModelProviders.of(this, viewModelFactory).get(MyViewModel::class.java), ViewModel will get fragment scope(it will be destroyed when fragment gets destroyed). You have to get ViewModel with activity scope so it can survive switch between fragments. You can do it like this viewModel = ViewModelProviders.of(activity!!, viewModelFactory).get(MyViewModel::class.java)

Related

How does fragment restores its state

I have an activity and fragments. The activity is controlling navigation via observing navigation LiveData state.
sealed class Navigation {
object Destination1: Navigation()
object Destination2: Navigation()
}
On each change it replaces a fragment in a container (each time a new instance of fragment is created for each destination).
This however does not work when I try to save the state of the fragment. In activity:
viewModel.navigation.observe(viewLifecycleOwner) {
supportFragmentManager.commit {
replace(R.id.container, fragmentInstance)
addToBackStack(null)
}
}
I tried to implement the ViewModel SavedStateHandle but the state is not saved. The Handle is empty when I have "don't keep activities" setting ON and I minimize and then maximize the app.
What am I doing wrong? Is it possible to save state of the Fragment?
How to plug the activity's savedInstanceState into the fragments if I create them by observing LiveData? Is the LiveData approach a problem?

communication between 2 fragments

framentA calls fragmentB via an mother activity. FragmentA is no longer in memory. FragmentB calls fragmentA(go back to previous screen). FragmentB has some data to share with FragmentA. But, how?
here is what I tried:
static variable - it worked, but a bad habit, I can not use it
viewModel - each fragment creates it's OWN instance of view model. Therefore the 2 instances of the viewModel will not work.
DB - not a good pattern. Therefore I cant use it.
I think what you need is to store the fragments in Fragment Manager, when you do that you are basically trying to maintain a back stack of your fragments:
FragmentManager fragmentManager = getSupportFragmentManager();
//or FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.add(FragmentToBeAdded, "textRelatedtoFragment")
.addToBackStack(null)
.commit();
Adding to the backstack helps in saving the state of the fragment.
My suggestion would be:
For Fragment Navigation use: https://developer.android.com/guide/navigation/navigation-getting-started
It will greatly simplify your in-app navigation.
For passing parameters between Fragments/Activities:
First solution is to use ViewModel with scoping of Activity using by activityViewModels(). Read More here: https://developer.android.com/jetpack/guide This will give you an indirect way of passing arguments.
Use SafeArgs (part of Navigation component) to directly pass needed data.
More information can be found here: https://developer.android.com/guide/navigation/navigation-getting-started#ensure_type-safety_by_using_safe_args

Android Navigation Components

I added Android new Navigation Component inside my app and created navGraph and added few fragments as a destinations. I figure out every time i navigates into destinations previous destination (fragment) is begin restarted. And my last state of previous destination (fragment) has lost. Is there any options to retain this?
As i already used retainInstance in fragment.
I have been using the navigation component with fragments in my project and I found that the easiest way to save a fragment state is using the view model component:
https://developer.android.com/topic/libraries/architecture/viewmodel?gclid=EAIaIQobChMI442XtIuR4wIVhuiaCh0uGAFZEAAYASAAEgIRIfD_BwE
I have been using MVP Architecture and in my model I extend the view model.
That way as long as your activity wasn't destroyed the model instance is being saved and you can access it when the fragment reload so all it's parameters are still there so you can reinitialise your fragment with the saved values inside your model.

Shared ViewModel to help communication between fragments and parent activity

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)
}
}

how to save state of fragment with a connection to my service

In my activity I use three buttons at bottom to choose between fragments like this for example:
scenarioFragment = new ScenarioFragment();
android.app.FragmentManager fragmentManager = this.getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment, scenarioFragment, scenarioFragment.toString());
fragmentTransaction.addToBackStack("stack");
fragmentTransaction.commit();
so in ScenarioFragment, I bounded a connection to my service.and whenever I change between fragments a new fragment is created and I have to bind a new connection.
I want to save all fragments state and restore it as they choose.
thanks.
According to what you said, your Fragment have the same Activity as a parent.
Assuming this, you could create a ViewModel (Component introduced as part of the Android Architecture Components) for your activity and put whichever state you want in them.
Then, inside each of your Fragments you can get the ViewModel with the statement:
ViewModelProviders.of(getActivity()).get(ViewModelClassName.class);
The key to this statement is to make sure you pass in the getActivity so that you can get the same ViewModel instance in all the Fragments as long as they are attached to the same parent. If you pass the Fragment instead of the Activity like this ViewModelProviders.of(this).get(ViewModelClassName.class); you will have a new instance of the ViewModel tied to the scope of the Fragment that will be cleared if the Fragment is destroyed.
This is possible because the ViewModel class will be created using the scope of the Activity and it doesn't matter how many time you recreate your fragment you will be getting the same ViewModel instance with your state.
You can learn more about Android Architecture components here.
If you wish to understand a bit more about how the ViewModel works internally you can find out more on a post I wrote here.

Categories

Resources