Blogs/Technology

How to Build Instagram-Style Bottom Navigation in Android

Written byMurtuza Kutub
Aug 10, 2026
10 Min Read
How to Build Instagram-Style Bottom Navigation in Android Hero
Too Long? Read This First
- Use BottomNavigationView for three to five top-level destinations.
- Give each bottom tab its own nested navigation graph.
- Connect the bottom bar using setupWithNavController().
- Jetpack Navigation automatically saves and restores each tab’s back stack.
- Use standard navigation actions to open screens within a tab.
- Store important UI data in a ViewModel or SavedStateHandle.
- Avoid manually coordinating fragments with repeated add(), hide(), and show() transactions.

Bottom navigation becomes difficult when each tab has its own navigation flow. Users expect to switch between Home, Search, News, and Profile without losing the screen or content they previously opened.

In our experience, manually coordinating add(), replace(), hide(), and show() transactions can quickly cause duplicate fragments, lost UI state, and unpredictable Back behaviour.

Jetpack Navigation provides a cleaner solution. When connected to BottomNavigationView, it can automatically save and restore a separate back stack for every top-level tab.

What Is Instagram-Style Bottom Navigation?

Instagram-style bottom navigation is a navigation pattern in which each bottom tab maintains its own screen history and UI state. Users can switch between tabs without losing their position within any tab.

For example, imagine an application with five tabs:

  • Home
  • Search
  • Create
  • News
  • Profile

A user might follow this path:

Home → Post Details → Comments
Profile → Edit Profile
Home → Returns to Comments

When the user switches from Home to Profile, the Home tab’s back stack is saved. Returning to Home restores the Comments screen instead of restarting from the main Home screen.

Each tab therefore maintains a separate back stack:

Home: Home → Post Details → Comments
Search: Search
Create: Create
News: News → Notification Details
Profile: Profile → Edit Profile

Only one stack is visible at a time, but the navigation state of the other tabs remains available. This creates the continuous experience associated with applications such as Instagram.

The system Back action should move backwards through the currently selected tab:

Comments → Post Details → Home

It should not unexpectedly open a destination belonging to another tab or reset every tab to its starting screen.

What Will We Build?

By the end of this guide, the application will:

  • Keep the bottom navigation bar visible across tab screens.
  • Maintain an independent back stack for every tab.
  • Restore the last-opened destination when a user returns to a tab.
  • Support nested navigation inside Home and News.
  • Work with Android’s system and gesture-based Back actions.
  • Preserve important screen state across configuration changes.

Why Manual Fragment Transactions Become Difficult

A basic implementation may replace the current fragment whenever a bottom tab is selected:

supportFragmentManager.beginTransaction()
    .replace(R.id.fragment_container, HomeFragment())
    .commit()

This works for a small prototype. However, every replacement can create a new fragment instance unless the application separately saves and restores its state.

A more advanced implementation might keep root fragments alive using add(), hide(), and show(). The activity must then manually coordinate:

  • The currently active fragment
  • Child fragment back stacks
  • Back-button behaviour
  • Duplicate tab selections
  • Fragment lifecycle states
  • Configuration changes
  • Process recreation
  • Saved UI state

We have found that problems become more visible when a screen inside one tab opens another fragment. Switching tabs may appear to work, but pressing Back can reveal that the selected tab and displayed fragment stack are no longer synchronized.

Jetpack Navigation handles these responsibilities through a NavController.

Why Use BottomNavigationView Instead of TabLayout?

Older Android implementations sometimes used TabLayout because early versions of BottomNavigationView provided less control. That reasoning is no longer applicable to most modern Android applications.

BottomNavigationView is specifically designed for three to five top-level destinations. It provides:

  • Material-compliant bottom navigation
  • Selected and unselected item states
  • Accessibility support
  • Badge support
  • Navigation Component integration
  • Automatic multiple-back-stack handling
  • State restoration when switching tabs

TabLayout is better suited to closely related or swipeable pages, such as categories displayed through ViewPager2.

For persistent top-level destinations such as Home, Search, News, and Profile, BottomNavigationView communicates the application hierarchy more accurately.

Step 1: Add the Navigation Dependencies

Add the Navigation Fragment and Navigation UI libraries to the app module:

dependencies {
    implementation("androidx.navigation:navigation-fragment-ktx:2.9.8")
    implementation("androidx.navigation:navigation-ui-ktx:2.9.8")
}

The project should also include Material Components for BottomNavigationView and ConstraintLayout for the activity layout.

Step 2: Create the Bottom Navigation Menu

