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.
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>
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()
}
}
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>
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 Component —
setupWithNavController()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:idin your menu must exactly match the destination ID innav_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
setOnItemSelectedListenerwhen 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.
- 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
setOnItemSelectedListener—setOnNavigationItemSelectedListeneris 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()