Stop fragment Re-Creation in BottomNavigation using NavGraph - android

I have multiple fragments like Dashboard,Notifications and Profile in Bottom Navigation. I am using NavGraph- NavController to control the fragments.
I want to save the state of previous fragment.
I don't want to call my API's again on Switching between fragments. Whenever I switch between fragment it calls onDestroyView of previous fragment and onCreateView of current Fragmnent . That's why all the operation in onCreateView or onViewCreated will call again.
How could I get rid of it. Is there any implementation using NavGraph that stop fragment from reCeating
Or is there a way to don't call those API's again . I mean to retatin the UIState.
For example:
The user is on Map fragment and he search some location on Google Map and moves to the next fragment Dashboard
I have tried using
lifecycleScope.launch {
lifecycleScope.launchWhenCreated {
Log.v("LifeCycleState","launchWhenCreated")
}
}
viewLifecycleOwner.lifecycleScope.launch {
lifecycleScope.launchWhenCreated {
Log.v("LifeCycleState","launchWhenCreated in viewLifeCycleOwner")
}
}

You've to follow multi navigations graph concept.
Create separate graphs for the bottom nav icon based. Ex Home, History and Account So you've to create 3 graphs and include in one graph.
Like this
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph"
app:startDestination="#+id/home">
<include app:graph="#navigation/home"/>
<include app:graph="#navigation/history"/>
<include app:graph="#navigation/account"/>
Follow this sample

Related

open a fragment in a different level of hierarchy with backstack in Android Navigation component

I am using Navigation Component in my project and :
I need to open a fragment in a different level of hierarchy, so that the back stack is created properly too
in my nav_graph.xml there is a hierarchy like this:
HomeFragment -> CollocationFragment -> ChapterFragment --[selfNavigate]--> ChapterFragment -> PlayerFragment
as you see one of my fragments navigates to itself and send an arguments each time like this:
<fragment
android:id="#+id/ChapterFragment ">
<action
android:id="#+id/action_chapterFragment_self"
app:destination="#id/ChapterFragment" />
<argument
android:name="chapterID"
app:argType="Long" />
</fragment>
in a nutshell, I need to open PlayerFragment from HomeFragment with an appropriate backstack (the hierarchy mentioned above) .
HomeFragment ---[my back stack]---> PlayerFragment
I know that it seems NavDeepLinkBuilder creates the backstack itself but I have no idea how to create a custom backstack by using Navigation component for my fragments in this case.
eventually I find a way...it's kind of a workaround but it works like a charm:
so, just call navigate function in your desire order to make the hierarchy backstack:
fun openPlayerFromHome(){
findNavController.navigate(R.id.HomeFragment)
findNavController.navigate(R.id.CollocationFragment)
findNavController.navigate(R.id.ChapterFragment,bundleOf("chapterID",id))
findNavController.navigate(R.id.PlayerFragment)
}

Android NavController: Navigating to a fragment from the main activity outside the default nav graph

In my project I have a main activity which host the default navController. From the default nav-graph, I can navigate to all the hosted fragments. I after the onResume() method of each fragment I check the authentication state, If the user is not authenticated the user will be navigated to the login fragment. So instead of checking the authentication state in each fragment which will lead to lots of code duplication. I want to check the authentication state in the onResume() method of the main activity then navigate appropriately. I have tried calling navController.navigate($fragmentId) and some of its variants. It usually navigate to the destination activity. But when I tried to navigate to another fragment from the login fragment I receive this error
java.lang.IllegalStateException: Fragment LoginFragment{de25d51}
(1c765036-4d65-41dc-8dec-549281d50db6)} not attached to an activity.
I created a global action on the nav graph of the activity I want to navigate to like this
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_nav"
app:startDestination="#id/mainFragment">
...
<action android:id="#+id/action_global_mainFragment"
app:destination="#id/mainFragment"/>
</navigation>
then after checking the authentication state,
I call the nav action in the enclosing activity like this
this.findNavController(R.id.activity_nav_host_fragment).navigate(R.id.action_global_mainFragment)
Feel free to read more about global actions here

