Anatomia das barras do sistema no Jetpack Compose: status bar e navigation bar com enableEdgeToEdge
Como funcionam a status bar e a navigation bar no Android, o que o edge-to-edge mudou e como configurá-las no Jetpack Compose com enableEdgeToEdge e insets.

Já tive publicações recusadas na App Store por causa da status bar. O problema era contraste: a cor dos ícones do sistema não combinava com o fundo da tela, e a informação do topo ficava difícil de ler. No Android, o mesmo descuido costuma aparecer no rodapé, com o ícone de voltar branco sobre uma navigation bar branca, e o usuário não encontra como sair da tela.
Esse tipo de erro não depende do framework. No Jetpack Compose, o edge-to-edge mudou a regra: pintar a status bar com a cor do app deixou de funcionar, e a cor dos ícones passou a depender do que o app desenha atrás deles. Por isso resolvi escrever sobre a anatomia dessas barras, como elas se comportam e como configurá-las no Compose.
O que é do sistema e o que é do app

No Android, a tela tem duas faixas que não pertencem ao app. No topo fica a status bar, com hora, rede e bateria, dividindo espaço com o recorte da câmera. No rodapé fica a navigation bar, que pode ser a de 3 botões (voltar, início e recentes) ou só a barra de gestos.
O app não desenha os ícones dessas barras. O que ele controla são duas coisas: o que aparece atrás delas e se os ícones do sistema vão ser claros ou escuros. A primeira resposta vem do layout do app. A segunda é uma configuração que muita gente nunca toca, e é ela que decide se a hora e o botão de voltar vão ser legíveis.
O erro que normalmente acontece

Esse erro chega até a loja porque depende de contexto. Quem desenvolve costuma testar com um tema e um modo de navegação, geralmente o de gestos, em que a barra de baixo quase não aparece. O usuário usa o tema escuro, ou a navegação de 3 botões, ou chega numa tela sem TopAppBar, como a splash, o onboarding ou um player de vídeo. Essas telas herdam o estilo da tela anterior, que foi pensado para outro fundo.
A crença que sustenta o problema é "a barra é do sistema, então o sistema cuida dela". Ela tem uma parte verdadeira: quem desenha a hora e a bateria é o sistema operacional. Mas a cor desses ícones é uma decisão do app, e o sistema não confere se ela combina com o que está atrás.
Edge-to-edge mudou a regra no Android

Por muito tempo, o jeito de resolver era pintar a status bar com a cor do app. A partir do Android 15, todo app com targetSdk 35 desenha de ponta a ponta por padrão: o conteúdo vai para trás da status bar e da navigation bar. O setStatusBarColor passou a ser ignorado, a navegação por gestos fica transparente e a de 3 botões ganha um fundo translúcido com 80% de opacidade. No Android 15 ainda dava para desativar esse comportamento, mas para apps com targetSdk 36, no Android 16, essa opção deixou de existir.
Na prática, sobram duas responsabilidades. A primeira é escolher a cor dos ícones de acordo com o fundo que vai ficar atrás deles. A segunda é aplicar os insets, as medidas de quanto cada barra ocupa, para que o fundo passe por trás mas o conteúdo interativo não fique embaixo da hora nem da barra de gestos.
Como cada framework chama claro e escuro

Antes de escrever código, responda uma pergunta: o fundo atrás da barra é claro ou escuro? Fundo claro pede ícones escuros, e fundo escuro pede ícones claros.
A confusão é que cada framework dá nome a isso de um jeito. No Flutter, o nome descreve o ícone, então ícones escuros são dark. No Compose e no SwiftUI, o nome descreve o fundo, então os mesmos ícones escuros são light.
No Jetpack Compose: enableEdgeToEdge

