How can I add startDestination programmatically accoridng the value? - android

How can I add startDestination programmatically according the value for example if the integer variable was 0 then I want to start navigation_main_countries_fragment, Otherwise navigation_main_categories_fragment.
Inside activity_main
<androidx.fragment.app.FragmentContainerView
android:id="#+id/activity_main_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:navGraph="#navigation/navigation_main" //I deleted this line already
android:layout_weight="1" />
Inside navigation_main
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#id/navigation_main_countries_fragment" //I deleted this line already
tools:ignore="InvalidNavigation">
<fragment
android:id="#+id/navigation_main_countries_fragment"
android:name="com.test.app.fragments.CountriesFragment"
tools:layout="#layout/fragment_countries" />
<fragment
android:id="#+id/navigation_main_categories_fragment"
android:name="com.test.app.fragments.CategoriesFragment"
tools:layout="#layout/fragment_categories" />
</navigation>

Try this code
// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController.navInflater.inflate(R.navigation.nav_graph)
if(value == 0)
graph.startDestination = R.id.counriesFragment
else
grapgh.startDestination = R.id.categoryFragment
navHost.navController.graph = graph
NavigationUI.setupActionBarWithNavController(this, navHost.navController)

You can use below code for start destination
val navHostFragment = (supportFragmentManager.findFragmentById(R.id.home_nav_fragment) as NavHostFragment)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_main)
graph.startDestination = R.id.fragment1
navHostFragment.navController.graph = graph

Related

BottomNavigationBar not working properly after navigate to navigation fragment by cutom button click

Take a Bottom Navigation activity from android studio template. there are 3 fragment with 3 item in BottomNavBar (HomeFragment, DashboardFragment, NotificationsFragment) navigate to DashboardFragment from HomeFragment by a button click. after that home item click from BottomNavBar should open Homefragment. But not working as expected.
Go to DashboardFrament from HomeFrament by
textView.setOnClickListener {
findNavController().navigate(R.id.navigation_dashboard)
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
navView.setupWithNavController(navController)
}
}
main_activity.xml
<?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"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="?attr/actionBarSize">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="#menu/bottom_nav_menu" />
<fragment
android:id="#+id/nav_host_fragment_activity_main"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFramgent.kt
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val textView: TextView = view.findViewById(R.id.text_home)
textView.setOnClickListener {
findNavController().navigate(R.id.navigation_dashboard)
}
}
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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/navigation_home">
<fragment
android:id="#+id/navigation_home"
android:name="com.app.bottomnav.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" />
<fragment
android:id="#+id/navigation_dashboard"
android:name="com.app.bottomnav.ui.dashboard.DashboardFragment"
android:label="#string/title_dashboard"
tools:layout="#layout/fragment_dashboard" />
<fragment
android:id="#+id/navigation_notifications"
android:name="com.app.bottomnav.ui.notifications.NotificationsFragment"
android:label="#string/title_notifications"
tools:layout="#layout/fragment_notifications" />
</navigation>
Whenever you are navigating to any root destination from another fragment, you should have to clear the previous stack using popUpTo option builder.
Update your code navigation code in your home fragment.
findNavController()
.navigate(R.id.navigation_dashboard,
null,
NavOptions.Builder()
.setPopUpTo(R.id.navigation_home, true)
.build()
)
I don't know what is the best solution for that problem but here is a workaround:
In your activity you add public method:
fun navigateToNavBarDestination(destinationId: Int) {
binding.navView.setSelectedItemId(destinationId)
}
And in your fragments referenced in BottomNavigationView instead calling NavController::navigate you navigate in that way, lets say to dashboard:
fun navigateToDashboard() {
(activity as? MainActivity)?.navigateToNavBarDestination(R.id.dashboard)
}
Its not the perfect solution but works.
Add these lines in your MainActivity onCreate.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_dashboard,
R.id.navigation_notifications
))
setupActionBarWithNavController(navController, appBarConfiguration)

Using navigation component with bottom navigation bar by using FragmentContainerView tag

