Skip to content
CW
Navegação

Idioma

Navegação

Voltar para o Blog
28 jul. 2026 9 min leitura 27 Visualizações
O Android 15 Matou Minha Status Bar: O Fix Que Realmente Funciona no .NET MAUI

O Android 15 Matou Minha Status Bar: O Fix Que Realmente Funciona no .NET MAUI

Uma atualização para Android 15 em produção fez os ícones da status bar sumirem do nada. Aqui está a causa raiz real e o fix em quatro partes que funciona do Android 13 ao 15+, com ou sem Shell.

Começou Com um Chamado de Suporte

"O relógio sumiu do topo do app." Esse foi o chamado inteiro. Sem stack trace, sem log de crash — só uma barra preta onde antes ficavam o relógio, a bateria e o Wi-Fi.

O aparelho: um celular que tinha acabado de receber a atualização para Android 15. O resto do app estava normal. Só a status bar tinha apagado — literalmente. Ícones brancos sobre fundo preto, exceto que o fundo também não estava mais sendo desenhado como preto. Estava simplesmente invisível.

Se você mantém um app .NET MAUI e ainda não bateu nesse problema, vai bater. Aqui está o que realmente está acontecendo, e o fix que se sustenta no Android 13, 14, 15 e no que vier depois.

A Causa Raiz: o Android 15 Parou de Escutar

O Android 15 (API 35) tornou a renderização edge-to-edge obrigatória. Todo app agora desenha o conteúdo por trás das barras do sistema por padrão — status bar, navigation bar, tudo. Esse é o novo normal, e não vai voltar atrás.

O problema é o que isso fez com a forma antiga de estilizar a status bar. Por anos, isso era suficiente:

<style name="AppTheme">
    <item name="android:statusBarColor">#000000</item>
    <item name="android:windowLightStatusBar">false</item>
</style>

No Android 14 e anteriores, statusBarColor e windowLightStatusBar faziam exatamente o que prometiam. A partir do Android 15, o sistema ignora os dois atributos completamente. Não é um bug do lado do Google — é uma depreciação deliberada em favor do WindowInsetsControllerCompat, a API que deveria substituir a estilização por atributos de tema em todo lugar.

Então a cadeia de falha é essa:

  1. O edge-to-edge agora é forçado
  2. Os atributos antigos statusBarColor / windowLightStatusBar são silenciosamente ignorados
  3. Nada mais no app define a cor do ícone explicitamente
  4. O sistema cai para um padrão que, no nosso caso, deixou ícones brancos invisíveis contra uma barra renderizada clara

E não foi só em dispositivos Android 15. O mesmo sintoma apareceu em um celular Android 13, porque o próprio MAUI estava discretamente sobrescrevendo o tema durante a transição Maui.SplashThemeMaui.MainTheme.NoActionBar na inicialização. Dois bugs diferentes, um sintoma visível.

O Fix Tem Três Camadas

Não existe uma flag única que resolve isso em todo lugar. O que funcionou foi empilhar três defesas independentes — cada uma tratando uma versão de Android ou um modo de falha diferente.

Camada 1 — Manda o Android 15 Recuar

O fix mais limpo para o novo comportamento obrigatório de edge-to-edge é recusá-lo, restaurando a renderização antiga e previsível da status bar.

Crie Platforms/Android/Resources/values-v35/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="OptOutEdgeToEdgeEnforcement">
        <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
    </style>
</resources>

O nome da pasta values-v35 não é arbitrário — é um qualificador de resource do Android. Qualquer coisa dentro de uma pasta values-v35 só é carregada em dispositivos rodando API 35 (Android 15) ou superior. Em qualquer aparelho mais antigo, esse arquivo inteiro é invisível para o sistema, como se não existisse.

Esse é exatamente o problema: o MainActivity.cs aplica esse style incondicionalmente, em todo dispositivo, independente da versão do Android:

protected override void OnCreate(Bundle? savedInstanceState)
{
    Theme?.ApplyStyle(Resource.Style.OptOutEdgeToEdgeEnforcement, force: false);
    base.OnCreate(savedInstanceState);
}

Resource.Style.OptOutEdgeToEdgeEnforcement é uma referência gerada em tempo de compilação. Se o style chamado OptOutEdgeToEdgeEnforcement existisse só dentro de values-v35, um dispositivo Android 13 ou 14 — que nunca carrega essa pasta — não teria nenhum resource com esse nome. O app quebraria tentando aplicar um style que, do ponto de vista daquele dispositivo, simplesmente não existe.