How to return to previous fragment by pressing back?

I'm working with a Navigation Drawer navigating to many different fragments. In a few occasions, some fragments are placed over other fragments. However, when I press the back button to bring me to the previous fragment, it brings be back to the home fragment.
MapFragment.java
img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction fr = getFragmentManager().beginTransaction();
fr.replace(R.id.navHostFragment, new ImgMapFragment()).addToBackStack(null);
fr.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fr.addToBackStack(null);
fr.commit();
How do I make it so that when I open ImgMapFragment, I can return to the previous fragment I was in instead of the HomeFragment
Look at the getFragmentManager().popBackStack() methods (there are several to choose from)
http://developer.android.com/reference/android/app/FragmentManager.html#popBackStack()
AndroidProButNo is a new contributor. Be nice, and check out our Code of Conduct.
Due to the inherent complexity of the Navigation component, this question is actually much trickier to answer than it first looks, considering you need an understanding of the Navigation component, how to use it, and what it tries to wrap away from you, and so what you shouldn't be trying to use if you are already using the Navigation component.
Navigation component internally uses the FragmentNavigator, and the FragmentNavigator is what executes an Action that is associated with a destination's ID (R.id.destination[...]). The NavHostFragment hosts this Navigator for you to make the Fragment-based navigation work via the Navigation component's NavController.
Therefore, to "navigate back" when one chooses to use the NavHostFragment, one needs to use Navigation Component to define all Fragment destinations, the actions that move between said Fragment destinations, and then pop the Backstack that belongs to the NavController (which will internally manage the FragmentManager).
It looks something like this
A main_activity.xml layout
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</FrameLayout>
And navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_graph"
app:startDestination="#id/blankFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.example.navigationexample.FirstFragment"
android:label="First Fragment"
tools:layout="#layout/first_fragment" >
<action
android:id="#+id/action_first_to_second"
app:destination="#id/secondFragment" />
</fragment>
<fragment
android:id="#+id/secondFragment"
android:name="com.example.navigationexample.SecondFragment"
android:label="Second Fragment"
tools:layout="#layout/second_fragment" />
</navigation>
Then to navigate from one screen to another, you need to do
try {
Navigation.findNavController(view).navigate(R.id.action_first_to_second);
} catch(Exception e) {}
And to return, you have to do
try {
Navigation.findNavController(view).popBackstack();
} catch(Exception e) {}
And as you are in this case using Navigation component, you shouldn't manually manage FragmentTransactions because it will make the Navigation component's backstack inconsistent.
Theoretically, if you did this correctly, then onBackPressed should already be handled by Navigation component correctly, as I believe they register themselves as a OnBackPressedDispatcher, and don't have to delegate the back event over manually.

Android Navigation Graph, Nested Graph Issue

