Android BottomNavigationView - The Complete Guide with Kotlin and Navigation Component

Almost every app you use daily has a bottom navigation bar — YouTube, Instagram, Google Maps, WhatsApp. It's the single most recognisable navigation pattern on Android, and for good reason. It keeps your top-level destinations one tap away without cluttering the screen.

The old way to implement it involved the Support Library, Java, and a lot of manual Fragment switching. The modern way uses Material Components, Kotlin, and the Navigation Component — which wires up your bottom nav to your Fragment destinations in about 10 lines of code. This guide covers both the standalone setup and the full Navigation Component integration.

Android BottomNavigationView example

BottomNavigationView — persistent top-level navigation in a single tap

When to Use BottomNavigationView

✅ Use BottomNav when ❌ Don't use it when
You have 3–5 top-level destinations You have only 1–2 destinations
Destinations need to be reachable from anywhere Navigation is linear/sequential (wizard, onboarding)
Mobile or tablet app Single-task screens (email composer, settings)

Setup — Dependencies

Add Material Components to your build.gradle (app). If you plan to use Navigation Component (recommended), add those too:

dependencies {
    // Material Components — includes BottomNavigationView
    implementation "com.google.android.material:material:1.12.0"

    // Navigation Component — for Fragment wiring (recommended)
    val nav_version = "2.7.7"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
}

Step 1 — Define the Menu

Create res/menu/bottom_nav_menu.xml. Each item becomes a tab:

<!-- res/menu/bottom_nav_menu.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_home_24"
        android:title="Home" />

    <item
        android:id="@+id/searchFragment"
        android:icon="@drawable/ic_search_24"
        android:title="Search" />

    <item
        android:id="@+id/notificationsFragment"
        android:icon="@drawable/ic_notifications_24"
        android:title="Alerts" />

    <item
        android:id="@+id/profileFragment"
        android:icon="@drawable/ic_person_24"
        android:title="Profile" />

</menu>
💡 Tip: When using Navigation Component, the menu item IDs must match your Fragment destination IDs in the nav graph exactly. This is what allows setupWithNavController() to wire everything automatically.

Step 2 — Add to Activity Layout

Place BottomNavigationView at the bottom of your Activity layout. If using Navigation Component, also add a NavHostFragment:

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Fragment container -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <!-- Bottom Navigation -->
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:menu="@menu/bottom_nav_menu"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 3 — Wire with Navigation Component (Recommended)

This is the modern approach. One line — setupWithNavController() — handles tab switching, back stack management, and keeping the selected tab highlighted. No manual Fragment transactions needed:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val navHostFragment = supportFragmentManager
            .findFragmentById(R.id.nav_host_fragment) as NavHostFragment
        val navController = navHostFragment.navController

        // One line — wires bottom nav to nav controller completely
        binding.bottomNavigation.setupWithNavController(navController)
    }
}

That's it. Navigation Component handles:

  • Switching Fragments when a tab is tapped
  • Keeping the correct tab highlighted
  • Back stack per tab (separate back history for each destination)
  • Restoring the correct tab after process death

Manual Fragment Switching (Without Navigation Component)

If you're not using Navigation Component, here's how to switch Fragments manually using Kotlin and setOnItemSelectedListener:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Load default fragment
        if (savedInstanceState == null) {
            loadFragment(HomeFragment())
        }

        binding.bottomNavigation.setOnItemSelectedListener { item ->
            val fragment = when (item.itemId) {
                R.id.homeFragment          -> HomeFragment()
                R.id.searchFragment        -> SearchFragment()
                R.id.notificationsFragment -> NotificationsFragment()
                R.id.profileFragment       -> ProfileFragment()
                else -> return@setOnItemSelectedListener false
            }
            loadFragment(fragment)
            true
        }
    }

    private fun loadFragment(fragment: Fragment) {
        supportFragmentManager.beginTransaction()
            .replace(R.id.nav_host_fragment, fragment)
            .commit()
    }
}
⚠️ Note: Manual Fragment switching recreates the Fragment every time the tab is tapped — losing scroll position and local state. Navigation Component avoids this by managing the Fragment back stack properly. Use Navigation Component for any new project.

Adding Badges to Tabs

Notification badges — the little red dot or number on a tab icon — are built into Material Components via BadgeDrawable:

// Show a numbered badge on the notifications tab
val badge = binding.bottomNavigation.getOrCreateBadge(R.id.notificationsFragment)
badge.number = 5         // Shows "5"
badge.isVisible = true

// Show a dot badge (no number)
val dotBadge = binding.bottomNavigation.getOrCreateBadge(R.id.messagesFragment)
dotBadge.isVisible = true

