Reputation: 1709
I'm converting my app to use one activity and added the BottomnavigationView, and in an effort to prevent fragments being recreated, making unecessary api calls, when navigating between fragments. However I can't make it work:
kotlin.TypeCastException: null cannot be cast to non-null type androidx.navigation.fragment.NavHostFragment at com.example.testapp.NavigationExtensionsKt$setupItemReselected$1.onNavigationItemReselected
Activity's layout:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/nav_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
app:menu="@menu/bottom_nav_menu" />
BottomNav menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_1"
android:icon="@drawable/ic_1"
android:title="@string/title_1" />
<item
android:id="@+id/homeFragment"
android:icon="@drawable/ic_2"
android:title="@string/home" />
<item
android:id="@+id/navigation_3"
android:icon="@drawable/ic_3"
android:title="@string/title_3" />
</menu>
In the MainActivity:
class MainActivity : DaggerAppCompatActivity() {
private var currentNavController: LiveData<NavController>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
nav_view.itemIconTintList = null
if(savedInstanceState == null){
setupBottomNavigationBar()
} // Else, need to wait for onRestoreInstanceState
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
// Now that BottomNavigationBar has restored its instance state
// and its selectedItemId, we can proceed with setting up the
// BottomNavigationBar with Navigation
setupBottomNavigationBar()
}
private fun setupBottomNavigationBar() {
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.nav_view)
val navGraphIds = listOf(R.navigation.mobile_navigation, R.navigation.settings)
val menuItemsIds = listOf(R.id.navigation_1, R.id.homeFragment, R.id.navigation_3)
val defaultIconsIds = listOf(R.drawable.ic_1, R.drawable.ic_2, R.drawable.ic_3)
val selectedIconsIds = listOf(R.drawable.ic_1a, R.drawable.ic_2a, R.drawable.ic_3a)
// Setup the bottom navigation view with a list of navigation graphs (custom extension)
val controller = bottomNavigationView.setupWithNavController(
navGraphIds = navGraphIds,
menuItemsIds = menuItemsIds,
defaultIconsIds = defaultIconsIds,
selectedIconsIds = selectedIconsIds,
fragmentManager = supportFragmentManager,
containerId = R.id.nav_host_container,
intent = intent
)
currentNavController = controller
// updating icons
nav_view.menu.iterator().forEach {
if (it.isChecked) {
it.setIcon(selectedIconsIds[menuItemsIds.indexOf(it.itemId)])
} else {
it.setIcon(defaultIconsIds[menuItemsIds.indexOf(it.itemId)])
}
}
}
override fun onSupportNavigateUp(): Boolean {
return currentNavController?.value?.navigateUp() ?: false
}
}
NavigationExtensions:
//.....
//....
private fun BottomNavigationView.setupItemReselected(
menuItemsIds: List<Int>,
defaultIconsIds: List<Int>,
selectedIconsIds: List<Int>,
graphIdToTagMap: SparseArray<String>,
fragmentManager: FragmentManager) {
setOnNavigationItemReselectedListener { item ->
this.menu.iterator().forEach { it ->
it.setIcon(defaultIconsIds[menuItemsIds.indexOf(it.itemId)])
}
item.setIcon(selectedIconsIds[menuItemsIds.indexOf(item.itemId)])
val newlySelectedItemTag = graphIdToTagMap[item.itemId]
val selectedFragment = fragmentManager.findFragmentByTag(newlySelectedItemTag) as NavHostFragment //TypeCastException happens here
val navController = selectedFragment.navController
// Pop the back stack to the start destination of the current navController graph
navController.popBackStack(
navController.graph.startDestination, false
)
}
}
The screen is blank without any fragment view rendered.
Anyone can help me fix this? Am I missing something?
PS: The code is taken from an open source app that can be found here
[Edit] Adding NavGraph:
mobile_navigation
<fragment
android:id="@+id/homeFragment"
android:name="com.example.testapp.homeFragment"
android:label="@string/home"
tools:layout="@layout/home_fragment" >
<action
android:id="@+id/homrFragment_to_detailsFragment"
app:launchSingleTop="true"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:destination="@id/detailsFragment" />
</fragment>
<fragment
android:id="@+id/navigation_1"
android:name="com.example.testapp.Fragment_S"
android:label="@string/fragment_s"
tools:layout="@layout/fragment_s" >
<action
android:id="@+id/action_navigation_1_to_detailsFragment"
app:destination="@id/detailsFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_in_left" />
</fragment>
<fragment
android:id="@+id/navigation_3"
android:name="com.example.testapp.Fragment3"
android:label="@string/fragment_ss"
tools:layout="@layout/fragment_ss" />
<dialog
android:id="@+id/detailsFragment"
android:name="com.example.testapp.DetailsFragment"
android:label="InDetails"
tools:layout="@layout/layout_details">
<argument
android:name="arg1"
android:defaultValue=""
app:argType="string"/>
<argument
android:name="arg2"
android:defaultValue=""
app:argType="string" />
</dialog>
Navigation Settings:
<?xml version="1.0" encoding="utf-8"?>
Upvotes: 1
Views: 421
Reputation: 539
Setting up a BottomNavigationView may be a little tricky to setup with Navigation Components, but I think the code you are following as example is a little over engineered and/or serves purposes way more complex than those of an initial code.
I'd have asked in a comment (don't have enough rep), but can you post the NavGraph as well? Setting up the Navigation Controller is extremely easy once you have the NavGraph, the menu and the layout properly set, but all these must be meticulously synced and the documentation in this area is not really that good.
Until you reply with the navgraph, I'll try to help with the solution:
First, change your
<androidx.fragment.app.FragmentContainerView
to
<fragment
but add the following tags
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/your_nav_graph.xml"
then, make sure the id's on your menu.xml match the same id for the destination you want, as described in your Nav Graph, for example, you should have 3 destinations called
android:id="@+id/navigation_1"
android:id="@+id/homeFragment"
android:id="@+id/navigation_3"
You won't need to setup any actions.
After that, just execute this in the activity with the BottomNavigationView:
NavigationUI.setupWithNavController(your_bottom_navigation_view, findNavController(R.id.your_navigation_fragment))
Navigation should work properly after this, without the need to setup everything on that "setupBottomNavigationView()" method, or the navigation extensions you also posted.
This way, navigation handles everything, from backtstack, to highlighting the proper button on the BottomNavigationView.
Upvotes: 2