Create res/menu/bottom_navigation_menu.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/home_graph"
        android:icon="@drawable/ic_home"
        android:title="@string/home" />

    <item
        android:id="@+id/search_graph"
        android:icon="@drawable/ic_search"
        android:title="@string/search" />

    <item
        android:id="@+id/create_graph"
        android:icon="@drawable/ic_add"
        android:title="@string/create" />

    <item
        android:id="@+id/news_graph"
        android:icon="@drawable/ic_notifications"
        android:title="@string/news" />

    <item
        android:id="@+id/profile_graph"
        android:icon="@drawable/ic_profile"
        android:title="@string/profile" />

</menu>

The menu item IDs must match the IDs of the corresponding nested navigation graphs. If they differ, setupWithNavController() cannot map each tab to the correct graph.

Step 3: Add the NavHost and BottomNavigationView

Create the activity layout:

<?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">

    <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/main_navigation"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

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

</androidx.constraintlayout.widget.ConstraintLayout>

FragmentContainerView hosts the active destination. The bottom navigation remains outside the container, so it stays visible while fragments change.

Automate Your Android Workflow

We build Android Studio plugins that cut manual effort, improve speed, and make your development process seamless.

Setting app:defaultNavHost="true" allows the NavHostFragment to handle Android’s Back action.

Step 4: Create a Nested Graph for Each Tab

Create res/navigation/main_navigation.xml:

<?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/main_navigation"
    app:startDestination="@id/home_graph">

    <navigation
        android:id="@+id/home_graph"
        app:startDestination="@id/homeFragment">

        <fragment
            android:id="@+id/homeFragment"
            android:name="com.example.app.home.HomeFragment"
            android:label="Home">

            <action
                android:id="@+id/action_home_to_post_details"
                app:destination="@id/postDetailsFragment" />
        </fragment>

        <fragment
            android:id="@+id/postDetailsFragment"
            android:name="com.example.app.home.PostDetailsFragment"
            android:label="Post details">

            <action
                android:id="@+id/action_post_details_to_comments"
                app:destination="@id/commentsFragment" />
        </fragment>

        <fragment
            android:id="@+id/commentsFragment"
            android:name="com.example.app.home.CommentsFragment"
            android:label="Comments" />

    </navigation>

    <navigation
        android:id="@+id/search_graph"
        app:startDestination="@id/searchFragment">

        <fragment
            android:id="@+id/searchFragment"
            android:name="com.example.app.search.SearchFragment"
            android:label="Search" />

    </navigation>

    <navigation
        android:id="@+id/create_graph"
        app:startDestination="@id/createFragment">

        <fragment
            android:id="@+id/createFragment"
            android:name="com.example.app.create.CreateFragment"
            android:label="Create" />

    </navigation>

    <navigation
        android:id="@+id/news_graph"
        app:startDestination="@id/newsFragment">

        <fragment
            android:id="@+id/newsFragment"
            android:name="com.example.app.news.NewsFragment"
            android:label="News">

            <action
                android:id="@+id/action_news_to_notification_details"
                app:destination="@id/notificationDetailsFragment" />
        </fragment>

        <fragment
            android:id="@+id/notificationDetailsFragment"
            android:name="com.example.app.news.NotificationDetailsFragment"
            android:label="Notification details" />

    </navigation>

    <navigation
        android:id="@+id/profile_graph"
        app:startDestination="@id/profileFragment">

        <fragment
            android:id="@+id/profileFragment"
            android:name="com.example.app.profile.ProfileFragment"
            android:label="Profile">

            <action
                android:id="@+id/action_profile_to_edit_profile"
                app:destination="@id/editProfileFragment" />
        </fragment>

        <fragment
            android:id="@+id/editProfileFragment"
            android:name="com.example.app.profile.EditProfileFragment"
            android:label="Edit profile" />

    </navigation>

</navigation>

Each nested graph represents an independent tab flow.

The Home graph contains:

HomeFragment → PostDetailsFragment → CommentsFragment

The News graph contains:

NewsFragment → NotificationDetailsFragment

The Profile graph contains:

ProfileFragment → EditProfileFragment

The com.example.app class names illustrate the expected project structure. In an actual project, they correspond to the package containing each fragment.

Step 5: Connect BottomNavigationView to NavController

If the project uses View Binding, configure the navigation in MainActivity:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.example.app.databinding.ActivityMainBinding

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

        binding.bottomNavigation.setupWithNavController(navController)
    }
}

That single setupWithNavController() call connects the bottom bar to the navigation graph.

Navigation Component 2.4.0 and later automatically saves the current stack and restores the previously selected tab’s stack. There is no need to manually add, replace, hide, or show the root fragments.

If View Binding is not already enabled, add it to the app module:

android {
    buildFeatures {
        viewBinding = true
    }
}

Step 6: Navigate Within the Home Tab

Inside HomeFragment, open Post Details:

import androidx.navigation.fragment.findNavController

binding.openPostButton.setOnClickListener {
    findNavController().navigate(
        R.id.action_home_to_post_details
    )
}

