Using the Bottom Navigation View in Android

Bottom navigation was introduced in Google’s Material Design Guidelines, it was added in Android’s design support library version 25.

To get the BottomNavigationView component, first we need to include the material components library with AndroidX enabled.

In your build.gradle file, add the following:

dependencies {
    implementation 'com.google.android.material:material:1.2.0-alpha02'
}

You can find last version here.

First, let’s define sample menu items that we’ll put inside our BottomNavigationView. We’ll create three sample items: "Item 1" and "Item 2":

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

    <item
        android:id="@+id/item2"
        android:icon="@android:drawable/ic_menu_call"
        android:title="@string/item1" />
</menu>

Now let’s create a simple layout containing a ViewPager and a BottomNavigationView below it:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:elevation="16dp"
        app:itemRippleColor="@color/colorPrimary"
        app:menu="@menu/bottom"/>
</LinearLayout>

We will create three fragment classes.

class ItemFragment : Fragment() {
    private var title: String = ""
    private val EXTRA_TITLE = "TITLE"

    companion object{
        fun newInstance(title: String) = ItemFragment().apply{
            arguments = Bundle(1).apply {
                putString(EXTRA_TITLE, title)
            }
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? =
        inflater.inflate(R.layout.fragment_item, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        arguments?.let {
            title = it.getString(EXTRA_TITLE)
        }

        tvTitle.text = title
    }
}

Now we define the MainPagerAdapter we’ll use to populate the ViewPager:

class MainPagerAdapter(fm: FragmentManager) : 
    FragmentPagerAdapter(fm, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    private val screens = arrayListOf<Fragment>()

    fun setItems(screens: List<Fragment>) {
        this.screens.apply {
            clear()
            addAll(screens)
            notifyDataSetChanged()
        }
    }

    fun getItems(): List<Fragment> {
        return screens
    }

    override fun getItem(position: Int): Fragment {
        return screens[position]
    }

    override fun getCount(): Int {
        return screens.size
    }
}

We’re now ready to go, so let’s set it all up in the MainActivity. As a bonus, we’ll also show the title of the displayed screen in the toolbar:

class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
    private lateinit var mainPagerAdapter: MainPagerAdapter
    private val titles = arrayListOf("Screen 1", "Screen 2")
    private val ids = arrayListOf(R.id.item1, R.id.item2)
    private val screens = arrayListOf(
        ItemFragment.newInstance(titles[0]),
        ItemFragment.newInstance(titles[1])
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mainPagerAdapter = MainPagerAdapter(supportFragmentManager)
        mainPagerAdapter.setItems(screens)

        scrollToScreen(screens.first())
        selectBottomNavigationViewMenuItem(R.id.item1)
        supportActionBar?.setTitle(titles.first())

        bnvItems.setOnNavigationItemSelectedListener(this)

        vpItems.adapter = mainPagerAdapter
        vpItems.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
            override fun onPageSelected(position: Int) {
                selectBottomNavigationViewMenuItem(ids[position])
                supportActionBar?.setTitle(titles[position])
            }
        })
    }

    private fun scrollToScreen(screen: Fragment) {
        val screenPosition = mainPagerAdapter.getItems().indexOf(screen)
        if (screenPosition != vpItems.currentItem) {
            vpItems.currentItem = screenPosition
        }
    }

    private fun selectBottomNavigationViewMenuItem(menuItemId: Int) {
        bnvItems.setOnNavigationItemSelectedListener(null)
        bnvItems.selectedItemId = menuItemId
        bnvItems.setOnNavigationItemSelectedListener(this)
    }


    override fun onNavigationItemSelected(menuItem: MenuItem): Boolean {
        when(menuItem.itemId) {
            R.id.item1 -> {
                scrollToScreen(screens[0])
                supportActionBar?.setTitle(titles[0])
                return true
            }
            R.id.item2 -> {
                scrollToScreen(screens[1])
                supportActionBar?.setTitle(titles[1])
                return true
            }
            else -> return false
        }
    }
}

Example of item selecting

Detecting when navigation items have been selected can be done with a convenience function:

bottomNavigation.setOnNavigationItemSelectedListener { item ->
    when(item.itemId) {
        R.id.item1 -> {
            // Do something for navigation item 1
            true
        }
        R.id.item2 -> {
            // Do something for navigation item 2
            true
        }
        else -> false
    }
}

Badges

The Bottom Navigation View items can include badges in their right upper corner as a notification. The badges contain dynamic information such as a number of pending requests and a colorful dot. Without further ado let’s create a badge notification on navigation item.

val badgeDrawable : BadgeDrawable = bottomNav.showBadge(R.id.item1) 

A badge displayed without a number is the default behavior. Now let’s say I want to show a notification count 1, for the clock menu item. The BadgeDrawable class exposes the convenience method to add notification count via the setNumber method.

bottomNav.showBadge(R.id.item1).apply { 
    number = 1  
}

Notification must be removed or dismissed once we tap on the item. We can do this by the setOnNavigationItemSelectedListener method to detect whether the item showing badge notification then simply removed it.

bottomNav.setOnNavigationItemSelectedListener { item: MenuItem ->
    val itemId = item.itemId
    bottomNav.getBadge(itemId)?.let { badgeDrawable ->
        if (badgeDrawable.isVisible)  // check whether the item showing badge 
            bottomNav.removeBadge(itemId)  //  remove badge notification
    }
    return@setOnNavigationItemSelectedListener true
}