leoybkim
leoybkim

Reputation: 312

How to add bottom navigation listeners to fragments

I used to have a single activity that included the navigation host and the BottomNavigationView that allowed switching views between the 3 fragments. I recently changed my design so that the initial activity now represents a single new fragment, and there exists a button that allows the user to navigate to another fragment which should hold the aforementioned 3 tabbed bottom navigation.

I've managed to implement the initial activity and the first fragment. But the issue that I am having is that when I navigate to the second fragment with the 3 tabbed bottom navigation bar, I don't know how to implement the onClickListener on the tabs. It used to be fairly straightforward when I had the bottom navigation inside the AppCompatActivity. I'm guessing that fragments are not supposed to be used for this case. I could easily use another activity class but I wanted to build a single activity application and wondered if there was another solution to this.

In the original method using an activity class, I was able to implement the bottom navigation like below:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)
        val navController = Navigation.findNavController(this, R.id.nav_host_fragment)

        setupBottomNavMenu(navController)
        setupActionBar(navController)
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.menu_toolbar, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
        val navigated = NavigationUI.onNavDestinationSelected(item!!, navController)
        return navigated || super.onOptionsItemSelected(item)
    }

    override fun onSupportNavigateUp(): Boolean {
        return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.nav_host_fragment), drawer_layout)
    }

    private fun setupBottomNavMenu(navController: NavController) {
        bottom_nav?.let {
            NavigationUI.setupWithNavController(it, navController)
        }
    }

    private fun setupActionBar(navController: NavController) {
        NavigationUI.setupActionBarWithNavController(this, navController, drawer_layout)
    }
}

<?xml version="1.0" encoding="utf-8"?>
<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">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="@color/colorMintCream"
        tools:context=".activity.MainActivity">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?colorPrimary"
            android:theme="@style/ToolbarTheme"/>

        <fragment
            android:id="@+id/nav_host_fragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/main_nav_graph"
            app:defaultNavHost="true"/>

       <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_nav"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:menu="@menu/menu_navigation"/>

    </LinearLayout>
</layout>

The navigation looks like follows:

<?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" android:id="@+id/mobile_navigation.xml"
    app:startDestination="@id/destination_profile">

    <fragment
        android:id="@+id/destination_profile"
        android:name="com.example.project.profile.ProfileFragment"
        android:label="Profile" />
    <fragment
        android:id="@+id/destination_achievements"
        android:name="com.example.project.fragments.AchievementsFragment"
        android:label="Achievements" />
    <fragment
        android:id="@+id/destination_logs"
        android:name="com.example.project.fragments.LogsFragment"
        android:label="Logs" />
    <fragment
        android:id="@+id/destination_settings"
        android:name="com.example.project.fragments.SettingsFragment"
        android:label="Settings" />
</navigation>

And finally the menu:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/destination_profile"
        android:icon="@drawable/ic_person_black_24dp"
        android:title="@string/profile"/>

    <item
        android:id="@+id/destination_logs"
        android:icon="@drawable/ic_fitness_center_black_24dp"
        android:title="@string/logs"/>

    <item
        android:id="@+id/destination_achievements"
        android:icon="@drawable/ic_star_black_24dp"
        android:title="@string/achievements"/>
</menu>

Similarly I tried to apply this on a fragment like below:


class MainFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        var binding: FragmentMainBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.fragment_main,
                container,
                false
        )

        val application = requireNotNull(this.activity).application
        val dataSource = UserProfileDatabase.getInstance(application).userProfileDao
        val viewModelFactory = MainViewModelFactory(dataSource, application)
        val navController = this.findNavController()
        NavigationUI.setupWithNavController(binding.bottomNav, navController)
        return binding.root
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.menu_toolbar, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val navController = this.findNavController()
        val navigated = NavigationUI.onNavDestinationSelected(item, navController)
        return navigated || super.onOptionsItemSelected(item)
    }

    companion object {
        fun newInstance(): MainFragment = MainFragment()
    }
}

The xml is almost exactly the same as the one for activity.

I expected to invoke onOptionsItemSelected() when I clicked on the tab elements, but I wasn't able to do so. How can I implement the listeners on those tab elements so that I can navigate to the correct fragment destinations?

Upvotes: 2

Views: 4735

Answers (2)

rehan
rehan

Reputation: 51

Keep your menu item's id same as your graph id and simply use 1 line code i.e val navHostFragment =childFragmentManager.findFragmentById(R.id.fragmentContainerView) as NavHostFragment

    val navController = navHostFragment.navController

    binding.bottomNavBar.setupWithNavController(navController)

Upvotes: 0

Ben Shmuel
Ben Shmuel

Reputation: 1979

Why don't you keep your first implementation for your BottomNavigationView which is good and combine it with your new implementation by using addOnDestinationChangedListener.

Your FirstFragment that hold the button will hide the BottomNavigationView and the SecondFragment with the 3 tabbed bottom navigation will show it.

For example :

navController.addOnDestinationChangedListener { _, destination, _ ->

 if(destination.id == R.id.first_fragment) {
    // your intro fragment will hide your bottomNavigationView
    bottomNavigationView.visibility = View.GONE
  } else if (destination.id == R.id.second_fragment){
   // your second fragment will show your bottomNavigationView
   bottomNavigationView.visibility = View.VISIBLE
 }
}

in that way you keep the control in your Activity instead of Fragment and by that interact better with your listeners and your NavController.

Upvotes: 1

Related Questions