How to use the Navigation Graph without loading a fragment by default? - android

I need to use the navigation component and have it already set up. The navigation graph requires a startDestination which loads that fragment by default upon creation. However, I have a fragment which I only want to show after an event is triggered within the existing screen. How could I leave the fragment container empty until I'm ready to navigate to it?

in your xml file declare your container as empty navigation host
<fragment
android:id="#+id/container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
then in your activity you can set your graph progmatically
val navHost = NavHostFragment()
fragmentManager.beginTransaction()
.replace(R.id.container, navHost)
.commit()
val graph = navHost.navController.navInflater.inflate(R.navigation.my_navigation)
graph.startDestination = R.id.my_fragment
navHost.navController.graph = graph
you can check this link

Related

Empty state of NavHostFragment of Android Navigation Component

Background
In app I have couple of NavHostFragments that are displayed in different parts of screen:
defaultNavHost is used for switching between tabs
inside one of fragments of defaultNavHost, I have another navHost for BottomSheets
There is a need for another navHost that could display fragments on top of everything else (above tabs, above both of other navHosts). Imagine a tablet layout, that displays a fragment on top of another fragment, just by covering 50% of it.
Issue
A secondary NavHostFragment, as declated below, works as expected when setting nav graph to it.
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false" />
I set the graph and start destination programmatically:
val navController = findNavController(R.id.fragmentContainerView)
val graph = navController.navInflater.inflate(R.navigation.navigation_additional)
graph.setStartDestination(R.id.navigation_additional_destination_5)
navController.graph = graph
What would be a correct approach to hide navController or navGraph? I want it to dissapear when I do navController.popBackStack(), but it does not work. If I just set fragmentContainerView to gone then fragment will still remain in that NavHostFragment and dissapear animation will not happen. What could be the correct approach to do this? Should the startDestination point to an empty fragment? Or is there a different architectural approach I should be using?

How to go from one fragment to another fragment in kotlin?

I have a button in fragment A and when I click on fragment A, I want it to be redirected to fragment B. How to achieve this in kotlin? Right now I am using this code but app crash.
btntest.setOnClickListener {
var intent = Intent(view.context, FragmentB::class.java)
startActivity(intent)
}
I'd suggest you take a look on the navigation component. First, you create a Navigation resource file (usually referred to as Nav Graph), where you add the fragments you wish to connect.
Make sure to give an ID to each fragment (that's what you'll use to navigate between them later). Additionally, you can use app:startDestination in order to set the first fragment to be shown.
<?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"
app:startDestination="#id/blankFragment">
<fragment
android:id="#+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="#string/label_blank"
tools:layout="#layout/fragment_blank" />
</navigation>
Then, in the activity/fragment that will be hosting the fragments, add a FragmentContainerView and set the graph to the one you created.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>
Finally, in order to navigate between fragments, use this in the Activity that is hosting the navigation (use getActivity() if a Fragment is hosting the navigation).
// as per defined in your FragmentContainerView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
// Navigate using the IDs you defined in your Nav Graph
navController.navigate(R.id.blankFragment)
Source: https://developer.android.com/guide/navigation/navigation-getting-started
Edit: if you want to access the controller within your Fragment, use activity?.supportFragmentManager to get the fragment manager.
Try this in From Fragment, after you have set action in nav graph xml.
findNavController().navigate(R.id.action_from_fragmentA_to_fragmentB)
set in adapter click listner
loadFragment(SettingFragment())
private fun loadFragment(homeFragment: SettingFragment) {
val transaction = (context as AppCompatActivity).supportFragmentManager.beginTransaction()
transaction.replace(R.id.container,homeFragment)
transaction.addToBackStack(null)
transaction.commit()
}

<androidx.fragment.app.FragmentContainerView> vs <fragment> as a view for a NavHost

When using androidx.fragment.app.FragmentContainerView as a navHost instead of a regular fragment app is not able to navigate to a destination after orientation change.
I get a following error:
java.lang.IllegalStateException: no current navigation node
Is there a gotcha that I should know about to use it properly or is my way of using nav components is incorrect?
Simple activity xml with a view:
...
<androidx.fragment.app.FragmentContainerView
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:layout_behavior="#string/appbar_scrolling_view_behavior"
app:navGraph="#navigation/nav_simple" />
...
Navigation code:
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_legislator.xml"
app:startDestination="#id/initialFragment">
<fragment
android:id="#+id/initialFragment"
android:name="com.example.fragmenttag.InitialFragment"
android:label="Initial Fragment"
tools:layout="#layout/initial_fragment">
<action
android:id="#+id/action_initialFragment_to_destinationFragment"
app:destination="#id/destinationFragment" />
</fragment>
<fragment
android:id="#+id/destinationFragment"
android:name="com.example.fragmenttag.DestinationFragment"
android:label="Destination Fragment"
tools:layout="#layout/destination_fragment" />
</navigation>
Here is a github repo where you can easily reproduce a bug: https://github.com/dmytroKarataiev/navHostBug
The no current navigation node error occurs when there's no graph set and you attempt to call navigate(). If it only occurs when you're using FragmentContainerView and after a configuration change, then this would be related to this bug, which is fixed and scheduled for release with Navigation 2.2.0-rc03.
To work around this issue, you can either switch back to <fragment> or remove app:navGraph="#navigation/nav_simple" and instead call navController.setGraph(R.navigation.nav_simple).
I do not see any explanation why replace Fragment view with FragmentContainerView. This is why I added this answer (source below).
One of the common patterns to host fragments in an activity is to use a FrameLayout. The Android community have been doing this for years but this is going to change now. The androidx team introduced this new view called FragmentContainerView to do this for you.
All you have to do is replace your FrameLayout with FragmentContainerView and everything should work out of the box, you won’t need to change anything on the way you handle fragment transactions.
The benefits gained here is the improved handling of what’s called the z-ordering for fragments. Here’s the example they used, but basically this means , for example, that the exit and enter transitions between two fragments will not overlap each other. Instead this FragmentContainerView is going to start the exit animation first then the enter animation.
If you are asking can this replace <fragment> tag? then the answer is yes. All you have to do is to add android:name="your_class_name" when defining FragmentContainerView and it will use a FragmentTransaction under the hood to build and show your fragment. This mean you can use fragment transactions later on to replace it.
Source
Android Docs
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.frag_splash, container, false)
}
Maybe you forgot the 3rd "false" parameter above. Only this three parameters onCreateView fun can be accept.
android developer website
pic from : https://developer.android.com/reference/androidx/fragment/app/FragmentContainerView
// drawer layout that I pass is the id of the layout in the activity from which I move to fragment//
SacnFragment sacnFragment = new SacnFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.drawer_layout, sacnFragment);
transaction.addToBackStack(null);
transaction.commit();

