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.
Related
I am using nav component, just Activity Frag1,Frag2 , however moving from Frag1 to Frag2 is working, but When phone back button is pressed, Frag1 is appearing But nothing works, Clicklistner is nothing, init code does working
class CarActivity : AppCompatActivity() {
fun getLayoutId(): Int = R.layout.activity_car
private lateinit var appBarConfiguration: AppBarConfiguration
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getLayoutId())
setSupportActionBar(toolbar_details)
val navController = findNavController(R.id.nav_host_fragment_content_main)
appBarConfiguration = AppBarConfiguration(navController.graph)
setupActionBarWithNavController(navController, appBarConfiguration)
}
}
Activity
<fragment
android:id="#+id/nav_host_booking_fragment_content_main"
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" />
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_cars"
app:startDestination="#id/carFragment">
<fragment
android:id="#+id/Frag1"
android:label=" ">
in activity
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment_content_main)
return navController.navigateUp(appBarConfiguration)
|| super.onSupportNavigateUp()
}
is seems like a new view is created, or a view is disconnected from fragment
using ViewBinding solve everything but ?
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)
I'm having some trouble to add options menu on a single fragment because it's breaking the navigation Up. Here my code
I have an single Activity with NoActionBar style and with this layout
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white">
<fragment
android:id="#+id/mainNavigationFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="#navigation/main_graph" />
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbarLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="top">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:labelVisibilityMode="labeled"
app:menu="#menu/main_bottom_nav" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
in activity onCreate I make this setup for navigation
private fun setupNavigation() {
val navController = findNavController(R.id.mainNavigationFragment)
//each fragment of botton nav
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.actionSchedule,
R.id.actionPayment,
R.id.actionNotification,
R.id.actionAccount))
toolbar.setupWithNavController(navController, appBarConfiguration)
bottomNavigationView.setupWithNavController(navController)
}
override fun onSupportNavigateUp() =
findNavController(R.id.mainNavigationFragment).navigateUp()
on each botton nav fragment I have some destinations and everything work as expected.
Now I need to add an menu only on right most fragment of botton nav, then on this specific fragment I add setHasOptionsMenu(true) in onCreate and inflate menu in onCreateOptionsMenu, but the menu does not appear.
Then I add setSupportActionBar(toolbar) on activity onCreate.
Now the menu appear only on this fragment but it's broke all 'UP' (back arrow on toolbar) of any destination (the back arrow appears, but when i press nothing happens). If I remove setSupportActionBar(toolbar) of activity the UP work again but not the toolbar menu.
What I need to do to make the menu work only in one fragment without broke anything else?
Thanks
If you're using setSupportActionBar, you must use setupActionBarWithNavController(), not toolbar.setupWithNavController as per the documentation.
if using toolbar do this:
in Acitvity:
class MyActivity : AppCompatActivity() {
private var currentNavController: NavController? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
currentNavController = findNavController(R.id.settingNavHost)
currentNavController?.let {
val appBarConfiguration = AppBarConfiguration
.Builder()
.setFallbackOnNavigateUpListener {
onBackPressed()
true
}.build()
setSupportActionBar(toolbar)
toolbar.setupWithNavController(it, appBarConfiguration)
}
}
}
and in fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.my_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.action_save) {
saveInfo()
}
return super.onOptionsItemSelected(item)
}
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)
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)
}