esQmo_
esQmo_

Reputation: 1709

Troubles setting up BottomNavigationView + Navigation Componenent

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:

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:

  1. 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>
  1. Navigation Settings:

     <?xml version="1.0" encoding="utf-8"?>
    

Upvotes: 1

Views: 421

Answers (1)

Luis Ventura
Luis Ventura

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

Related Questions