Currently I'm developing an Android app, in which I would like to utilize both Android Navigation Component and BottomNavigationView.
While working on said app I checked out the official codelab and several questions here, but they didn't prove to be of any help.
The problem is that assigning navController to my BottomNavigationView does not seem to have any effect - clicking on menu items does not affect the navigation host.
My code:
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_host_fr"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/main_nav_bnv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@android:color/transparent"
android:elevation="0dp"
app:elevation="0dp"
app:menu="@menu/menu_main" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
MainActivity.kt
import android.os.Bundle
import android.os.PersistableBundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import androidx.navigation.ui.setupWithNavController
import com.aredruss.qurio.databinding.ActivityMainBinding
import com.aredruss.qurio.helpers.viewBinding
class MainActivity : AppCompatActivity() {
// ViewBindingDelegate
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
// Using onPostCreate because accessing NavHost in OnCreate causes crashes
override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onPostCreate(savedInstanceState, persistentState)
binding.navigationBb.setupWithNavController(findNavController(R.id.main_host_fr))
NavigationUI.setupWithNavController(binding.mainNavBnv, findNavController(R.id.main_host_fr))
}
}
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/action_history">
<fragment
android:id="@+id/action_history"
android:name="com.aredruss.qurio.ui.HistoryFragment"
android:label="HistoryFragment"
tools:layout="@layout/fragment_history" >
</fragment>
<fragment
android:id="@+id/action_settings"
android:name="com.aredruss.qurio.ui.SettingsFragment"
android:label="SettingsFragment"
tools:layout="@layout/fragment_settings" >
</fragment>
</navigation>
menu_main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_history"
android:icon="@drawable/ic_history"
android:title="History"
app:showAsAction="withText" />
<item
android:id="@+id/action_settings"
android:title="Settings"
android:icon="@drawable/ic_settings"
app:showAsAction="withText"
/>
</menu>
I've checked out this solution and it wasn't of any help.
You've overridden the wrong onPostCreate():
override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
You don't want the one that takes a PersistableBundle - you want the regular one:
override fun onPostCreate(savedInstanceState: Bundle?) {
Now your onPostCreate() will actually be called.
Of course, you shouldn't be using onPostCreate() at all. Instead, you should be following the documentation, which specifically state that you should be using the following code to get your NavController in onCreate():
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_host_fr) as NavHostFragment
val navController = navHostFragment.navController
which lets you write your entire code as:
// ViewBindingDelegate
private val binding: ActivityMainBinding by viewBinding(ActivityMainBinding::inflate)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Note how we actually set the content view from the binding
setContentView(binding.root)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_host_fr) as NavHostFragment
val navController = navHostFragment.navController
binding.navigationBb.setupWithNavController(navController)
binding.mainNavBnv.setupWithNavController(navController)
}
Here are the concerns that need to be fixed:
ViewBindingDelegate which automatically handles the setContentView, so you need to remove this, as you won't be able to use binding.xx in this case:override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main) //<<<<< remove this
}
onPostCreate while it should not be implemented by developers, and only need to be touched by the system:Called when activity start-up is complete (after onStart() and onRestoreInstanceState(Bundle) have been called). Applications will generally not implement this method; it is intended for system classes to do final initialization after application code has run.
As per my test, onPostCreate() didn't get called. So, to fix this, move this part of code to a lifecycle method that can be implemented by developers like onStart() or onResume():
override fun onStart() {
super.onStart()
NavigationUI.setupWithNavController(
binding.mainNavBnv,
findNavController(R.id.main_host_fr)
)
}
This way the NavController can auto handle the BottomNavigationView:
UPDATE: As per comments, you should get the NavController using the NavHostFragment as follows, and therefore you can transfer the code to the onCreate() method:
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.main_host_fr) as NavHostFragment
val navController = navHostFragment.navController

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With