Stop Fighting the System Bars
For years, Android developers used flags like SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN to make their apps look modern. Those flags are now deprecated. In the modern era of Android development, we use Window Insets. Insets tell your app exactly where the system UI (like the status bar, navigation bar, and the keyboard) sits so you can move your views out of the way without breaking the immersive experience.
The 'Edge-to-Edge' Foundation
The first step is telling the system that your app wants to draw behind the system bars. Instead of the old XML hacks, use the WindowCompat utility in your Activity's onCreate method.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// This tells Android to draw your app content behind the system bars
WindowCompat.setDecorFitsSystemWindows(window, false)
setContentView(R.layout.activity_main)
}Once you enable this, your UI will likely be covered by the status bar at the top and the navigation bar at the bottom. This is where most developers get stuck and revert to fitsSystemWindows="true", which often ruins the look of custom backgrounds.
Precision Control with WindowInsetsCompat
The correct way to handle this is to apply padding only to the specific views that need it. For example, you might want your background image to fill the whole screen, but your 'Submit' button needs to stay above the navigation bar. You can achieve this using ViewCompat.setOnApplyWindowInsetsListener.
val bottomButton = findViewById<Button>(R.id.submit_button)
ViewCompat.setOnApplyWindowInsetsListener(bottomButton) { view, windowInsets ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
// Apply the bottom inset as padding so the button isn't covered
view.updatePadding(bottom = insets.bottom)
// Return the insets so they can continue to propagate to other views
windowInsets
}Handling the Keyboard (IME) Dynamically
One of the best features of the modern Insets API is the ability to handle the software keyboard (IME) gracefully. In the past, we relied on adjustResize in the Manifest, which was often unpredictable. Now, you can listen for the keyboard specifically.
ViewCompat.setOnApplyWindowInsetsListener(rootView) { v, insets ->
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
// Adjust your layout based on whether the keyboard is open
mainContainer.setPadding(0, systemBars.top, 0, imeInsets.bottom)
insets
}Why This Matters for UX
Using Window Insets allows for a much more fluid experience. When you use the system bar types, your app automatically reacts to different gesture navigation modes or punch-hole camera cutouts without you needing to hardcode pixel values. It makes your app feel like a native part of the OS rather than a box trapped between black bars. By mastering these listeners, you ensure that your UI is always accessible, readable, and visually stunning regardless of the device hardware.