I have already looked this question: FragmentContainerView using findNavController about this problem. But however I stiil couldn't solve the problem. It never opens other fragments. I thought maybe it is because of view binding but I tried to do it again without using view binding. It doesn't open still.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
private lateinit var navHostFragment: NavHostFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
binding.bottomNavigationView.setupWithNavController(navController)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.home -> {
navHostFragment.navController.popBackStack()
return true
}
}
return super.onOptionsItemSelected(item)
}
}
activity_main.xml
<?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/main_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_main" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="56dp"
app:menu="#menu/menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
nav_graph_main
<?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_graph_main"
app:startDestination="#id/fragmentLibrary">
<fragment
android:id="#+id/fragmentLibrary"
android:name="com.project.biosec.FragmentLibrary"
android:label="fragment_library"
tools:layout="#layout/fragment_library" />
<fragment
android:id="#+id/fragmentTransaction"
android:name="com.project.biosec.FragmentTransaction"
android:label="fragment_transaction"
tools:layout="#layout/fragment_transaction" />
<fragment
android:id="#+id/fragmentAccount"
android:name="com.project.biosec.FragmentAccount"
android:label="fragment_account"
tools:layout="#layout/fragment_account" >
<action
android:id="#+id/action_fragmentAccount_to_changeSignatureFragment"
app:destination="#id/changeSignatureFragment" />
<action
android:id="#+id/action_fragmentAccount_to_securityFragment"
app:destination="#id/securityFragment" />
<action
android:id="#+id/action_fragmentAccount_to_helpFragment"
app:destination="#id/helpFragment" />
<action
android:id="#+id/action_fragmentAccount_to_termsFragment"
app:destination="#id/termsFragment" />
</fragment>
<fragment
android:id="#+id/changeSignatureFragment"
android:name="com.project.biosec.ChangeSignatureFragment"
android:label="fragment_change_signature"
tools:layout="#layout/fragment_change_signature" />
<fragment
android:id="#+id/securityFragment"
android:name="com.project.biosec.SecurityFragment"
android:label="fragment_security"
tools:layout="#layout/fragment_security" />
<fragment
android:id="#+id/helpFragment"
android:name="com.project.biosec.HelpFragment"
android:label="fragment_help"
tools:layout="#layout/fragment_help" />
<fragment
android:id="#+id/termsFragment"
android:name="com.project.biosec.TermsFragment"
android:label="fragment_terms"
tools:layout="#layout/fragment_terms" />
</navigation>
menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/feed_fragment"
android:icon="#drawable/ic_library"
android:title="#string/library" />
<item
android:id="#+id/messages_fragment"
android:icon="#drawable/ic_assignment"
android:title="#string/transactions" />
<item
android:id="#+id/profile_fragment"
android:icon="#drawable/ic_account"
android:title="#string/account" />
</menu>
As per the Setting up bottom navigation guide:
Note: Setting up bottom navigation requires that you also set up your navigation graph and menu xml as described in Tie destinations to menu items.
That section specifically states that the android:id of your destination in your navigation graph XML file needs to match the android:id of the menu item in your menu XML file.
In your case, your navigation XML uses android:id="#+id/fragmentLibrary", android:id="#+id/fragmentTransaction", and android:id="#+id/fragmentAccount", so your menu items should change to use those same IDs.

Is it possible to set startDestination conditionally using Android Navigation Architecture Component(Android Jetpack)?