// Update badge count
fun updateNotificationCount(count: Int) {
    val badge = binding.bottomNavigation.getOrCreateBadge(R.id.notificationsFragment)
    if (count > 0) {
        badge.number = count
        badge.isVisible = true
    } else {
        binding.bottomNavigation.removeBadge(R.id.notificationsFragment)
    }
}

// Remove badge when tab is opened
binding.bottomNavigation.setOnItemSelectedListener { item ->
    if (item.itemId == R.id.notificationsFragment) {
        binding.bottomNavigation.removeBadge(R.id.notificationsFragment)
    }
    true
}

Styling BottomNavigationView

Material 3 makes styling much simpler than the old selector approach. Set colours directly via XML attributes or in your theme:

<!-- Direct XML attributes -->
<com.google.android.material.bottomnavigation.BottomNavigationView
    android:id="@+id/bottom_navigation"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:menu="@menu/bottom_nav_menu"
    app:itemActiveIndicatorStyle="@style/BottomNavIndicator"
    app:itemIconTint="@color/bottom_nav_icon_color"
    app:itemTextColor="@color/bottom_nav_text_color"
    android:background="@color/surface" />
<!-- res/color/bottom_nav_icon_color.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/colorPrimary" android:state_checked="true" />
    <item android:color="@color/colorOnSurfaceVariant" />
</selector>
Android BottomNavigationView custom styling

BottomNavigationView with custom colours — active and inactive states via color selectors

Hide Bottom Nav on Scroll

For content-heavy screens, hiding the bottom nav when the user scrolls down gives more space to the content and feels polished:

// In your Fragment — hide bottom nav when RecyclerView scrolls down
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            val bottomNav = activity?.findViewById<BottomNavigationView>(R.id.bottom_navigation)
            if (dy > 0) {
                // Scrolling down — hide with animation
                bottomNav?.animate()?.translationY(bottomNav.height.toFloat())?.start()
            } else if (dy < 0) {
                // Scrolling up — show with animation
                bottomNav?.animate()?.translationY(0f)?.start()
            }
        }
    })
}

Best Practices

  • Use Navigation ComponentsetupWithNavController() handles back stack, tab restoration, and deep links automatically. Manual Fragment switching is error-prone and harder to maintain.
  • Keep 3–5 tabs only — fewer than 3 tabs doesn't justify a bottom nav. More than 5 makes icons too small and the bar feels crowded. This is a Material Design guideline, not just a style preference.
  • Match menu IDs to nav graph IDs — when using Navigation Component, the android:id in your menu must exactly match the destination ID in nav_graph.xml. Mismatched IDs mean the tab won't switch to the right Fragment.
  • Remove badges when the destination is visited — always clear the badge in setOnItemSelectedListener when the user taps the badged tab. Leaving a stale badge is confusing UX.
  • Don't recreate Fragments on every tab tap — if managing manually, cache Fragment instances rather than creating new ones each time. Better yet, use Navigation Component and avoid the problem entirely.

Frequently Asked Questions

How do I add BottomNavigationView with Kotlin?
Add Material Components dependency, create a menu XML with 3–5 items, add BottomNavigationView to your layout, and call setupWithNavController(navController) to wire it to Navigation Component. One line handles everything.

What replaced setOnNavigationItemSelectedListener?
It was deprecated in Material 1.4.0. Use setOnItemSelectedListener instead — same callback signature, just a cleaner name. Also available: setOnItemReselectedListener for detecting taps on the already-selected tab.

How do I add a notification badge to a tab?
Call getOrCreateBadge(R.id.yourFragment) to get a BadgeDrawable. Set badge.number for a count or just badge.isVisible = true for a dot. Remove with removeBadge() when the user visits.

How do I connect BottomNavigationView to Fragments?
Use Navigation Component — add a NavHostFragment, create a nav graph with IDs matching your menu items, then call bottomNavigation.setupWithNavController(navController). Avoid manual Fragment transactions — they don't handle back stack correctly.

📝 Summary
  • Use Material Components — not the old Support Library
  • Use setupWithNavController() — one line replaces all manual Fragment switching
  • Match menu item IDs to nav graph destination IDs exactly
  • Use setOnItemSelectedListenersetOnNavigationItemSelectedListener is deprecated
  • Add badges with getOrCreateBadge() — built into Material Components
  • Keep 3–5 tabs only — Material Design guideline
  • Hide on scroll with RecyclerView.OnScrollListener + animate().translationY()

Pragnesh Ghoda

A forward-thinking developer offering more than 8 years of experience building, integrating, and supporting android applications for mobile and tablet devices on the Android platform. Talks about #kotlin and #android

Post a Comment

Please let us know about any concerns or query.

Previous Post Next Post

Contact Form