No Compose, a configuração fica na Activity. O enableEdgeToEdge liga o modo de ponta a ponta também nas versões anteriores ao Android 15 e recebe o estilo de cada barra:
import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge(
// fundo claro atrás da status bar: ícones escuros
statusBarStyle = SystemBarStyle.light(
scrim = Color.TRANSPARENT,
darkScrim = Color.BLACK,
),
// navigationBarStyle fica no padrão: transparente nos gestos,
// translúcida na navegação de 3 botões
)
setContent { MeuAppTheme { AppNavHost() } }
}
}O nome aqui é SystemBarStyle.light, e o resultado são ícones escuros, o inverso do preset do Flutter com o mesmo nome. O darkScrim só entra em aparelhos antigos, em que o sistema não consegue desenhar ícones escuros. Se o app acompanha o tema escuro do sistema, troque por SystemBarStyle.auto, que alterna sozinho.
Para uma tela que foge do padrão, o caminho é ajustar o WindowInsetsControllerCompat enquanto ela está na composição e devolver o valor anterior quando ela sai:
@Composable
fun StatusBarIcons(escuros: Boolean) {
val view = LocalView.current
if (view.isInEditMode) return
DisposableEffect(escuros) {
val window = (view.context as Activity).window
val controller = WindowCompat.getInsetsController(window, view)
val anterior = controller.isAppearanceLightStatusBars
controller.isAppearanceLightStatusBars = escuros
onDispose { controller.isAppearanceLightStatusBars = anterior }
}
}
@Composable
fun PlayerScreen() {
StatusBarIcons(escuros = false) // fundo preto, ícones claros
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.safeDrawingPadding(),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = "Reproduzir",
tint = Color.White,
modifier = Modifier.size(96.dp),
)
}
}O onDispose faz o papel que o AnnotatedRegion faz no Flutter: quando o player sai da tela, os ícones voltam a ser escuros. O safeDrawingPadding() é o equivalente ao SafeArea.
Nas listas, o Scaffold entrega os insets prontos no innerPadding:
@Composable
fun PerfilScreen(itens: List<String>) {
Scaffold { innerPadding ->
LazyColumn(contentPadding = innerPadding) {
items(itens) { item ->
Text(item, modifier = Modifier.padding(16.dp))
}
}
}
}Passar o innerPadding como contentPadding, e não como Modifier.padding, é o que deixa a lista rolar por trás das barras sem esconder o primeiro e o último item.

Antes de mandar para a loja
A maior parte desses erros só aparece em uma combinação específica de tema, navegação e tela. Antes de cada envio, vale passar por três cenários:
Tema claro e escuro. Troque o tema do sistema com o app aberto e navegue por todas as telas.
Navegação de 3 botões. Ative nas configurações do Android. É nela que o botão voltar some quando o contraste está errado.
Telas sem TopAppBar. Splash, onboarding, player, câmera e qualquer tela com imagem atrás da status bar.
A regra que vale para qualquer framework
Toda decisão sobre as barras do sistema começa pela cor do fundo atrás delas. Com essa resposta, o nome de cada API vira detalhe de tradução, e o resto é aplicar os insets para o conteúdo não ficar embaixo da hora nem dos gestos.
Nenhuma dessas APIs confere a sua configuração. O fundo translúcido do Android protege a navegação de 3 botões, mas quando o app declara a cor errada, o ícone branco sobre fundo branco chega até a revisão da loja do mesmo jeito.
Depois das barras, o próximo passo são os outros insets que o edge-to-edge deixou expostos: o teclado, o recorte da câmera quando o app gira para paisagem e a dobra dos aparelhos dobráveis. Em todos eles o sistema ocupa uma parte da tela, e o app precisa saber qual.
Continue lendo
Aprimorando a Experiência do Usuário com Teclados no iOS usando Keyboard Actions
Você nunca mais vai lutar contra o Teclado do iOS no Flutter! Veja como resolver um problema comum em aplicativos mobile para iOS que é…
Flutter Widgetbook: Como Documentar seu Design System do jeito certo!
Como o Widgetbook resolve o problema de documentar componentes em projetos Flutter com design system próprio. No Direção Fácil, virou catálogo visual, contrato com o Figma e contexto preciso para o Claude Code gerar telas usando os widgets certos.
Criando um Agente de IA no Claude Code para Controlar meu Smartphone
Automatizando documentação e testes de apps Android com inteligência artificial. Eu estava enfrentando um desafio específico: precisava documentar automaticamente diversos fluxos de uma aplicação Android. Foi quando comecei a explorar maneiras de realizar esse processo, e uma das ferramentas que decidi testar foi o Claude Code.