I am using Navigation from Android Jetpack to navigate between screens.
Now I want to set startDestination dynamically.
I have an Activity named MainActivity
And two Fragments, FragmentA & FragmentB.
var isAllSetUp : Boolean = // It is dynamic and I’m getting this from Preferences.
If(isAllSetUp)
{
// show FragmentA
}
else
{
//show FragmentB
}
I want to set above flow using Navigation Architecture Component. Currently I have used startDestionation as below but it’s not fulfilling my requirement.
<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/lrf_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="#layout/fragment_a" />
</navigation>
Is it possible to set startDestination conditionally using Android Navigation Architecture Component?
Finally, I got a solution to my query...
Put below code in onCreate() method of Activity.
Kotlin code
val navHostFragment = (supportFragmentManager.findFragmentById(R.id.home_nav_fragment) as NavHostFragment)
val inflater = navHostFragment.navController.navInflater
val graph = inflater.inflate(R.navigation.nav_main)
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1)
//or
//graph.setStartDestination(R.id.fragment2)
navHostFragment.navController.graph = graph
Java code
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.home_nav_fragment); // Hostfragment
NavInflater inflater = navHostFragment.getNavController().getNavInflater();
NavGraph graph = inflater.inflate(R.navigation.nav_main);
//graph.addArgument("argument", NavArgument)
graph.setStartDestination(R.id.fragment1);
navHostFragment.getNavController().setGraph(graph);
navHostFragment.getNavController().getGraph().setDefaultArguments(getIntent().getExtras());
NavigationView navigationView = findViewById(R.id.navigationView);
NavigationUI.setupWithNavController(navigationView, navHostFragment.getNavController());
Additional Info
As #artnest suggested, remove the app:navGraph attribute from the layout. It would look something like this after removal
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/home_nav_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
In the case of a fragment tag used instead of FragmentContainerView, the above changes remain the same
Some of the APIs have changed, are unavailable or are not necessary since Akash's answer. It's a bit simpler now.
MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
NavHostFragment navHost = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_main_nav_host);
NavController navController = navHost.getNavController();
NavInflater navInflater = navController.getNavInflater();
NavGraph graph = navInflater.inflate(R.navigation.navigation_main);
if (false) {
graph.setStartDestination(R.id.oneFragment);
} else {
graph.setStartDestination(R.id.twoFragment);
}
navController.setGraph(graph);
}
activity_main.xml:
<?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">
<!-- Following line omitted inside <fragment> -->
<!-- app:navGraph="#navigation/navigation_main" -->
<fragment
android:id="#+id/fragment_main_nav_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
navigation_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- Following line omitted inside <navigation>-->
<!-- app:startDestination="#id/oneFragment" -->
<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/navigation_main"
>
<fragment
android:id="#+id/oneFragment"
android:name="com.apponymous.apponymous.OneFragment"
android:label="fragment_one"
tools:layout="#layout/fragment_one"/>
<fragment
android:id="#+id/twoFragment"
android:name="com.apponymous.apponymous.TwoFragment"
android:label="fragment_two"
tools:layout="#layout/fragment_two"/>
</navigation>
This can be done with navigation action. Because fragmentA is your start destination, so define an action in fragmentA.
Note these two fields:
app:popUpToInclusive="true" app:popUpTo="#id/fragmentA"
<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/lrf_navigation"
app:startDestination="#id/fragmentA">
<fragment
android:id="#+id/fragmentA"
android:name="com.mindinventory.FragmentA"
android:label="fragment_a"
tools:layout="#layout/fragment_a">
<action android:id="#+id/action_a_to_b"
app:destination="#id/fragmentB"
app:popUpToInclusive="true"
app:popUpTo="#id/fragmentA"/>
<fragment>
<fragment
android:id="#+id/fragmentB"
android:name="com.mindinventory.FragmentB"
android:label="fragment_b"
tools:layout="#layout/fragment_b"/>
</navigation>
When your MainActivity started, just do the navigation with action id, it will remove fragmentA in the stack, and jump to fragmentB. Seemingly, fragmentB is your start destination.
if(!isAllSetUp)
{
// FragmentB
navController.navigate(R.id.action_a_to_b)
}
this is not an answer but Just a replication of #Akash Patel answer in more clean and clear way
// in your MainActivity
navController = findNavController(R.id.nav_host_fragment)
val graph = navController.navInflater.inflate(R.navigation.nav_graph)
if (Authentication.checkUserLoggedIn()) {
graph.startDestination = R.id.homeFragment
} else {
graph.startDestination = R.id.loginFragment
}
navController.graph = graph
You can set your starting destination programmatically, however, most of the time your starting logic will consult some remote endpoint. If you don't show anything on screen your UI will look bad.
What I do is always show a Splash screen. It will determine which is the next Screen to Show.
For instance, in the picture above you can ask in the Splash Screen State if there is a saved LoginToken. In case it's empty then you navigate to the Login Screen.
Once the Login Screen is done, then you analyze the result save the Token and navigate to your Next Fragment Home Screen.
When the Back Button is Pressed in the Home Screen, you will send back a Result message to the Splash Screen that indicates it to finish the App.
To pop 1 Fragment back and navigate to another you can use the following code:
val nextDestination = if (loginSuccess) {
R.id.action_Dashboard
} else {
R.id.action_NotAuthorized
}
val options = NavOptions.Builder()
.setPopUpTo(R.id.loginParentFragment, true)
.build()
findNavController().navigate(nextDestination, null, options)

