View does not have a navController set at [duplicate] - android

I am using Navigation Component for navigating in my app. It works fine inside fragments but it fails to find the nav host in the activity that holds the actual navigation host.
I am trying to open a new fragment when the user clicks on FAB, which I included in Main activity's XML. When I call findNavController() it fails to find the controller. The nav host controller is in the XML layout. I can't understand why it fails to find it.
MainActivity
class MainActivity : AppCompatActivity(), OnActivityComponentRequest {
override fun getTabLayout(): TabLayout {
return this.tabLayout
}
override fun getFap(): FloatingActionButton {
return this.floatingActionButton
}
private lateinit var tabLayout: TabLayout
private lateinit var floatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
this.tabLayout = tabs
this.floatingActionButton = fab
fab.setOnClickListener {
it.findNavController().navigate(R.id.addNewWorkoutFragment)
}
}
}
Activity main XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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=".domain.MainActivity"
android:animateLayoutChanges="true">
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.tabs.TabItem
android:text="Test 1"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
<com.google.android.material.tabs.TabItem
android:text="Test 2"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/main_navigation" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchorGravity="right|top"
app:layout_anchor="#+id/bar"
android:src="#drawable/ic_add_black_24dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

I was getting the same exeception until I managed to get the navController this way:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_fragment_container_view_id) as NavHostFragment
val navController = navHostFragment.navController
Obtained from this answer:
Before finding the nav controller, you need to set the view, if you are using binding:
binding = BaseLayoutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
If not:
setContentView(R.layout.activity_main)
Use the corresponding binding for fragments.

Try setting up onClickListener of Fab button in onStart of the Activity as in onCreate Activity is just inflating the View and haven't set the NavHostController. So if you setup onClickListener in onStart of activity is will work as expected.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
this.tabLayout = tabs
this.floatingActionButton = fab
}
override fun onStart() {
super.onStart()
floatingActionButton.setOnClickListener {
it.findNavController().navigate(R.id.addNewWorkoutFragment)
}
}

In Java you can find NavController inside activity this way:
Navigation.findNavController(this,R.id.nav_host).navigate(R.id.YourFragment);

The problem might be that FAB was added to the activity or a fragment different from the one used by NavHost fragment. In this case, when you call it.findNavController() it cannot find the navigation controller.
You can either check that your FAB belongs to the fragment pulled by NavHost or call an Activity's findNavController(<id>) and pass it id of the fragment you're looking up
override fun onCreate(savedInstanceState: Bundle?) {
...
fab.setOnClickListener {
findNavController(R.id.nav_host_fragment)
.navigate(R.id.addNewWorkoutFragment)
}
}

Retrieve it later, in onPostCreate. like this:
#Override
public void onPostCreate(Bundle savedInstanceState ) {
super.onPostCreate(savedInstanceState);
navController = Navigation.findNavController(this, R.id.fragment_container_view);
}

Turns out that activity that holds navigation controller... doesn't have navigation component.
The solution is to manually set the NavController to each view contained in the activity.
val navController = findNavController(R.id.nav_host_fragment)
Navigation.setViewNavController(fab, navController)
Now this would work:
fab.setOnClickListener {
it.findNavController().navigate(R.id.addNewWorkoutFragment)
}
I still don't understand why this works the way it works so any explanation would be more than welcome :)
As of now, Android API simply doesn't make much sense.
Source: Navigate to fragment on FAB click (Navigation Architecture Components)

