in the fragment which i made it host in the tag fragment in activity when i use navController = Navigation.findNAvController(view) the app crashes by the error:
View does not have a navController set.
this is nav_graph:
<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/mainFragment">
<fragment
android:id="#+id/mainFragment"
android:name="studio.apptick.mouj.view.fragments.MainFragment"
tools:layout="#layout/fragment_main"
android:label="fragment_main" >
<action
android:id="#+id/action_mainFragment_to_playlistActivityFragment"
app:destination="#id/playlistActivityFragment" />
<action
android:id="#+id/action_mainFragment_to_searchActivity"
app:destination="#id/searchActivity" />
</fragment>
<activity
android:id="#+id/searchActivity"
android:name="studio.apptick.mouj.view.activity.SearchActivity"
android:label="activity_search"
tools:layout="#layout/activity_search" />
</navigation>
this is fragment tag in activity:
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="#id/view_player"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph">
</fragment>
If you want navController in activity you can find it via
navController = Navigation.findNavController(Activity, R.id.nav_host_fragment)
or in a fragment
navController = NavHostFragment.findNavController(Fragment)
or
navController = Navigation.findNavController(View)
I was facing the same issue, until I realized that we have to setup navControol onPostCreate function like this
#Override
public void onPostCreate(#Nullable Bundle savedInstanceState, #Nullable PersistableBundle persistentState) {
super.onPostCreate(savedInstanceState, persistentState);
BottomNavigationView navigationView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_memories, R.id.navigation_account)
.build();
NavController navController = Navigation.findNavController(this, R.id.main_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
}
I got today alike error
ava.lang.IllegalStateException: Activity
com.example.roomtutorial.MainActivity#2a8d7bb does not have a
NavController set on 2131231116
I get this error pointing on setupActionBarWithNavController(findNavController(R.id.mainFragment))
So the problem was in XML
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
etc..... I delt with a problem by changing <androidx.fragment.app.FragmentContainerView to <fragment
Someone who designs the option really messed it up.
You can use the Kotlin Extension Function to navigate to the destination.
findNavController().navigate(R.id.action_homeFragment_to_destinationFragment)
Also, make sure you use to replace the fragment tag with FragmentContainerView as recommended by android.
<androidx.fragment.app.FragmentContainerView
android:id="#+id/my_nav_host_fragment"
android:layout_width="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph"
android:layout_height="match_parent"/>
Initiate the navController after view created in case of fragment
// ....
lateinit var navController: NavController
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
//...
}
I use to this solution in my projects.
navController = (supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
navController.addOnDestinationChangedListener { controller, destination, arguments ->
when(destination.id){
R.id.genre->{ materialToolbar.title = getString(R.string.app_name) }
R.id.search->{ materialToolbar.title = getString(R.string.search) }
R.id.favorites->{ materialToolbar.title = getString(R.string.favorites) }
}
}
You can simply use post method on your nav host fragment view:
nav_host_fragment.post {
findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener { _, des, _ ->
when(des.id){
//your conditions here
}
}
}
If you get navController via
NavController navController = Navigation.findNavController(Activity, R.id.nav_host_fragment);
Solution 1
Get navController via the following method
NavHostFragment navHostFragment =
(NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
With layout:
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="#navigation/nav_graph"/>
It is fully compliant with Documentation
Solution 2
Simply changing <androidx.fragment.app.FragmentContainerView to <fragment
It works for me, but I don't find any DOC associate to it.
Hope that helps!
I had the same issue and found that problem was in my activity class which was not configured as it done when create activity with fragments with help of the Basic Activity. Try to create Basic activity and see if created activity class is similar to your initial class with problem
Related
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)
This question already has answers here:
android navigation component up button, arrow displays but popTo not working
(2 answers)
Closed 1 year ago.
I have an AppCompatActivity with a FragmentContainerView and a BottomNavigationView.
When I navigate from a fragment_A to the fragment_B(which are not in the bottom menu) a back button appears, the problem is that the back button closes the app instead of going back to the previous fragment_A.
My 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"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/homeToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll|enterAlways|snap" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment_activity_home"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/nav_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/appBarLayout"
app:navGraph="#navigation/mobile_navigation" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom"
app:layout_behavior=".ui.behaviour.TestBehavior"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>
And this is the kotlin code:
class HomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityHomeBinding
private val viewModel: HomeActivityViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.homeToolbar)
val navView: BottomNavigationView = binding.navView
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_home) as NavHostFragment
val navController = navHostFragment.navController
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_fragment_a,
R.id.navigation_notifications
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
This is the action xml:
<action
android:id="#+id/navigate_to_fragment_b"
app:destination="#id/fragment_b"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_right"
app:popExitAnim="#anim/slide_out_left" />
This the code I use to obtain the navController reference:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_home) as NavHostFragment
val navController = navHostFragment.navController
And this is the navigation function that i call from the fragment_A:
navController.navigate(R.id.navigate_to_fragment_b)
I tried to override the onBackPressed() in my activity but it's never gets called.
Update
I have confused the back button with the up button.
In my case back button returns to previous fragment but up button close the app.
So my problem is with the UP BUTTON.
UP button appears only when I navigate to the fragment_B, and referring to this I would say that there is something that does not work because it closes the app for me.
The Up button never exits your app
If a user is at the app's start destination, then the Up button does not appear, because the Up button never exits the app. The Back button, however, is shown and does exit the app.
Thanks to ianhanniballake for the solution in this answer
// Java
#Override
public boolean onSupportNavigateUp() {
return Navigation.findNavController(this, R.id.main_fragment).navigateUp()|| super.onSupportNavigateUp();
}
// Kotlin
override fun onSupportNavigateUp(): Boolean {
return Navigation.findNavController(this, R.id.main_fragment).navigateUp() || super.onSupportNavigateUp()
}
I'm making slidable menu and hamburger icon/sliding not working. I'm also not sure if this working how i think: when we are setting FragmentContainerView like this
<androidx.fragment.app.FragmentContainerView
app:navGraph="#navigation/nav_graph"
We are refer to Fragment set in navigation manager, am I right?
Code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, EggTimerFragment.newInstance())
.commitNow()
}
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_graph) as NavHostFragment
val navController = navHostFragment.navController
findViewById<NavigationView>(R.id.nav_view)
.setupWithNavController(navController)
val appBarConfiguration = AppBarConfiguration(navController.graph, binding.drawerLayout)
NavigationUI.setupActionBarWithNavController(this, navController, binding.drawerLayout)
NavigationUI.setupWithNavController(binding.navView, navController)
binding.navView.setNavigationItemSelectedListener {
when(it.itemId){
R.id.item1 -> Toast.makeText(applicationContext, "item1 clicked", Toast.LENGTH_SHORT).show()
}
true
}
super.onCreate(savedInstanceState)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if(toggle.onOptionsItemSelected(item)){
return true
}
return onOptionsItemSelected(item)
}
XML main_activity file:
<?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.drawerlayout.widget.DrawerLayout
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".app.MainActivity">
<androidx.fragment.app.FragmentContainerView
app:navGraph="#navigation/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="#+id/myNavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
/>
<com.google.android.material.navigation.NavigationView
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:headerLayout="#layout/nav_header"
app:menu="#menu/nav_header_menu"
android:layout_gravity="start"
android:fitsSystemWindows="true"/>
</androidx.drawerlayout.widget.DrawerLayout>
</layout>
Maybe I should cut some code and move it to fragment? Android guide is too briefly and unclear for me.
Please make this following changes:
Add android:fitsSystemWindows="true" to <androidx.drawerlayout.widget.DrawerLayout>
In your MainActivity override onSupportNavigateUp() method
Initialize data Binding like this:
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
Also there is no need for this code. Please remove it. As all the navigation should be managed by navigation graph.
if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .replace(R.id.container, EggTimerFragment.newInstance()) .commitNow() }
I understand the Android Guide is too brief but you can refer to this particular section: Add a navigation drawer
I have installed the latest canary version of Android Studio, and followed this (https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing) instruction to implement a simple two page navigation. Basically page1 has a button, and when it is clicked, the app shows page2.
It works, but there is one problem... It does not seem to do anything with the action bar automatically. Is it supposed to show up/back arrow and the "Label" attribute on the action bar automatically by the navigation library? Or am I supposed to do all the work manually as before? I want to show the back arrow and "Details" on action(tool) bar when page2 is showing.
On button click at page 1.
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
button1.setOnClickListener {
val nav = NavHostFragment.findNavController(this);
nav.navigate(R.id.show_page2)
}
}
Main activity XML. By default it was the default Action Bar, I have replaced it with a ToolBar. There was no difference.
<?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.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
android:layout_width="match_parent">
</androidx.appcompat.widget.Toolbar>
<fragment
android:id="#+id/my_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_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar"
app:navGraph="#navigation/nav_graph"/>
</androidx.constraintlayout.widget.ConstraintLayout>
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/page1">
<activity
android:id="#+id/mainActivity2"
android:name="com.android.navtest.MainActivity"
android:label="activity_main"
tools:layout="#layout/activity_main"/>
<fragment
android:id="#+id/page1"
android:name="com.android.navtest.BlankFragment2"
android:label="Home page"
tools:layout="#layout/page1">
<action
android:id="#+id/show_page2"
app:destination="#id/page2"
app:enterAnim="#anim/anim1"
app:popExitAnim="#anim/anim2"/>
</fragment>
<fragment
android:id="#+id/page2"
android:name="com.android.navtest.BlankFragment"
android:label="Details"
tools:layout="#layout/page2"/>
</navigation>
You can connect your ActionBar to a NavController using NavigationUI.setupActionBarWithNavController(). This is generally done in your Activity, right after you call setSupportActionBar():
supportActionBar = findViewById<Toolbar>(R.id.toolbar)
// Get the NavController for your NavHostFragment
val navController = findNavController(R.id.nav_host_fragment)
// Set up the ActionBar to stay in sync with the NavController
setupActionBarWithNavController(navController)
This approach is covered in the Navigation talk at Google I/O 2018.
If you want to have navigation back button hidden in more than one place (default is only for home fragment) you can add ids of fragments to AppBarConfiguration and pass this as second parameter of setupActionBarWithNavController, for example:
val appBarConfiguration = AppBarConfiguration(setOf(R.id.splashFragment, R.id.onboardingFragment, R.id.homeFragment))
setupActionBarWithNavController(findNavController(R.id.nav_host), appBarConfiguration)
This is what I have done.
onSupportNavigateUp is called when the user navigates up and it set again. by calling this setupActionBarWithNavController tell android to update the title of toolbar.
navigateUp Handles the Up button by delegating its behavior to the given NavController. This should generally be called from AppCompatActivity.onSupportNavigateUp().
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityGameConfigBinding =
DataBindingUtil.setContentView(this, R.layout.activity_game_config)
supportActionBar?.show()
val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController, null)
appBarConfiguration = AppBarConfiguration.Builder(navController.graph)
.build()
NavigationUI.setupWithNavController(binding.navView, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = Navigation.findNavController(this, R.id.myNavHostFragment)
return NavigationUI.navigateUp(navController, appBarConfiguration)
}
my solution with binding - the code is in MainActivity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.main_activity)
navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
setSupportActionBar(toolbar)//needs to be after binding
toolbar.setupWithNavController(navController,AppBarConfiguration(navController.graph))
}
as for the titles - first I removed labels (android:label) from the fragments in navigation graph (label overwrites title from what I've tested)
<fragment
android:id="#+id/productListFragment"
android:name="com.example.ProductListFragment"
android:label="TO_BE_REMOVED"
tools:layout="#layout/product_list_fragment">
<action
android:id="#+id/action_productListFragment_to_mainMenuFragment"
app:destination="#id/mainMenuFragment" />
</fragment>
each fragment sets the title and subtitle in onResume, here example from ProductListFragment
override fun onResume() {
super.onResume()
val actionBar = (activity as AppCompatActivity).supportActionBar
actionBar?.title = getString(R.string.product_list_title)
actionBar?.subtitle = getString(R.string.product_list_subtitle)
}
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" />