New navigation component from arch with nested navigation graph

I have one case and wish to implement it by arch navigation component. For example I have 2 Nav Graphs (main and nested). Can I call main graph from nested and how?
The point is to get the right NavController to navigate in the right graph.
Let's take this scenario as an example:
MainActivity
|- MainNavHost
|- NavBarFragment
| |- NestedNavHost
| | |-NestedContentFragment1
| | |-NestedContentFragment2
| |
| |- BottomNavigationView
|
|- LoginFragment
The main graph and the nested graph are in separate xml files: this is required, as far as I understood, because the navigations target different layout areas, so they require two different NavHosts. Each Navhost will need to reference its graph by id, which requires them to be in different resource files.
The point is that to navigate in a specific graph, we must get a reference to the right graph's owner: to do this, when calling Navigation.findNavController(view), the view argument is crucial.
Docs say that
NavHostFragments register their navigation controller at the root of their view subtree such that any descendant can obtain the controller instance through the Navigation helper class's methods
So for example, if inside NavBarFragment we write
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navController = Navigation.findNavController(view)
}
here view is a parent of the NestedNavHost (that is the nested NavHostFragment), not a descendant, meaning that findNavController will search upstream in the tree and will return the MainNavHost's NavController.
If instead we write
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val nestedNavHostFragment = childFragmentManager.findFragmentById(R.id.nestedNavHostFragment) as? NavHostFragment
navController = nestedNavHostFragment?.navController
}
where nestedNavHostFragment is the id of the FragmentContainerView in the layout, we get a reference to the correct NestedNavHost. Note the use of childFragmentManager, not parentFragmentManager.
In case you're still using the deprecated xml <fragment> tag, you can write
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val fragmentContainer = view.findViewById<View>(R.id.nestedNavHostFragment)
navController = Navigation.findNavController(fragmentContainer)
}
where nestedNavHostFragment is the id of the <fragment> tag. We get a reference to the correct NestedNavHost now, because the view we pass to findNavController belongs to the NestedNavHost's subtree.
Similarly, if you need to get a reference to the main NavController from inside a NestedContentFragment, here's what we can do:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// we can get the innermost NavController using this view,
// because we are inside its subtree:
nestedNavController = Navigation.findNavController(view)
// we can find the outer NavController passing the owning Activity
// and the id of a view associated to that NavController,
// for example the NavHostFragment id:
mainNavController = Navigation.findNavController(activity!!, R.id.mainNavHostFragment)
}
Actually you could use Global actions to navigate from a nested nav graph destination to a main nav graph destination.
Create a global action from nested nav graph to desired destination in main nav graph (highlighted in the image below)
example:
<navigation android:id="#+id/main_nav_graph"
... >
<fragment android:id="#+id/fragStart" .../>
<fragment .../>
<fragment .../>
<navigation android:id="#+id/nested_nav_graph">
...
<!-- Global Action -->
<action
android:id="#+id/action_global_start"
app:destination="#id/fragStart" />
</navigation>
</navigation>
To navigate to main graph destination use
findNavController().navigate(R.id.action_global_start)
I created an answer with the info devrocca provided. It's a full answer from scratch, i didn't skip anything if anyone ever needs.
This is the main fragment for navigation. Camera is direct destination without any nested graph, Dashboard has it's own nested graph but it's added to same backstack camera fragment is added. Home has 3 fragments with it's own nav host
MainActivity
|- MainNavHost
|- HomeNavHostFragment
| |- NestedNavHost
| |-HomeFragment1
| |-HomeFragment2
| |-HomeFragment3
|
|- nav_graph_dashboard
|
|- CameraFragment
Here is the navigation files
Main Navigation nav_graph.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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#id/main_dest">
<!-- MainFragment-->
<fragment
android:id="#+id/main_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.MainFragment"
android:label="MainFragment"
tools:layout="#layout/fragment_main">
<!-- Camera -->
<action
android:id="#+id/action_main_dest_to_cameraFragment"
app:destination="#id/cameraFragment" />
<!-- Home NavGraph -->
<action
android:id="#+id/action_main_dest_to_nav_graph_home"
app:destination="#id/nav_graph_home" />
<!-- Dashboard NavGraph-->
<action
android:id="#+id/action_main_dest_to_nav_graph_dashboard"
app:destination="#id/nav_graph_dashboard" />
</fragment>
<!-- Camera -->
<fragment
android:id="#+id/cameraFragment"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.CameraFragment"
android:label="CameraFragment" />
<!-- Home-->
<include app:graph="#navigation/nav_graph_home" />
<!-- Dashboard-->
<include app:graph="#navigation/nav_graph_dashboard" />
<!-- Global Action Start -->
<action
android:id="#+id/action_global_start"
app:destination="#id/main_dest"
app:popUpTo="#id/main_dest"
app:popUpToInclusive="true" />
</navigation>
Dashboard nested navigation graph
<?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_graph_dashboard"
app:startDestination="#id/dashboard_dest">
<fragment
android:id="#+id/dashboard_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.DashboardFragment1"
android:label="DashboardFragment1"
tools:layout="#layout/fragment_dashboard1">
<action
android:id="#+id/action_dashboardFragment1_to_dashboardFragment2"
app:destination="#id/dashboardFragment2" />
</fragment>
<fragment
android:id="#+id/dashboardFragment2"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.DashboardFragment2"
android:label="DashboardFragment2"
tools:layout="#layout/fragment_dashboard2">
</fragment>
</navigation>
And nested navigation graph with it's own NavHost nav_graph_home
<?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_graph_home"
app:startDestination="#id/home_dest">
<fragment
android:id="#+id/home_dest"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeNavHostFragment"
android:label="HomeHost"
tools:layout="#layout/fragment_home_navhost" />
<fragment
android:id="#+id/homeFragment1"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment1"
android:label="HomeFragment1"
tools:layout="#layout/fragment_home1">
<action
android:id="#+id/action_homeFragment1_to_homeFragment2"
app:destination="#id/homeFragment2" />
</fragment>
<fragment
android:id="#+id/homeFragment2"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment2"
android:label="HomeFragment2"
tools:layout="#layout/fragment_home2">
<action
android:id="#+id/action_homeFragment2_to_homeFragment3"
app:destination="#id/homeFragment3" />
</fragment>
<fragment
android:id="#+id/homeFragment3"
android:name="com.smarttoolfactory.tutorial1_3navigation_nestednavhost.blankfragment.HomeFragment3"
android:label="HomeFragment3"
tools:layout="#layout/fragment_home3" />
</navigation>
Layouts, i only add necessary ones, others are simple layouts with buttons, i add link for sample project with other navigation components samples included.
MainActivity
<?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">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/ThemeOverlay.AppCompat.ActionBar" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/main_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>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Main Fragment, this is first fragment that shown in the image used as start of main navigation
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/parentLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/btnDestCam"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Destination Camera"
app:layout_constraintBottom_toTopOf="#+id/btnNavGraphHome"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/btnNavGraphHome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nested NavHost Graph Home"
app:layout_constraintBottom_toTopOf="#+id/btnNavGraphDashboard"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnDestCam" />
<Button
android:id="#+id/btnNavGraphDashboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nested Graph Dashboard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintLeft_toRightOf="parent"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnNavGraphHome" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Layout that contains inner NavHostFragment for home navigation
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nested_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:defaultNavHost="false"
app:navGraph="#navigation/nav_graph_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MainActivity is for checking main navigation back stack, important thing here is
supportFragmentManager back stack is not updated as you navigate it's childFragmentManager even for main navigation, even if you only have one
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get NavHostFragment
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment)
// ChildFragmentManager of NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
navHostChildFragmentManager?.addOnBackStackChangedListener {
val backStackEntryCount = navHostChildFragmentManager.backStackEntryCount
val fragments = navHostChildFragmentManager.fragments
}
}
}
Fragment that contains Home navigation's host
class HomeNavHostFragment : BaseDataBindingFragment<FragmentHomeNavhostBinding>() {
override fun getLayoutRes(): Int = R.layout.fragment_home_navhost
private var navController: NavController? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val nestedNavHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment) as? NavHostFragment
navController = nestedNavHostFragment?.navController
navController?.navigate(R.id.homeFragment1)
listenBackStack()
}
private fun listenBackStack() {
// Get NavHostFragment
val navHostFragment =
childFragmentManager.findFragmentById(R.id.nested_nav_host_fragment)
// ChildFragmentManager of the current NavHostFragment
val navHostChildFragmentManager = navHostFragment?.childFragmentManager
navHostChildFragmentManager?.addOnBackStackChangedListener {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
val fragments = navHostChildFragmentManager!!.fragments
Toast.makeText(
requireContext(),
"HomeNavHost backStackEntryCount: $backStackEntryCount, fragments: $fragments",
Toast.LENGTH_SHORT
).show()
}
val callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val backStackEntryCount = navHostChildFragmentManager!!.backStackEntryCount
Toast.makeText(
requireContext(),
"HomeNavHost backStackEntryCount: $backStackEntryCount",
Toast.LENGTH_SHORT
).show()
if (backStackEntryCount == 1) {
OnBackPressedCallback# this.isEnabled = false
requireActivity().onBackPressed()
} else {
navController?.navigateUp()
}
}
}
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}
}
There is one thing i don't know if it's improved in graph or code with nested NavHostFragment
If you set start destination of nav_graph_home HomeFragment1 instead of HomeNavHostFragment it works as dashboard which ignores nested NavHost and added to main back stack of fragments.
Since you are in inner NavHostFragment findNavController() in any home fragment returns the inner one
class HomeFragment3 : BaseDataBindingFragment<FragmentHome3Binding>() {
override fun getLayoutRes(): Int = R.layout.fragment_home3
private var count = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dataBinding.btnIncrease.setOnClickListener {
dataBinding.tvTitle.text = "Count: ${count++}"
}
val mainNavController =
Navigation.findNavController(requireActivity(), R.id.main_nav_host_fragment)
dataBinding.btnGoToStart.setOnClickListener {
// 🔥Using destination belong to main_nav_host with nested navHost causes app to crash
// findNavController().navigate(R.id.action_global_start)
mainNavController.navigate(R.id.action_global_start)/**/
}
}
}
You can also use global action but it's not required since back navigation in inner navHost directly moves you back to main navigation if you don't use OnBackPressed.
Link for full example and the other nav component samples if you are interested.
Actually is working,
using
val host: NavHostFragment? = (childFragmentManager.findFragmentById(R.id.main_app_fragment_container) as NavHostFragment?)
I can navigate from main fragment
I found a temporary solution to the problem of inner NavController being covered.
You can use custom NavHostFragment which provides you with desired navController.
My code:
<androidx.fragment.app.FragmentContainerView
...
android:name="MyNavHostFragment"
app:defaultNavHost="false"
app:navGraph="#navigation/inner_nav">
...
</androidx.fragment.app.FragmentContainerView>
...
class MyNavHostFragment: NavHostFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MainFragment.innerNavController = navController
}
}
...
class MainFragment : Fragment() {
companion object{
lateinit var innerNavController: NavController
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bottomNavigationView =
view!!.findViewById<BottomNavigationView>(R.id.bottom_navigation_view)
bottomNavigationView.setupWithNavController(innerNavController)
}
}
we can achieve it by finding the root navhost controller and then navigating through root nav host controller
val Fragment.findRootNavHost: NavController?
get() = this.activity?.let {
Navigation.findNavController(it, your_root_fragment_id)
}
findRootNavHost?.navigate(`your_destination_fragment_id`)
Kindly check the medium article link
Github repo for the same

