Fixing Bottom Navigation Overlap in Android (Gesture Navigation + Edge-to-Edge)

lindin

Fixing Bottom Navigation Overlap in Android (Gesture Navigation + Edge-to-Edge)

While using the LinkedIn Android app recently, I noticed a subtle but important UI issue:

The bottom navigation bar was visually overlapping the system back gesture area when gesture navigation was enabled.

It wasn’t a crash.
It wasn’t a rendering glitch.

It was a WindowInsets handling problem — the kind that easily slips into production in large-scale apps.

This article explains:

  • Why it happens
  • How to correctly handle it
  • Production-grade solutions in XML (View system) and Jetpack Compose

1️⃣ Understanding the Root Cause

Since Android 10 (API 29), gesture navigation replaced the traditional 3-button navigation.

When you enable edge-to-edge rendering using:

WindowCompat.setDecorFitsSystemWindows(window, false)

You’re effectively telling the system:

“I will handle all system bar spacing manually.”

If you don’t apply bottom insets correctly, your BottomNavigationView (or Compose NavigationBar) will render into the gesture zone.

This results in:

  • Visual overlap
  • Gesture conflict
  • Reduced usability
  • Poor UX perception

2️⃣ Production-Grade XML (View System) Solution

Step 1 — Enable Edge-to-Edge Correctly

In your Activity:

WindowCompat.setDecorFitsSystemWindows(window, false)

Avoid mixing this with random fitsSystemWindows flags across multiple views.


Step 2 — Apply Navigation Insets Properly

ViewCompat.setOnApplyWindowInsetsListener(bottomNavigationView) { view, insets ->
    val navigationInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
    view.updatePadding(bottom = navigationInsets.bottom)
    insets
}

Why this works:

  • Dynamically reads actual system navigation height
  • Works for gesture & 3-button modes
  • Supports API 21+ via WindowInsetsCompat
  • Handles OEM variations

Step 3 — Handle Keyboard Insets Separately

For input screens:

ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
    val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
    view.updatePadding(bottom = imeInsets.bottom)
    insets
}

Never blindly combine IME and navigation insets unless you explicitly intend to.


3️⃣ Production-Grade Jetpack Compose Solution

Compose provides more structured inset handling — but only if used correctly.


Activity Setup

WindowCompat.setDecorFitsSystemWindows(window, false)

Recommended: Scaffold-Based Pattern

Scaffold(
    bottomBar = {
        NavigationBar(
            modifier = Modifier.navigationBarsPadding()
        ) {
            // Navigation items
        }
    }
) { innerPadding ->

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(innerPadding)
    ) {
        // Screen content
    }
}

Why this is production-safe:

  • navigationBarsPadding() adapts automatically
  • innerPadding prevents double layout conflicts
  • Works cleanly across API levels

Advanced Fine-Grained Control

Modifier.windowInsetsPadding(
    WindowInsets.navigationBars
)

Or merge navigation + keyboard:

Modifier.windowInsetsPadding(
    WindowInsets.navigationBars.union(WindowInsets.ime)
)

4️⃣ Centralized Handling for Scalable Apps

In large apps (multi-module, multi-fragment, hybrid Compose + View):

Best practice is to centralize inset configuration.

Example:

abstract class BaseActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        WindowCompat.setDecorFitsSystemWindows(window, false)
    }
}

Then apply insets only at:

  • Root container
  • Bottom navigation
  • IME host

Avoid per-fragment duplicate inset logic.


5️⃣ Production Testing Matrix

Always validate:

Navigation Modes

  • Gesture navigation
  • 3-button navigation

Device States

  • Keyboard open
  • Split-screen
  • Landscape mode
  • Transient system bars

OEM Variations

  • Pixel (AOSP baseline)
  • Samsung (One UI modifies gesture height)
  • Xiaomi / Oppo custom ROMs

Never assume fixed inset sizes.


6️⃣ Common Mistakes in Real Projects

❌ Hardcoding bottom padding (e.g., 16dp)
❌ Mixing fitsSystemWindows and manual insets
❌ Applying padding to RecyclerView instead of parent container
❌ Ignoring landscape behavior
❌ Forgetting transient system bar changes


Final Takeaway

While using the LinkedIn app, the overlap issue was minor — but technically significant.

Inset handling is no longer optional in modern Android engineering.

If you enable:

WindowCompat.setDecorFitsSystemWindows(window, false)

You are responsible for correctly managing:

  • Navigation bar insets
  • Gesture areas
  • IME insets
  • System UI changes

Proper WindowInsets handling directly impacts:

  • Gesture reliability
  • Accessibility
  • UX quality
  • Play Store ratings

Edge-to-edge design is powerful — but only when engineered correctly.

jetpackcompose1

Top Rated Book For Jetpack Compose

Leave a Comment

Your email address will not be published. Required fields are marked *