Kotlin Only
Android Navigation KTX provides an extension function for Activity
All I had to do was
findNavController(R.id.myNavHostFragment).navigate(R.id.to_someFragment)
I do have the following dependencies in my app/gradle.build.kts file (
implementation ("androidx.navigation:navigation-fragment-ktx:2.3.5")
implementation ("androidx.navigation:navigation-runtime-ktx:2.3.5")
implementation ("androidx.navigation:navigation-ui-ktx:2.3.5")
Using Directions
Alternatively you could also use generated Directions class. Since this is a global action the Directions class is found in the NavigationDirections.to_someFragment()
//very useful if you're passing args then you could do something like
//NavigationDirections.to_someFragment(myArg)
NavigationDirections.to_someFragment()findNavController(R.id.myNavHostFragment).navigate(NavigationDirections.to_someFragment())
Side-note the specific extension function is being contributed from the runtime-ktx aar

you can still get access in your oncreate by doing
//the id would be the id of the Fragment Element In Your Activity XML File
val navGraph = Navigation.findNavController(this, R.id.activity_main)
binding.mainFab.setOnClickListener {
navGraph.navigate(R.id.destinationFragment)
}

I found this answer helpful. In your activity paste this code
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(R.id.yourDestination)

This is Extension version for Kotlin:
fun AppCompatActivity.findNavController(
#IdRes navHostViewId: Int
): NavController {
val navHostFragment =
supportFragmentManager.findFragmentById(navHostViewId) as NavHostFragment
return navHostFragment.navController
}
The navHostViewId is id of your androidx.fragment.app.FragmentContainerView.

The best way to access navController in OnCreate of Activity is to access it after inflating the layout
that can achieved by kotlin extention function doOnPreDraw()
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val navController: NavController
get() = Navigation.findNavController(this, R.id.nav_host_fragment)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.root.doOnPreDraw {
// here you can safely use navController
binding.toolbar.setupWithNavController(navController)
}
}
}

You can use binding.root.findNavController().navigate(id)

Related

How to enable navigation drawer only when we are in a certain fragment?

I want to set my navigation drawer menu on a Fragment. I don't want the navigation drawer to show up when I start my app. I want it to show up after I enter another page (i.e. not first page). But the problem is, onSupportNavigateUp can only be written on MainActivity, which is the first page.
This is my MainActivity.kt :
class MainActivity : AppCompatActivity() {
private lateinit var drawerLayout: DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
#Suppress("UNUSED_VARIABLE")
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
drawerLayout = binding.drawerLayout
val navController = this.findNavController(R.id.myNavHostFragment)
NavigationUI.setupActionBarWithNavController(this,navController, drawerLayout)
NavigationUI.setupWithNavController(binding.navView, navController)
}
override fun onSupportNavigateUp(): Boolean {
val navController = this.findNavController(R.id.myNavHostFragment)
return NavigationUI.navigateUp(navController, drawerLayout)
}
}
Here is my activity_main.xml
`
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="#+id/myNavHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="#navigation/navigation"
app:defaultNavHost="true"
/>
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="#+id/navView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/nav_header"
app:menu="#menu/navdrawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>
`
It takes fragment_title.xml display and use it for activity_main.xml display by using fragment tag and NavHostFragment.
I also have another fragment called fragment_home.xml and HomeFragment.kt class.
What I don't know is, how to not show the navigation drawer when I'm on title fragment and start showing the navigation drawer when I'm on home fragment?
Below code will set navView visibility gone in TitleFragment and set visible on the other destinations:
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.titleFragment) {
navView.visibility = View.GONE
} else {
navView.visibility = View.VISIBLE
}
}

How to use navigation component in fragment Android

I want to use Navigation component in my fragment.
My XML code like this
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
...
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_nav_host_home"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="#navigation/home_navigation"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/app_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>
And Initializing navigation code like this.
class HomeFragment : Fragment() {
//...initialie view
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val navController = Navigation.findNavController(binding.fragmentNavHostHome)
navController.setGraph(R.navigation.home_navigation)
val appBarConfiguration = AppBarConfiguration(navController.graph)
NavigationUI.setupActionBarWithNavController(requireActivity() as AppCompatActivity, navController, appBarConfiguration)
}
}
And after I launched my app, I got an error message like this.
View androidx.fragment.app.FragmentContainerView{... app:id/fragment_nav_host_home} does not have a NavController set
Also, findNavController() does not work.
How can I use navigation component in my fragment
Kotlin Answer
Key point is that try to use childFragmentManager in onViewCreated() function.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Define container.
val loginDialogContainer = childFragmentManager.findFragmentById(R.id.container) as NavHostFragment
// Set nav controller.
val loginNavController: NavController = loginDialogContainer.navController
}

Add back arrow to fragment with Navigation Component