I hope you're all doing well.
I have a problem about navigation graphs. You can see the structure in the image below;
So we have 2 different navigation graphs; navigation_A and navigation_B.
I need to navigate to Fragment Y and Fragment Z from a fragment in nested graph which is navigation_B.
The problem is;
If I use <include> for navigation_B, I can not use it as NavHost.
If I use different navigation graph and add it as nested graph like above, I can not use navigation destination for Fragment Y or Fragment Z.
I need to use navigation_B as NavHost, also i need to be able to navigate to Fragment Y and Fragment Z. How can I achieve that?
Thank you!
Your navigation graph structure breaks the recommended navigation pattern!
But for your needs, I have a way to workaround.
Method 1:
First, FragmentY and FragmentZ need to have their own deeplinks.
<fragment
android:id="#+id/fragY"
android:name=".FragmentY"
tools:layout="#layout/frag_y">
<deepLink
android:id="#+id/"
app:uri="any-app://internal_navigation_frag_y" />
</fragment>
<fragment
android:id="#+id/fragZ"
android:name=".FragmentZ"
tools:layout="#layout/frag_z">
<deepLink
android:id="#+id/dlink_frag_z"
app:uri="any-app://internal_navigation_frag_z" />
</fragment>
Then, Inside a Fragment that lives in navigation_b. Let's call it FragmentB
// navigate to FramgmentY
val deeplinkY = Uri.parse("any-app://internal_navigation_frag_y")
findNavController().navigate(deeplinkY)
// navigate to FramgmentZ
val deeplinkZ = Uri.parse("any-app://internal_navigation_frag_z")
findNavController().navigate(deeplinkZ)
You can store the string any-app://internal_navigation_frag_y and any-app://internal_navigation_frag_z in string.xml file though.
Check this out: https://developer.android.com/guide/navigation/navigation-navigate#uri
Method 2:
Inside the nested graph navigation_B, define 2 global actions that point to fragmentY and fragmentZ. Since navigation_B is a NavHost, so it will know about FragmentY and FragmnentZ
Check this: https://developer.android.com/guide/navigation/navigation-global-action
When you use nested navigation graph the inner graph should not know anything about the outside graph. In this case graph_B should not know about the existence of a FragmentY or FragmentZ. What you should do is deliver a result from your graph_B to whomever launched it. Then at that point decide whether to go FragmentY or FragmentZ.

Navigation drawer unlock mode ignored

So I have this app with one activity (MainActivity) and I'm using the navigation architecture component. So it has DrawerLayout which is wrapping the fragment view.
These are two methods in the Activity class which are responsible for Locking and Unlocking of the drawerLayout.
...
fun lockNavDrawer() {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
}
fun unLockNavDrawer() {
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
}
...
There are some fragments which need to have the navigation drawer. Everything is working fine except those fragments which not suppose to have the Drawer.
So, in my base fragment, and I'm implementing an interface (HasToolbar) which have a variable called hasNavigationDrawer and in the onDestroyView of the base fragment, I'm doing this. The locking of the Drawer is fine.
if (this is HasToolbar) {
if (hasNavigationDrawer) {
activity.lockNavDrawer()
}
unregisterListeners()
}
But when I do this in the onViewCreated of the base fragment
if (this is HasToolbar) {
if (hasNavigationDrawer) {
activity.unlockNavDrawer()
}
}
It does not unlock the drawer hence it does not open the drawer inside the fragment which suppose to have the drawer.
If I do this inside a fragment it works it stays unlocked.
lockNavDrawer()
unLockNavDrawer()
If you look at the ordering of the callbacks, you'll see that the onViewCreated() of the new Fragment is called before the onDestroyView() of the old Fragment. This makes particular sense when it comes to animations since to smoothly animate between two Fragments, both the new View and the old View must coexist and only after the animation ends does the old Fragment get destroyed. Fragments, in order to be consistent, use the same ordering whether you have an animation or not.
Ideally, your Fragments shouldn't be reaching up to the Activity at all to affect the Activity's behavior. As per the Listen for Navigation Events documentation, you should instead use an OnDestinationChangedListener to update your Activity's UI. The listener only receives information about the destination and its arguments however, so to use this approach you'd want to add an argument to your navigation graph:
<!-- this destination leaves the drawer in its default unlocked state -->
<fragment
android:id="#+id/fragment_with_drawer"
android:name=".MainFragment" />
<!-- the drawer is locked when you're at this destination -->
<fragment
android:id="#+id/fragment_with_locked_drawer"
android:name=".SettingsFragment">
<argument
android:name="lock_drawer"
android:defaultValue="true"/>
</fragment>
Then your OnDestinationChangedListener could look like
navController.addOnDestinationChangedListener { _, _, arguments ->
if(arguments?.getBoolean("lock_drawer", false) == true) {
lockNavDrawer()
} else {
unlockNavDrawer()
}
}

Categories

Resources