Bottom Navigation & Tabs
Implement multi-section navigation with BottomNavigationView connected to NavController, and swipeable tabs with TabLayout, ViewPager2, and TabLayoutMediator.
Bottom Navigation & Tabs is a free Android Academy lesson on CoddyKit — lesson 5 of 5. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Android Academy learning path, one of 5 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Navigation Patterns
Two common patterns for top-level navigation in Android apps:
- Bottom Navigation — for 3–5 primary destinations. Always visible. Best for equal-importance sections (e.g., Home, Search, Profile).
- Tabs — for related content within a screen (e.g., All / Active / Done). Often used with ViewPager2 for swipe support.
BottomNavigationView Layout
Add BottomNavigationView and a FragmentContainerView to your Activity layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav_menu" />
</LinearLayout>Bottom Nav Menu Items
Define items in res/menu/bottom_nav_menu.xml. Each item ID must match a navigation graph destination ID:
<!-- 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"
android:title="Home" />
<item
android:id="@+id/searchFragment"
android:icon="@drawable/ic_search"
android:title="Search" />
<item
android:id="@+id/profileFragment"
android:icon="@drawable/ic_person"
android:title="Profile" />
</menu>Connecting to NavController
Wire BottomNavigationView to the NavController in onCreate():
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHost = supportFragmentManager
.findFragmentById(R.id.navHostFragment) as NavHostFragment
val navController = navHost.navController
binding.bottomNav.setupWithNavController(navController)
// Hide bottom nav on detail screens:
navController.addOnDestinationChangedListener { _, destination, _ ->
binding.bottomNav.visibility = when (destination.id) {
R.id.detailFragment -> View.GONE
else -> View.VISIBLE
}
}
}
}Badges on Bottom Nav
Show notification badges on bottom nav items:
// Show a numbered badge:
val badge = binding.bottomNav.getOrCreateBadge(R.id.homeFragment)
badge.number = 5
badge.isVisible = true
// Show dot badge (no number):
val dotBadge = binding.bottomNav.getOrCreateBadge(R.id.searchFragment)
dotBadge.isVisible = true
// Remove badge:
binding.bottomNav.removeBadge(R.id.homeFragment)TabLayout
TabLayout shows horizontal tabs, typically used with ViewPager2 for swipeable content:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>ViewPager2 + FragmentStateAdapter
Create an adapter that provides a fragment for each tab position:
class TasksPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun getItemCount() = 3
override fun createFragment(position: Int): Fragment = when (position) {
0 -> AllTasksFragment()
1 -> ActiveTasksFragment()
2 -> CompletedTasksFragment()
else -> throw IllegalStateException("Unknown tab $position")
}
}TabLayoutMediator
Connect ViewPager2 and TabLayout with TabLayoutMediator:
// In Fragment.onViewCreated():
val adapter = TasksPagerAdapter(this)
binding.viewPager.adapter = adapter
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
tab.text = when (position) {
0 -> "All"
1 -> "Active"
2 -> "Done"
else -> ""
}
}.attach()
// Disable swipe if needed:
binding.viewPager.isUserInputEnabled = falseTab with Icons
Add icons to TabLayout tabs:
TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position ->
when (position) {
0 -> {
tab.text = "Home"
tab.setIcon(R.drawable.ic_home)
}
1 -> {
tab.text = "Search"
tab.setIcon(R.drawable.ic_search)
}
2 -> {
tab.text = "Profile"
tab.setIcon(R.drawable.ic_person)
}
}
}.attach()Offscreen Page Limit
By default, ViewPager2 only keeps the adjacent page in memory. Increase offscreenPageLimit if your tabs should stay alive when switching:
// Keep 2 pages on each side alive:
binding.viewPager.offscreenPageLimit = 2
// Note: FragmentStateAdapter saves and restores state for off-screen
// fragments even when offscreenPageLimit is 1 (default).
// Only increase this when you need views (not just state) to survive.Bottom Nav vs Tabs: Choosing
Quick guide for picking the right pattern:
- Use Bottom Navigation when: 3–5 top-level destinations with different purposes (Home, Search, Notifications, Profile)
- Use Tabs when: filtering or categorizing the same type of content (All / Active / Archived tasks)
- Combine both: Bottom Nav for top-level sections, Tabs inside a section for sub-filtering
Quick Check
Which class connects a TabLayout to a ViewPager2 and sets the tab labels?
Recap: Bottom Navigation & Tabs
Multi-section navigation done right:
- BottomNavigationView — 3–5 equal-importance destinations, menu IDs match nav graph destination IDs
setupWithNavController()— auto tab switching + back stack- Badges with
getOrCreateBadge() - TabLayout + ViewPager2 — swipeable, filtered views of the same content
TabLayoutMediator— connects the two and sets labels
Next: Unit Testing with JUnit to keep your code reliable.
Frequently asked questions
Is the “Bottom Navigation & Tabs” lesson free?
Yes — the full text of “Bottom Navigation & Tabs” is free to read here on the web, and the Android Academy course includes 5 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Android Academy course, upgrade to CoddyKit PRO.
What will I learn in “Bottom Navigation & Tabs”?
Implement multi-section navigation with BottomNavigationView connected to NavController, and swipeable tabs with TabLayout, ViewPager2, and TabLayoutMediator. You practise Android Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Android Academy?
No prior experience is required. Android Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 5 of 5, so you can start here or from the beginning and move at your own pace.
How long does the “Bottom Navigation & Tabs” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Android Academy lesson?
Yes. Every Android Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Material Design Basics
- Themes & Styles
- Custom Views
- Animations & Transitions
- Bottom Navigation & Tabs