Android Navigation: Access NavHostFragment from same Fragment

Currently I have the following scenario: a user must sign in to use the app. This means I've used 2 nav_graphs, a main graph for everything and then a nested home graph for the views after you have signed in.
After signing in, a bottom navigation bar should appear to change tabs in the home graph.
I have the following home_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<fragment
android:id="#+id/home_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/home_graph"
app:defaultNavHost="true"/>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation"
app:menu="#menu/navigation_items"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And I want to be able to change tabs in the bottom navigation view, so I setup this logic in HomeFragement.kt using bottomNavigationView.setOnNavigationItemSelectedListener.
Unfortunetly, when I try to fetch the home_nav_host_fragment in home_fragment.xml I cannot because homeNavController = findNavController() in the fragment can only find the main_nav_host_fragment that's in the main activity.
I want findNavController() to return the home_nav_host_fragment instead but because this method only searches for parent NavControllers and not ones on the same level it cannot find the one I want.
Is there a better structure that will provide a solution to this issue? Thanks
This isn't the correct approach. Instead, you should listen for navigation events and hide the BottomNavigationView when you're on the login graph:
navController.addOnDestinationChangedListener { _, destination, _ ->
if(destination.parent!!.id == R.id.login_graph) {
toolbar.visibility = View.GONE
bottomNavigationView.visibility = View.GONE
} else {
toolbar.visibility = View.VISIBLE
bottomNavigationView.visibility = View.VISIBLE
}
}
Then, you can use just a single graph, following the best practices for user login.
Better use popUpTo combined with popUpToInclusive to create a one-way navigation action - in order to use a single one graph, instead of two disconnected graphs. This only should be applied to the first 1-2 fragments in the graph, so that eg. the back button cannot navigate back to the splash-screen or to the login-screen, because these actions do not count towards "the expected behavior".
<fragment
android:id="#+id/splashFragment"
android:name="com.acme.fragment.SplashFragment"
tools:layout="#layout/fragment_splash"
android:label="fragment_splash">
<action
android:id="#+id/action_splashFragment_to_loginFragment"
app:destination="#id/loginFragment"
app:popUpTo="#id/splashFragment"
app:popUpToInclusive="true"/>
</fragment>

State of nested Fragments is lost when returned to same tab in 'Navigation Architecture Component'

I'm exploring the the 'Navigation Architecture Component' concept which introduced in Google I/O 2018 last month.
Let say I have an activity with a bottom navigation view and a 'fragment' to host all fragments:-
<android.support.constraint.ConstraintLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<fragment
android:id="#+id/root_form"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/nav_graph"
app:defaultNavHost="true"
/>
<android.support.design.widget.BottomNavigationView
android:id="#+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/navigation" />
</android.support.constraint.ConstraintLayout>
And I have 2 actions/tabs in my bottom navigation view which will show FragmentA and FragmentB respectively. FragmentB have a nested (child) fragment FragmentC which can be open from FragmentB.
in my Activity class I link the bottom navigation view with the navigation controller using the 'setupWithNavController()' function.
(Kotlin extension function that included in ' androidx.navigation.ui' package) :-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bottom_navigation.setupWithNavController(findNavController(R.id.root_form))
}
Everything works fine as expected.
But incase user navigate to FragmentA while he is in FragmentC and come back agin to same tab then he will only get the FragmentB not the fragmentC. Its a common pattern in bottom navigation when user return to same tab it should show the last child/nested fragment he was viewing.
I know we can handle this if we have our own fragment transaction but How to resolve this issue in 'Navigation Architecture Component' domain?
You can use a container fragment which holds the viewpager and is the navigation location from parent. When you your in A or B you can hit C and then when you go back it will go back to parent which will still be at which ever fragment tab you were at.
The nav graph never knows about Frag A or B but it knows about the container.
You can slove this issue by updateing the action inside navGraph file.
Just specify popUpTo to the parent fragment (FragmentB) and also set popUpToInclusive to true
<fragment
android:id="#+id/FragmentB"
tools:layout="#layout/fragment_b">
<action
android:id="#+id/root_form"
app:destination="#id/FragmentC"
app:popUpTo="#id/FragmentB"
app:popUpToInclusive="true" />
</fragment>

Categories

Resources