Quick Notes about Edge to Edge

When upgrading an app to target Android 15 (targetSdk 35), edge to edge layouts are enabled by default. While the documentation about doing this for Compose and Views is very detailed and has everything needed for the migration, I want to call out some quick points that weren’t immediately obvious (or that I missed in the documentation the first time).1

Android 29 and Below

Typically, handling edge to edge is just a matter of targeting Android 35, calling enableEdgeToEdge(), and applying the appropriate insets to the layouts to make things look correct. Setting these insets usually takes the form of:

ViewCompat.setOnApplyWindowInsetsListener(view) { view, windowInsets ->
    // extract the insets we care about for this view
    val insets = windowInsets.getInsets(
        WindowInsetsCompat.Type.systemBars() or
        WindowInsetsCompat.Type.displayCutout()
    )

    // apply the padding
    view.updatePadding(left = insets.left, top = insets.top, right = insets.right, bottom = insets.bottom)
    // or margin
    // (requires a MarginLayoutParams friendly parent, ex FrameLayout)
    view.updateLayoutParams<MarginLayoutParams> {
        topMargin = insets.top
        leftMargin = insets.left
        bottomMargin = insets.bottom
        rightMargin = insets.right
    }

    // return a WindowInsetsCompat here removing whatever insets we
    // consumed that children should receive, or CONSUMED to consume all.
    WindowInsetsCompat.CONSUMED
}

One of the things I noticed was that, after getting the layout working properly on Android 35, it looked like my insets were being ignored on Android 24 when I tested it there.

Whenever we apply an OnApplyWindowInsetsListener and consume the insets, Android 29 and below do not dispatch the insets to siblings. This causes sibling views to never have their listener fired. This is described more in the documentation here. There are two fixes for this.

The first, described in the documentation, was added in the 1.16.0-alpha01 or above versions of androidx’s core / core-ktx libraries (the latest as of this writing is androidx.core:core-ktx:1.16.0-rc01). It can be used by calling this function to allow Android 29 and below to behave similarly to Android 30 and above.

// before setting window insets listeners and applying them
ViewGroupCompat.installCompatInsetsDispatch(rootView)

Note that this fix also fixes the same issue happening with usages of android:fitsSystemWindows="true".

A second fix would be just to return the insets back from the listener (instead of consuming them). This has the implication that children of this ViewGroup would get these same insets again and have the option to react to them, which can cause double padding if it doesn’t make sense to do so (for example, if the parent handles the insets for the navigation bar, there’s likely no reason for its children to do so).

1960s Navigation on Android 29 and Above

If you run the app on Android 29 and above on a device with the 1960s (3 button) navigation style, you’ll get a transluscent scrim on top of the navigation area. Adding this will remove it:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    window.setNavigationBarContrastEnforced(false)
}

Before / After

RecyclerView

The documentation mentions setting setting clipToPadding to false to allow items to be drawn under the paddings set for insets. Using this, in combination with setting the bottom padding on the RecyclerView, will ensure that the last item is not obscured. Alternatively, an ItemDecoration can be used to add the spacing at the bottom.


  1. After writing this, I looked back at a project I migrated a few months back and forgot that I had seen and worked around some of these same problems before. Probably a good reason to document things earlier in the future. ↩︎