O fix é um placeholder: definir o mesmo nome de style de novo, mas vazio, no Platforms/Android/Resources/values/styles.xml normal — a pasta que é carregada em todas as versões do Android, sem precisar de qualificador:

<resources>
    <style name="AppTheme" parent="Maui.SplashTheme">
        <!-- seus atributos de tema existentes -->
    </style>

    <style name="OptOutEdgeToEdgeEnforcement">
    </style>
</resources>

Isso dá ao Android duas versões do mesmo nome de style para escolher, e ele pega a certa automaticamente com base na versão da API:

  • Android 15+ carrega values-v35/styles.xml — pega o atributo real, recusa o edge-to-edge
  • Android 13/14 carrega values/styles.xml — pega o placeholder vazio, que não faz absolutamente nada

A mesma linha de código C#, dois resultados completamente diferentes, zero crashes nos dois casos.

Aplique o style antes de base.OnCreate. Se aplicar depois, já é tarde demais — a janela já foi configurada.

Vale saber: o Google já sinalizou esse atributo como temporário. É uma ponte, não uma solução permanente — e é exatamente por isso que essa é a camada um, não o fix inteiro.

Camada 2 — StatusBarBehavior via CommunityToolkit.Maui

Essa é a parte que realmente deixa o app preparado para o futuro, em vez de só remendar em volta do Android 15. O CommunityToolkit.Maui.Behaviors.StatusBarBehavior embrulha o WindowInsetsControllerCompat no Android (e o mecanismo nativo equivalente no iOS) — é um behavior cross-platform, não um workaround só de Android, e é a API que o Google realmente quer que você use daqui pra frente.

Instalando, se o projeto ainda não referencia:

dotnet add package CommunityToolkit.Maui

No momento em que este post foi escrito, isso puxa a versão 15.0.0. O CommunityToolkit.Maui.Core vem junto como dependência transitiva — não precisa referenciar à parte.

Registrando no MauiProgram.cs:

var builder = MauiApp.CreateBuilder();
builder
    .UseMauiApp<App>()
#if !MACCATALYST
    .UseMauiCommunityToolkit()
#endif
    .ConfigureFonts(fonts =>
    {
        // sua configuração de fontes
    });

Aquele #if !MACCATALYST não é paranoia de boilerplate — a inicialização do toolkit não roda em builds Mac Catalyst, então embrulhe a chamada do mesmo jeito se seu app também tiver esse alvo.

Usando, o StatusBarBehavior expõe duas propriedades que importam aqui:

  • StatusBarColor — uma Color, ex: Black
  • StatusBarStyle — um enum: Default, LightContent (ícones brancos, pra barras escuras), ou DarkContent (ícones escuros, pra barras claras)
<ContentPage.Behaviors>
    <toolkit:StatusBarBehavior
        StatusBarColor="Black"
        StatusBarStyle="LightContent" />
</ContentPage.Behaviors>

Agora a parte que realmente pega as pessoas de surpresa. Se seu app é Shell-routed — múltiplas entradas ShellContent, navegação feita via GoToAsync, páginas vivendo dentro da própria hierarquia de páginas do Shell — um StatusBarBehavior no AppShell já basta. Ele cascateia pra toda rota.

A maioria dos apps MAUI reais não é construída assim, porém — especialmente os migrados de Xamarin.Forms. É comum ter o Shell só embrulhando uma única página raiz (uma home com abas, por exemplo), enquanto toda outra tela — formulários de edição, diálogos de filtro, telas de detalhe — é aberta com Navigation.PushModalAsync(page) em vez de roteamento Shell. É exatamente assim que esse app funciona: o AppShell.xaml hospeda um único ShellContent, e toda página secundária passa por um helper de navegação customizado que chama PushModalAsync diretamente na pilha INavigation do app.

Páginas abertas desse jeito ficam completamente fora da árvore de páginas do Shell. Um behavior anexado ao AppShell não tem caminho até elas — o Shell não consegue cascatear algo pra uma página que ele nem conhece. Então se esse for o formato do seu app também, a página raiz recebe um StatusBarBehavior:

<ContentPage
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit">

    <ContentPage.Behaviors>
        <toolkit:StatusBarBehavior
            StatusBarColor="Black"
            StatusBarStyle="LightContent" />
    </ContentPage.Behaviors>

    <!-- resto do conteúdo -->
</ContentPage>

E toda página modal aberta fora do Shell recebe a sua própria cópia:

<ContentPage ...>

    <ContentPage.Behaviors>
        <toolkit:StatusBarBehavior
            StatusBarColor="Black"
            StatusBarStyle="LightContent" />
    </ContentPage.Behaviors>

    <!-- conteúdo da página -->
</ContentPage>

Em um app real isso significa passar por cada página modal, uma por uma. Nesse caso, foram 22 delas — formulários de edição, diálogos de filtro, telas de avaliação. Repetitivo, mas mecânico: um bloco de XAML, repetido. Se você estiver auditando um app pra isso, procure onde seu helper de navegação chama PushModalAsync — toda página do outro lado dessa chamada precisa do behavior.

Camada 3 — Parar o Conteúdo de Deslizar Por Baixo das Barras

O .NET MAUI 10 mudou discretamente o padrão de SafeAreaEdges de Container para None. Combinado com a renderização edge-to-edge, isso significa que o conteúdo da página agora pode desenhar por baixo da status bar e da navigation bar — mesmo depois que a própria status bar já parece correta de novo.

Resolva isso uma vez, globalmente, em vez de configurar em cada página:

<Style TargetType="ContentPage" ApplyToDerivedTypes="True">
    <Setter Property="SafeAreaEdges" Value="Container" />
</Style>

Coloque isso no dicionário de recursos de estilos global e toda ContentPage — inclusive classes base customizadas derivadas dela — volta a respeitar os insets das barras do sistema.

Verificando Se Realmente Funcionou

Nada disso vale muito sem testar na faixa de versões que quebrou originalmente:

dotnet build -f net10.0-android -c Debug

Depois confira em hardware real ou emuladores cobrindo:

  • Android 13–14 — deve ficar exatamente como estava antes de tudo isso (sem regressão)
  • Android 15+ — status bar visível, fundo preto, ícones brancos, relógio e bateria legíveis de novo

E não pare na status bar. Confira se o SafeAreaEdges="Container" não introduziu cortes novos ou scroll inesperado em páginas com fundo escuro ou layouts full-bleed.

Por Que Vale a Pena Acertar Isso

A status bar não é decoração. É o único lugar onde o usuário confere se tem sinal, quanto de bateria resta, e se algum processo em background morreu silenciosamente. Quando ela fica invisível, o app não parece quebrado — parece que o celular está quebrado, e essa é uma impressão muito pior de deixar.

Também é uma prévia de para onde o Android está indo. Edge-to-edge não é uma peculiaridade do Android 15 que vai ser revertida — é a direção que a plataforma assumiu. Apps que adotam o WindowInsetsControllerCompat (via StatusBarBehavior) agora, em vez de se apoiar na flag de opt-out como solução permanente, não vão precisar passar por isso de novo no Android 16 ou 17.

Resumo

Quatro mudanças, quatro modos de falha diferentes cobertos:

  1. Recuse o edge-to-edge na API 35+ para que a status bar volte a renderizar de forma previsível — e não esqueça o style placeholder vazio em values/, ou dispositivos mais antigos crasham
  2. Instale e registre o CommunityToolkit.Maui, depois use StatusBarBehavior em vez de atributos de tema deprecados — essa é a parte que realmente prepara o app para o futuro
  3. Confira como seu app navega. Apps Shell-routed precisam do behavior uma vez, no AppShell. Apps que empurram páginas via PushModalAsync fora do Shell — que é a maioria dos apps MAUI reais — precisam dele em cada página modal individualmente
  4. Restaure SafeAreaEdges="Container" globalmente para que o conteúdo pare de deslizar por baixo das barras

Nenhuma dessas quatro mudanças sozinha teria resolvido. Juntas, a status bar voltou — em todas as versões de Android que conseguimos testar.

Cezar Wagenheimer

Escrito por

Cezar Wagenheimer

Compartilhe

Comentários

Ficou com uma dúvida ou encontrou um problema no código? Comente abaixo — eu leio e respondo pessoalmente.

Carregando comentários…

Deixe seu comentário

Continue lendo