I implemented a basic Navigation component on an app that consist of a MainActivity that holds a Toolbar (that I have added to have the back arrow functionality) and a fragment container which starts with Fragment A. In this fragment I have a button which redirects to a blank Fragment B.
I can return to fragment A (from fragment B) from the bottom Android navigation using Navigation component, but I want to do the same using the back arrow from the toolbar that I have added.
I implemented the arrow putting setSupportActionBar(findViewById(R.id.toolbar)) in Main Activity and (activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) on Fragment B, but when I tap it it doesn't redirect to Fragment A because (I suppose) that the back stack is empty.
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.toolbar))
}
MainActivity xml
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<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="#+id/toolbar"
app:navGraph="#navigation/nav_graph" />
FragmentB
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as AppCompatActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
Added this to remove the default toolbar in styles.xml
<item name="windowNoTitle">true</item>
In FragmentB up button not working
fragmentB with back not working
I want to do that using Navigation Component, any ideas?
Thanks.
Thanks to #MustafaKhaled I could find the correct documentation that is in this link
I deleted setDisplayHomeAsUpEnabled of fragmentB (you don't need to add anything in this fragment).
I leave as it is the code of styles.xml.
My new MainActivity is like this:
private lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
toolbar = findViewById(R.id.toolbar)
setupNavigation()
}
private fun setupNavigation() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
val appBarConfiguration = AppBarConfiguration.Builder(navController.graph).build()
toolbar.setupWithNavController(navController, appBarConfiguration)
}
Following this way you manage the toolbar behavior through the Navigation-UI feature.
UPDATE 28/04/20
There is an approach simpler than this that doesn't need a toolbar because uses the default one.
MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupNavigation()
}
private fun setupNavigation() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
appBarConfiguration = AppBarConfiguration.Builder(navController.graph).build()
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
}
activity_main.xml
<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/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="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_toTopOf="parent"
app:navGraph="#navigation/nav_graph" />
And my styles.xml file is like the default one:
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
</style>
The updated repo link
In your MainActivity
setup your navigation:
private fun setupNavigation() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
navController = navHostFragment!!.navController
appBarConfiguration = AppBarConfiguration.Builder(setOf(R.id.checklocation,R.id.questions,R.id.news,R.id.profile_tab)).build()
NavigationUI.setupWithNavController(bottomNavigation,navController)
NavigationUI.setupActionBarWithNavController(this,navController)
}
to enable back arrow be shown and shows the previous destination override this function
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp()
}
By the way, using navigation component with bottom navigation, we don't have to configure your toolbar, this will be handled by adding above dunctions in your MainActivity.

Navigation Component: Cannot find NavController