IllegalStateException: Link does not have a NavController set

I'm using Android Navigation Component for Navigation. I have a LoginFragment which has a button to transition to SignUpFragment. On clicking the button I'm getting this error.
java.lang.IllegalStateException: View android.support.v7.widget.AppCompatButton{49d9bd1 VFED..C.. ...P.... 201,917-782,1061 #7f090172 app:id/signUpLink} does not have a NavController set
Here is my nav_graph.xml
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="#id/loginFragment">
<fragment
android:id="#+id/loginFragment"
android:name="org.fossasia.openevent.app.core.auth.login.LoginFragment"
android:label="login_fragment"
tools:layout="#layout/login_fragment">
<action
android:id="#+id/action_loginFragment_to_signUpFragment"
app:destination="#id/signUpFragment" />
</fragment>
</navigation>
Here is the code in LoginFragment for Navigation -
binding.signUpLink.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_loginFragment_to_signUpFragment, null));
Here is extract from activity layout file for NavHostFragment -
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:name="android.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/main_navigation"
app:defaultNavHost="true"/>
Officially recommended solution
Currently using the FragmentContainerView is not very friendly, you have to access it from the supportFragmentManager:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
In my xml my FragmentContainerView looks like this:
<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:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="parent"
app:navGraph="#navigation/nav_graph"
/>
This has been tested with androidx navigation version 2.3.0
I've removed the old answer because it has been clarified by Google devs that it is not the recommended solution anymore. Thanks #Justlearnedit, for posting a comment allowing me to update this issue.
UPDATED SOLUTION
Actually, Navigation can't find NavController in FrameLayout. So replacing <FrameLayout> with <fragment> will make it work.
Add the following inside the <fragment> tag -
android:name="androidx.navigation.fragment.NavHostFragment"
After doing the changes, the code will look similar to this -
<fragment
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/main_navigation"
app:defaultNavHost="true"/>
After doing some research I found this Issue also on Google's bugtracker.
So here's the official solution in Java:
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
NavController navCo = navHostFragment.getNavController();
and here in Kotlin:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
The reason is that the fragment view isn't available inside the Activity.onCreate() method if you're adding it using FragmentContainerView (or just a FrameLayout). The proper way to get the NavController in this case is to find the NavHostFragment and get the controller from it. See the issue and the explanation.
override fun onCreate(savedInstanceState: Bundle?) {
...
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_fragment_container_view_id) as NavHostFragment
val navController = navHostFragment.navController
}
Don't use <fragment> instead of <androidx.fragment.app.FragmentContainerView> as some other answers suggest. See the comment from the Google team in the issue I mentioned above.
You should always use FragmentContainerView. There are absolutely other fixes around window insets and layout issues that occur when a fragment's root layout is directly within other layouts such as ConstraintLayout, besides the underlying issues with the tag where fragments added via that tag go through lifecycle states entirely differently from the other Fragments added to the FragmentManager. The Lint check is there exactly because you absolutely should switch over to FragmentContainerView in all cases.
There's also an announcement from AndroidDevSummit 2019 which explains why FragmentContainerView was introduced, and another thread on SO about the difference: <androidx.fragment.app.FragmentContainerView> vs as a view for a NavHost
Just replace <FrameLayout> With <fragment> and replace android:name="org.fossasia.openevent.app.core.auth.login.LoginFragment" with android:name="androidx.navigation.fragment.NavHostFragment"
In my case it was done by android studio...
<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:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
The above code is the working code that was replaced as shown below by a warning!
<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_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="#navigation/mobile_navigation" />
I faced the same problem.
So,instead of this,
binding.signUpLink.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_loginFragment_to_signUpFragment, null));
I used my NavHostFragment to find the NavHostFragment:
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Fragment navhost = getSupportFragmentManager().findFragmentById(R.id.fragment2);
NavController c = NavHostFragment.findNavController(navhost);
c.navigate(R.id.firstFragment);
}
});
fragment2 is navhostfragmentID.
In Java try this below line:
Navigation.findNavController(findViewById(R.id.nav_host_fragment)).navigate(R.id.first_fragment);
Use the view of fragment such as onViewCreated
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navController = Navigation.findNavController(view)
binding.signUpLink.setOnClickListener {
navController.navigate(R.id.action_loginFragment_to_signUpFragment)
}
I had the following error:
MainActivity#9ff856 does not have a NavController set on 2131230894
I was using Bottom Navigation View:
The following worked for me:
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragment)
val navController = navHostFragment?.findNavController()
if (navController != null) {
bottomNavigationView.setupWithNavController(navController)
}
A weird thing happens to me, below code snippet was working on normal flow from Fragment1 to fragment2, but after coming to fragment1 and on again navigate fragment2, this was throwing the "Navigation controller not set for the view" error.
binding.ivIcon.setOnClickListener(v -> {
Openfragment2(v);});
private void Openfragment2(View view) {
Navigation.findNavController(binding.ivIcon).navigate(R.id.fragment2);
}
Here problem was in view, in findNavController need to pass the onclicked view.
private void Openfragment2(View view) {
Navigation.findNavController(view).navigate(R.id.fragment2);
}
i faced this issue just now, but i was sure about my code and then realized that i have changed the location of the fragment from under the main package to another folder
so
i solved the issue with Build-> clean then Build ->make project to let the IDE to change its Directions class
this code should work properly for kotlin
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_loginFlow_fragment) as NavHostFragment
val navController = navHostFragment.navController
navController.navigate(R.id.to_dictionaryDownload1)
In my case I change my code
val action =
StartFragmentDirections.actionStartFragmentToLoginFragment()
Navigation.findNavController(view).navigate(action);
In onViewCreated()
in my case (I was using BottomNavigation), in findNavController I was adding Framlayout Id! you should add the fragment Id like this:
in my xml:
<FrameLayout
android:id="#+id/container_orders"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/bnv_main"
android:visibility="gone">
<fragment
android:id="#+id/nav_orders"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="false" />
</FrameLayout>
in Kotlin class:
private val navOrdersController by lazy {
mainActivity.findNavController(R.id.nav_orders).apply {
graph = navInflater.inflate(R.navigation.main_navigation_graph).apply {
startDestination = startDestinations.getValue(R.id.action_history)
}
}
}
private lateinit var binding : ActivityNewsBinding
lateinit var viewModel: NewsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(R.layout.activity_news)
binding = ActivityNewsBinding.inflate(layoutInflater)
setContentView(binding.root)
val newsrepository = NewsRepository(ArticleDatabase(this))
val viewModelProviderFactory = NewsViewModelProviderFactory(newsrepository)
viewModel = ViewModelProvider(this,viewModelProviderFactory).get(NewsViewModel::class.java)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.newsNavHostFragment) as NavHostFragment
val navController = navHostFragment.navController
binding.bottomNavigationView.setupWithNavController(navController)
}
Updated answer for FragmentViewContainer
Make sure you have
android:name="androidx.navigation.fragment.NavHostFragment"
instead of the fragment you want as start fragment.
(Start destination is defined in the Nav Graph).
For this to work you have to override both onCreatedView and onViewCreated in all the fragments
my code:
`
<FrameLayout
android:id="#+id/fl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/main_nav_graph"
app:defaultNavHost="true"/>
</FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="#menu/bottom_nav_menu" />

Categories

Resources