Edge-to-edge support on Android 15+

Table of contents

    Edge-to-edge support on Android 15+

    Edge-to-edge(opens in a new tab) enforcement ensures your app content fills the entire screen of a device. Starting with Android 15 (API level 35), this behavior is enabled by default for all apps.

    Implications and potential issues

    With edge-to-edge enabled, app content often extends beneath the status and navigation bars. This can make parts of your user interface (UI) difficult to read or interact with, since elements may be obscured by system components.

    Proper handling is crucial to ensure your app content is fully visible and interactive.

    Goal of proper edge-to-edge handling

    The goal is to ensure all app content is fully visible and interactive. This article will cover handling edge-to-edge behavior using both Jetpack Compose and traditional XML layouts.

    Compose approach

    The following snippet demonstrates a common problem in Compose:

    EdgeToEdgeTestTheme {
    LazyColumn {
    items(100) {
    Text(
    text = "Item number $it",
    modifier = Modifier
    .fillMaxWidth()
    .padding(16.dp)
    )
    }
    }
    }

    The result looks like this.

    Result

    Here, the list content scrolls beneath the status and navigation bars.

    Solution with Scaffold

    A simple solution is to use Scaffold, which automatically handles system insets. The LazyColumn can then take advantage of the paddingValues provided by Scaffold:

    EdgeToEdgeTestTheme {
    Scaffold { paddingValues ->
    LazyColumn(
    modifier = Modifier.padding(paddingValues)
    ) {
    items(100) {
    Text(
    text = "Item number $it",
    modifier = Modifier
    .fillMaxWidth()
    .padding(16.dp)
    )
    }
    }
    }
    }

    Now the content is drawn properly.

    Result

    This approach works with all composables — not just LazyColumn — making it flexible for any UI layout. The content is fully visible and remains interactable at all times.

    Handling cases without Scaffold

    If you’re not using Scaffold, content may still extend beneath system bars:

    EdgeToEdgeTestTheme {
    Text(
    modifier = Modifier
    .fillMaxSize()
    .background(Color.LightGray),
    text = LoremIpsum(words = 1000).values.joinToString { it }
    )
    }

    Result

    To fix this, apply Modifier.safeContentPadding():

    EdgeToEdgeTestTheme {
    Text(
    modifier = Modifier
    .fillMaxSize()
    .background(Color.LightGray)
    .safeContentPadding(),
    text = LoremIpsum(words = 1000).values.joinToString { it }
    )
    }

    The content is now fully visible.

    Result

    XML approach

    Here’s a basic example showing the issue in XML:

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    setContentView(R.layout.activity_main)
    val textView = findViewById<TextView>(R.id.textView)
    textView.text = LoremIpsum(words = 1000).values.joinToString { it }
    }

    activity_main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_light"
    android:textColor="@color/black" />
    </LinearLayout>

    The content will be drawn beneath the system bars.

    Result

    Applying window insets

    To fix the problem, programmatically apply window insets:

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    setContentView(R.layout.activity_main)
    val textView = findViewById<TextView>(R.id.textView)
    textView.text = LoremIpsum(words = 1000).values.joinToString { it }
    ViewCompat.setOnApplyWindowInsetsListener(textView) { v, windowInsets ->
    // Here you need to determine which specific insets you want to combine with your `TextView` insets.
    // Choosing `WindowInsetsCompat.Type.systemBars()` would mean both status bar and navigation bar insets.
    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
    v.updateLayoutParams<ViewGroup.MarginLayoutParams> {
    leftMargin = insets.left
    topMargin = insets.top
    bottomMargin = insets.bottom
    rightMargin = insets.right
    }
    // Return `CONSUMED` if you don't want the window insets to keep passing
    // down to descendant views. Otherwise, just return `windowInsets
    WindowInsetsCompat.CONSUMED`.
    }
    }

    Now the content is fully visible and interactive.

    Result

    Conclusion

    This post explored approaches using both Compose and XML to handle edge-to-edge behavior on Android 15+. These methods ensure your app content remains fully visible and interactive, keeping system bars from overlapping any UI elements.

    With these strategies, you can confidently adopt edge-to-edge layouts while maintaining usability and accessibility.

    Rafał Wesołowski

    Rafał Wesołowski

    Android Senior Software Engineer

    Explore related topics

    FREE TRIAL Ready to get started?