Inside PostDetailsFragment, open Comments:

binding.openCommentsButton.setOnClickListener {
    findNavController().navigate(
        R.id.action_post_details_to_comments
    )
}

The Home stack now becomes:

HomeFragment → PostDetailsFragment → CommentsFragment

If the user switches to Profile and later returns to Home, the Comments screen is restored automatically.

Step 7: Navigate Within the Other Tabs

Inside NewsFragment:

import androidx.navigation.fragment.findNavController

binding.openNotificationButton.setOnClickListener {
    findNavController().navigate(
        R.id.action_news_to_notification_details
    )
}

Inside ProfileFragment:

import androidx.navigation.fragment.findNavController

binding.editProfileButton.setOnClickListener {
    findNavController().navigate(
        R.id.action_profile_to_edit_profile
    )
}

The user can now move between three independent flows:

Home → Post Details → Comments
News → Notification Details
Profile → Edit Profile

Switching tabs does not discard the destination previously opened in another tab.

How Does Multiple-Back-Stack Restoration Work?

When a user selects another bottom tab, Navigation UI performs two operations:

  1. It saves the current tab’s navigation state.
  2. It restores the previously saved state of the selected tab.

The equivalent manual navigation options would look like this:

import androidx.navigation.NavGraph.Companion.findStartDestination

navController.navigate(selectedDestinationId) {
    launchSingleTop = true
    restoreState = true

    popUpTo(navController.graph.findStartDestination().id) {
        saveState = true
    }
}

You do not need this code when using setupWithNavController(). It is shown only to explain the underlying behaviour.

launchSingleTop prevents another copy of the same top-level destination from being added. saveState preserves the stack being left, while restoreState restores the stack associated with the selected destination.

How Is UI State Preserved?

Back-stack restoration and UI-state restoration are related, but they are not identical.

Jetpack Navigation restores the destination and its saved navigation state. Android views with stable IDs can also restore values such as entered text or scroll position when those views support instance-state saving.

Important application data should still be stored in a ViewModel:

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel

class HomeViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    var selectedPostId: Long?
        get() = savedStateHandle["selected_post_id"]
        set(value) {
            savedStateHandle["selected_post_id"] = value
        }
}

A mistake we frequently see is treating a fragment instance as the application’s source of truth. It may appear to work while switching tabs, but it becomes unreliable after process death or configuration changes.

Use:

  • ViewModel for screen and business state
  • SavedStateHandle for small values that should survive process recreation
  • Automatic view state for transient values such as entered text
  • A repository or database for persistent application data

Handling Repeated Tab Selections

Some applications scroll the current feed to the top when the selected tab is tapped again. Others return that tab to its root destination.

Use setOnItemReselectedListener() for this behaviour:

binding.bottomNavigation.setOnItemReselectedListener { item ->
    if (item.itemId == R.id.home_graph) {
        // Scroll the Home feed to the top,
        // or return the Home graph to its start destination.
    }
}

Avoid calling setupWithNavController() again inside this listener. The bottom navigation is already connected to the controller.

Hiding the Bottom Navigation on Deeper Screens

Instagram-like navigation does not always keep the bottom bar visible on every destination. For example, a full-screen editor or media viewer may need additional space.

Automate Your Android Workflow

We build Android Studio plugins that cut manual effort, improve speed, and make your development process seamless.

You can observe destination changes and control the bottom bar:

import android.view.View

navController.addOnDestinationChangedListener { _, destination, _ ->
    val hideBottomNavigation = destination.id in setOf(
        R.id.commentsFragment,
        R.id.editProfileFragment
    )

    binding.bottomNavigation.visibility =
        if (hideBottomNavigation) View.GONE else View.VISIBLE
}

Whether the bar should remain visible depends on the role of the destination. Keep it visible while users explore a core tab flow, but consider hiding it for immersive or task-focused screens.

Common Problems and Fixes

A tab always opens its first screen

Confirm that the project uses Navigation Component 2.4.0 or later and that the bottom bar is connected through setupWithNavController().

Older Navigation versions do not provide the same automatic multiple-back-stack behaviour.

Selecting a tab does nothing

The bottom-menu item ID must match the nested navigation graph ID:

android:id="@+id/home_graph"

Both the menu and navigation graph must use the exact same ID.

A screen loses entered data

Move meaningful state into a ViewModel or SavedStateHandle. Do not rely only on fragment fields or assume the fragment will remain in memory.

Back opens a screen from another tab

This commonly occurs in manual implementations that place transactions from every tab into one shared fragment back stack. Give each top-level tab its own nested navigation graph.

The same destination opens more than once

Use navigation actions correctly and avoid triggering navigation more than once through rapid button taps. Top-level tab navigation already applies launchSingleTop through Navigation UI.

Fragments are recreated unexpectedly