I am using Navigation Component for navigating in my app. It works fine inside fragments but it fails to find the nav host in the activity that holds the actual navigation host.
I am trying to open a new fragment when the user clicks on FAB, which I included in Main activity's XML. When I call findNavController() it fails to find the controller. The nav host controller is in the XML layout. I can't understand why it fails to find it.
MainActivity
class MainActivity : AppCompatActivity(), OnActivityComponentRequest {
override fun getTabLayout(): TabLayout {
return this.tabLayout
}
override fun getFap(): FloatingActionButton {
return this.floatingActionButton
}
private lateinit var tabLayout: TabLayout
private lateinit var floatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
this.tabLayout = tabs
this.floatingActionButton = fab
fab.setOnClickListener {
it.findNavController().navigate(R.id.addNewWorkoutFragment)
}
}
}
Activity main XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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=".domain.MainActivity"
android:animateLayoutChanges="true">
<com.google.android.material.appbar.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.tabs.TabItem
android:text="Test 1"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
<com.google.android.material.tabs.TabItem
android:text="Test 2"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/main_navigation" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="#+id/bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_anchorGravity="right|top"
app:layout_anchor="#+id/bar"
android:src="#drawable/ic_add_black_24dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
I was getting the same exeception until I managed to get the navController this way:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.my_fragment_container_view_id) as NavHostFragment
val navController = navHostFragment.navController
Obtained from this answer:
Before finding the nav controller, you need to set the view, if you are using binding:
binding = BaseLayoutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
If not:
setContentView(R.layout.activity_main)
Use the corresponding binding for fragments.
Try setting up onClickListener of Fab button in onStart of the Activity as in onCreate Activity is just inflating the View and haven't set the NavHostController. So if you setup onClickListener in onStart of activity is will work as expected.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
this.tabLayout = tabs
this.floatingActionButton = fab
}
override fun onStart() {
super.onStart()
floatingActionButton.setOnClickListener {
it.findNavController().navigate(R.id.addNewWorkoutFragment)
}
}
In Java you can find NavController inside activity this way:
Navigation.findNavController(this,R.id.nav_host).navigate(R.id.YourFragment);
The problem might be that FAB was added to the activity or a fragment different from the one used by NavHost fragment. In this case, when you call it.findNavController() it cannot find the navigation controller.
You can either check that your FAB belongs to the fragment pulled by NavHost or call an Activity's findNavController(<id>) and pass it id of the fragment you're looking up
override fun onCreate(savedInstanceState: Bundle?) {
...
fab.setOnClickListener {
findNavController(R.id.nav_host_fragment)
.navigate(R.id.addNewWorkoutFragment)
}
}
Retrieve it later, in onPostCreate. like this:
#Override
public void onPostCreate(Bundle savedInstanceState ) {
super.onPostCreate(savedInstanceState);
navController = Navigation.findNavController(this, R.id.fragment_container_view);
}
Turns out that activity that holds navigation controller... doesn't have navigation component.
The solution is to manually set the NavController to each view contained in the activity.
val navController = findNavController(R.id.nav_host_fragment)
Navigation.setViewNavController(fab, navController)
Now this would work:
fab.setOnClickListener {
it.findNavController().navigate(R.id.addNewWorkoutFragment)
}
I still don't understand why this works the way it works so any explanation would be more than welcome :)
As of now, Android API simply doesn't make much sense.
Source: Navigate to fragment on FAB click (Navigation Architecture Components)
Kotlin Only
Android Navigation KTX provides an extension function for Activity
All I had to do was
findNavController(R.id.myNavHostFragment).navigate(R.id.to_someFragment)
I do have the following dependencies in my app/gradle.build.kts file (
implementation ("androidx.navigation:navigation-fragment-ktx:2.3.5")
implementation ("androidx.navigation:navigation-runtime-ktx:2.3.5")
implementation ("androidx.navigation:navigation-ui-ktx:2.3.5")
Using Directions
Alternatively you could also use generated Directions class. Since this is a global action the Directions class is found in the NavigationDirections.to_someFragment()
//very useful if you're passing args then you could do something like
//NavigationDirections.to_someFragment(myArg)
NavigationDirections.to_someFragment()findNavController(R.id.myNavHostFragment).navigate(NavigationDirections.to_someFragment())
Side-note the specific extension function is being contributed from the runtime-ktx aar
you can still get access in your oncreate by doing
//the id would be the id of the Fragment Element In Your Activity XML File
val navGraph = Navigation.findNavController(this, R.id.activity_main)
binding.mainFab.setOnClickListener {
navGraph.navigate(R.id.destinationFragment)
}
I found this answer helpful. In your activity paste this code
Navigation.findNavController(this, R.id.nav_host_fragment).navigate(R.id.yourDestination)
This is Extension version for Kotlin:
fun AppCompatActivity.findNavController(
#IdRes navHostViewId: Int
): NavController {
val navHostFragment =
supportFragmentManager.findFragmentById(navHostViewId) as NavHostFragment
return navHostFragment.navController
}
The navHostViewId is id of your androidx.fragment.app.FragmentContainerView.
The best way to access navController in OnCreate of Activity is to access it after inflating the layout
that can achieved by kotlin extention function doOnPreDraw()
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val navController: NavController
get() = Navigation.findNavController(this, R.id.nav_host_fragment)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.root.doOnPreDraw {
// here you can safely use navController
binding.toolbar.setupWithNavController(navController)
}
}
}
You can use binding.root.findNavController().navigate(id)

Jetpack navigation: Title and back/up arrow in the action bar?

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)
}

Categories

Resources