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.
Related
Overall look at the code, I don't understand why it can be done.
https://github.com/google/iosched
from apps/iosched/ui/MainActivity.kt, It initialized NavController and NavHostFragment, but seems that there is no special treatment.
https://github.com/google/iosched/blob/main/mobile/src/main/java/com/google/samples/apps/iosched/ui/MainActivity.kt
val appBarConfiguration = AppBarConfiguration(TOP_LEVEL_DESTINATIONS)
private val TOP_LEVEL_DESTINATIONS = setOf(
R.id.navigation_feed,
R.id.navigation_schedule,
R.id.navigation_map,
R.id.navigation_info,
// R.id.navigation_agenda, comment will not stop saving the statement.
R.id.navigation_codelabs,
R.id.navigation_settings
)
from apps/iosched/ui/AgendaFragment.kt: the most simplest fragment, BindingAdapter method will always init AgendaAdapter(), but It can save the position of RecyclerView after init.
https://github.com/google/iosched/blob/main/mobile/src/main/java/com/google/samples/apps/iosched/ui/agenda/AgendaFragment.kt
Why can it save the State of each fragment?
IOSched depends on Navigation 2.4.1. As per the release notes of Navigation 2.4.0:
The NavController ... has been expanded to support saving and restoring the back stack.
As part of this change, the NavigationUI methods of onNavDestinationSelected(), BottomNavigationView.setupWithNavController() and NavigationView.setupWithNavController() now automatically save and restore the state of popped destinations, enabling support for multiple back stacks without any code changes. When using Navigation with Fragments, this is the recommended way to integrate with multiple back stacks.
And IOSched uses setupWithNavController, which means each tab is automatically going to save and restore its state correctly.
That includes the state of a RecyclerView, which has always supported saving and restoring its position automatically.
how to make button to open another fragment. being within a fragment. kotlin
I'm starting in kotlin and I'm having a hard time trying to open a fragment with a button, how do I?
You need to use FragmentManager and FragmentTransaction to add your fragment on the fly. you can call a function similar to this in your button's onClick method. But it is recommended for the parent activity to handle each fragment's lifecycle and the fragments
are not supposed to interact each other. The following is taken from the developer docs, that can be found here.
"Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done either through a shared ViewModel or through the associated Activity. Two Fragments should never communicate directly."
fun createFragmentonTheFly(){
var mFragmentTransaction: FragmentTransaction = getSupportFragmentManager().beginTransaction()
mFragmentTransaction.add(R.id.fr_container,new ProductListFragment())
mFragmentTransaction.commit()
}
The best way to do it would be to add an interface let say onFragmentDetachedLisetner and add one method replaceFragment() or something and make your Activity implement this interaface and had it replace as soon as the fragment is detached and make your fragment that contains your button finish itself when user clicks the button, then your activity will replace it with the one you wanted to start. And also consider reusing fragments, as that is the main purpose of fragments at the first place.
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)
}
}
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.
I am a bit confused about what is the ideologically correct way of using fragments.
As the Android Developers states,
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. You
can think of a fragment as a modular section of an activity, which has
its own lifecycle, receives its own input events, and which you can
add or remove while the activity is running (sort of like a "sub
activity" that you can reuse in different activities).
And also:
Fragments decompose application functionality and UI into reusable
modules Add multiple fragments to a screen to avoid switching
activities
And my usage of fragments goes the following way:
I have only one main Activity and a whole bunch of fragments. Instead of starting activities, I prefer replacing fragments.
For example, I have FeedsFragment, FriendsFragment, MessagesFragment, and when I select something from sliding menu, my Activity just replaces the main Fragment. If I'm launching a Fragment from other Fragment, I put the previous one in backstack.
Some fragments require the Activity to change the actionbar, an I do it directly by
((MainActivity)getActivity()).setupActionBar();
Currently I don't have any code that supports tablet layouts (as seen in examples on android developers), but I'm planning to add it.
So, is this the right way of doing things? Or am I completely missing something?
As you know fragment has their own lifecycle, and you can use its event when ever you want from the activity lifecyle.
But Fragments lifecycle depens on activity lifecycle So when actvity destroyed, fragments destroyed also.
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
Yo can use the fragment transaction, to replace them in one activity, I think you use the same way,
i think you arent wrong way and there are no problem to use fragment instead of using different activites.
But you should think about, you realy need to use them in one activty ? If you dont need to show them in one activity you dont need to use fragment.
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle. For
example, when the activity is paused, so are all fragments in it, and
when the activity is destroyed, so are all fragments. However, while
an activity is running (it is in the resumed lifecycle state), you can
manipulate each fragment independently, such as add or remove them.
When you perform such a fragment transaction, you can also add it to a
back stack that's managed by the activity—each back stack entry in the
activity is a record of the fragment transaction that occurred. The
back stack allows the user to reverse a fragment transaction (navigate
backwards), by pressing the Back button.
As a result , it is not wrong , but i think if you dont need, creating different activity is easy to maintain.