Fragment recreation is not necessarily an error. Android may recreate fragments during configuration changes or process restoration. Preserve the screen’s state instead of depending on permanent fragment instances.

Should You Use Manual Fragment Transactions?

Manual fragment transactions are still useful when an application requires highly specialized fragment orchestration that the Navigation Component cannot represent.

For standard bottom navigation, however, they usually introduce more code than value. Navigation Component also makes the application flow easier to inspect because destinations and actions are defined in one graph rather than distributed across activity click listeners.

Use manual transactions only when there is a specific behavioural requirement—not simply to prevent fragment recreation.

BottomNavigationView vs TabLayout

RequirementBottomNavigationViewTabLayout
Top-level application destinationsBest suitedNot recommended
Three to five persistent tabsYesPossible
Swipeable pagesNoYes
Navigation Component integrationBuilt inRequires custom setup
Multiple navigation back stacksSupportedRequires additional handling
Category or filter tabsPossibleBest suited
Badges and bottom-navigation stylingBuilt inRequires customization
Top-level application destinations
BottomNavigationView
Best suited
TabLayout
Not recommended
1 of 7

For an Instagram-style application structure, BottomNavigationView is the appropriate choice.

Frequently Asked Questions

What is Instagram-style bottom navigation?

Instagram-style bottom navigation gives every top-level tab an independent navigation history. Users can switch between tabs and return to the destination and UI state they previously left.

How do you preserve fragment state with bottom navigation?

Create a nested navigation graph for each tab and connect BottomNavigationView using setupWithNavController(). Jetpack Navigation then saves and restores each tab’s navigation stack automatically.

Does BottomNavigationView support multiple back stacks?

Yes. Navigation Component 2.4.0 and later support multiple back stacks when BottomNavigationView is connected to a NavController through the standard Navigation UI integration.

Should I use TabLayout or BottomNavigationView?

Use BottomNavigationView for three to five top-level application destinations. Use TabLayout for closely related pages, categories, filters, or swipeable content displayed through ViewPager2.

Why does my fragment lose its state after switching tabs?

The tab may be recreating its destination, or its data may exist only in fragment fields. Store meaningful screen state in a ViewModel and restoration values in SavedStateHandle.

How does the Back button work with multiple tab stacks?

The NavController moves backwards through the currently selected tab. Because the NavHostFragment is the default navigation host, it integrates with Android’s system and gesture-based Back actions.

Can every bottom-navigation tab contain nested fragments?

Yes. Each tab can use a nested navigation graph containing several fragment destinations. Navigating inside one graph does not discard the saved navigation stack belonging to another tab.

Should the bottom navigation remain visible on every screen?

Not necessarily. Keep it visible across core tab flows, but consider hiding it for immersive destinations, full-screen media, editors, authentication screens, or focused tasks where it could cause accidental navigation.

Author-Murtuza Kutub
Murtuza Kutub
LinkedIn

A product development and growth expert, helping founders and startups build and grow their products at lightning speed with a track record of success. Apart from work, I love to Network & Travel.

Share this article

Phone

Next for you

8 Best GraphQL Libraries for Node.js in 2025 Cover

Technology

Aug 4, 202613 min read

8 Best GraphQL Libraries for Node.js in 2025

8 Best GraphQL Libraries for Node.js in 2026 Too Long? Read This First - Choose Apollo Server when you need a mature ecosystem, GraphOS integration, plugins, or Apollo Federation. - Choose GraphQL Yoga for a modern, portable server with Fetch API compatibility and built-in support for subscriptions over Server-Sent Events. - Choose Mercurius when your application already uses Fastify and runtime efficiency is a major priority. - Use GraphQL.js when you need the official JavaScript implementati

9 React Native Animation Libraries and Tools Compared Cover

Technology

Aug 4, 202615 min read

9 React Native Animation Libraries and Tools Compared

Too Long? Read This First - Use React Native Reanimated for gesture-driven, interruptible, and performance-sensitive interface animations. - Use the built-in Animated API for simple fades, transforms, and timed sequences without another dependency. - Pair React Native Gesture Handler with Reanimated for swipes, dragging, pinching, rotation, and other touch-driven experiences. - Use Lottie React Native for non-interactive motion graphics supplied by designers. - Choose React Native Skia for cust

9 Critical Practices for Secure Web Application Development Cover

Technology

Aug 4, 202616 min read

9 Critical Practices for Secure Web Application Development

Too Long? Read This First - Define security requirements and model threats before implementation begins. - Treat authentication, account recovery, and MFA as one complete identity system. - Apply server-side authorization to every protected action and object. - Prevent injection with parameterized APIs, structured validation, safe output handling, and restricted outbound requests. - Protect sessions and tokens throughout their complete lifecycle. - Minimise sensitive data and manage encryption