Sunstone Apps
← Voltar para o blog

Builds Nativos Expo

Quando um upgrade do Expo transforma sua splash screen em tela branca

Tela branca com spinner padrão não é problema de layout. Depois de um upgrade do Expo SDK, muitas vezes é configuração nativa esperando o próximo prebuild.

Publicado: 8 min de leitura
Imagem de prévia OpenGraph deste artigo. Quando um upgrade do Expo transforma sua splash screen em tela branca

A parte desconfortável de um bug de splash screen é que ele pode parecer pequeno demais para ser tratado como bug de build. O app ainda abre. A tela de carregamento em JavaScript ainda renderiza. Os testes continuam verdes. Mesmo assim, os primeiros segundos do lançamento nativo passam a mostrar uma tela branca genérica com um indicador de progresso no meio, em vez da splash com a identidade visual que o app mostrava antes.

Foi isso que vimos depois de um upgrade de dependências em um app React Native com Expo. Antes do upgrade, a splash nativa mostrava o ícone do app sobre o mesmo fundo claro usado pela splash em JavaScript. Depois do upgrade, no Android, apareceu uma tela branca com alguns círculos de loading no centro. O app não crashava. Não havia erro óbvio de TypeScript. A regressão visual acontecia antes da árvore React ter controle suficiente para corrigir qualquer coisa.

A causa não estava no componente SplashScreen em React. Estava na configuração nativa da splash.

Durante um tempo, o projeto ainda usava o bloco legado de splash no app.json. Ele parecia razoável porque tinha os mesmos campos que muitos apps Expo usaram por anos: imagem, modo de redimensionamento e cor de fundo.

Depois do upgrade do SDK, depender desse formato legado virou a fonte errada de verdade. O caminho atual do Expo é o pacote expo-splash-screen com o seu config plugin. É esse plugin que escreve os arquivos nativos gerados durante o prebuild. Se ele não está instalado e configurado, o app pode cair na tela de lançamento padrão da plataforma, mesmo que o componente em JavaScript fique correto depois que aparece.

Separe a splash nativa da splash em JS

A primeira pergunta útil não foi “por que meu componente de splash está branco?”. Foi “qual splash está branca?”.

A maioria dos apps Expo tem dois momentos diferentes que todo mundo chama de splash screen. O primeiro é nativo. Ele é desenhado pelo Android ou iOS antes do JavaScript estar pronto. O segundo é renderizado pelo app. É o componente React que você mostra enquanto inicializa banco local, traduções, consentimento, compras, remote config ou qualquer outra coisa do bootstrap.

Essas duas telas podem usar a mesma arte e a mesma cor, mas não são controladas pelo mesmo código.

No nosso app, o componente React de splash era simples. Ele renderizava uma tela inteira, usava o ícone do app e tinha a mesma cor de fundo que costumava estar declarada no app.json. Essa parte ainda funcionava. Quando o JavaScript carregava, o app mostrava a tela de carregamento com mensagem e estado de erro.

A parte quebrada acontecia antes. O Android mostrava a tela genérica de lançamento antes do componente React montar. Isso significa que alterar o componente React seria trabalho sem efeito. A tela nativa de lançamento precisava ser regenerada com o plugin correto.

Essa separação importa porque mantém a correção pequena. Sem ela, é tentador adicionar mais views de fallback, trocar o background da raiz, esconder status bar mais cedo ou atrasar o startup. Nenhuma dessas mudanças escreve os recursos nativos da splash.

Verifique a config do app, não só o componente

O projeto tinha um app.json com cara normal: ícone do app, adaptive icon, imagem de splash e uma lista de config plugins. A parte suspeita era o que não estava ali. Não havia dependência expo-splash-screen no pacote do app e não havia entrada expo-splash-screen no array plugins.

Essa lacuna é fácil de não ver porque o bloco legado de splash ainda parece uma configuração válida. Ele também continua aparecendo em vários apps antigos, exemplos e docs internas. Mas a documentação atual do Expo recomenda o config plugin porque ele configura propriedades nativas que exigem novo binário.

O formato certo ficou assim:

{
  "expo": {
    "plugins": [
      [
        "expo-splash-screen",
        {
          "image": "./assets/icon.png",
          "imageWidth": 112,
          "resizeMode": "contain",
          "backgroundColor": "#F8FAFC"
        }
      ]
    ]
  }
}

Mantivemos os valores sem inventar moda. Mesmo ícone. Mesmo fundo. Mesmo comportamento contain. A única escolha de tamanho foi imageWidth: 112, que bate com o tamanho da imagem na splash em JavaScript. Esse número não é regra universal. Ele só deixou a transição entre nativo e JS parecida neste app.

O ponto importante não foi o número. Foi mover a responsabilidade da tela nativa de lançamento para o plugin.

Instale com o gerenciador do workspace

A correção parece um comando só:

expo install expo-splash-screen

Em um monorepo real, até isso pode ter uma armadilha. Este workspace declarava packageManager: pnpm@11.9.0, mas o binário global de pnpm na máquina ainda era 9.15.4. Ao rodar expo install, a CLI tentou adicionar o pacote pelo major errado do pnpm e bateu em erro de localização inesperada do store.

Essa falha foi útil. Ela impediu que a dependência fosse adicionada com um layout de gerenciador diferente do contrato do repositório. A correção não foi mudar configuração global do pnpm. Foi usar a versão que o repositório declara:

npx pnpm@11.9.0 --filter sudoku add expo-splash-screen@~57.0.1

O próprio Expo já tinha informado a versão compatível com o SDK: ~57.0.1. Usamos essa versão, atualizamos o lockfile e não tocamos no ambiente global do desenvolvedor.

Se você encontrar esse tipo de erro de store ao instalar uma dependência nativa, pare antes de “consertar” a máquina. Em um repo compartilhado, a versão do gerenciador de pacotes faz parte do contrato de build. O shell local é só uma forma de rodá-lo.

Remova o legado quando o plugin existir

Depois de adicionar o plugin, removemos o bloco antigo de splash. Manter os dois deixaria a configuração mais difícil de raciocinar. Duas partes do app.json pareceriam definir o mesmo comportamento de lançamento, mas só uma seria a fonte que queremos confiar.

Essa é uma limpeza pequena que evita o próximo bug. Se alguém no futuro muda o background legado da splash enquanto o plugin mantém outro backgroundColor, o repositório acabou de criar uma divergência nova. O app talvez ainda compile, mas a próxima investigação começa com uma pista falsa.

Também atualizamos o comentário no componente React de splash. Antes ele dizia que o background batia com a config legada de splash. Isso era verdade antes da migração e enganoso depois dela. O comentário agora aponta para a configuração do plugin expo-splash-screen.

Comentários próximos de código que faz ponte visual precisam continuar honestos. A transição de splash é um contrato entre recursos nativos e UI React. Se o comentário nomeia a fonte de verdade errada, ele fica pior do que comentário nenhum.

Mudanças de splash nativa exigem build nativo

Um detalhe fácil de esquecer: alterar app.json e o lockfile não basta para ver a correção em um aparelho que já tem um binário nativo antigo instalado.

O plugin escreve recursos nativos durante o prebuild, e esses recursos entram no APK, AAB ou build iOS. No nosso fluxo, o caminho Android release-like já roda:

expo prebuild --platform android --no-install

por meio dos scripts pnpm sudoku:apk e pnpm sudoku:aab. Isso significa que o próximo APK release aplica o plugin sem passo manual extra. Mas um build antigo já instalado não muda magicamente sua tela de lançamento só porque o JavaScript mudou.

Isso importa na validação. Se o bug está na splash nativa, não valide apenas com TypeScript, testes unitários ou build web. Esses checks continuam úteis, mas não provam que a tela nativa de lançamento foi corrigida. Para isso, você precisa de um build nativo novo ou, pelo menos, conferir o resultado do prebuild.

Mesmo assim, rodamos os checks normais:

  • TypeScript, para pegar problemas de import, config e tipagem;
  • lint do app, para detectar problemas de integração React e Expo;
  • expo config --type public, para confirmar que o plugin aparece na config resolvida e que o bloco legado splash sumiu;
  • Prettier, porque mudança em JSON e docs não deveria adicionar ruído.

Esses checks deixaram a mudança segura para seguir. A confirmação visual final pertence a um app nativo reconstruído.

Checklist prático

Quando a splash screen quebra depois de um upgrade do Expo, este é o checklist que eu rodaria antes de mexer em layout:

  1. Identificar se a tela quebrada é o lançamento nativo ou a UI de loading renderizada pelo React.
  2. Verificar se expo-splash-screen está instalado no pacote do app.
  3. Verificar se expo-splash-screen está configurado no array plugins.
  4. Mover imagem, largura, resize mode e cor de fundo para a configuração do plugin.
  5. Remover o bloco legado de splash se o plugin virou a fonte de verdade.
  6. Atualizar comentários e docs que ainda citam a fonte antiga.
  7. Rodar expo config --type public e conferir a config resolvida.
  8. Rebuildar o app nativo antes de julgar o resultado no aparelho.

O conserto eficiente não é adicionar outro componente de loading. O conserto eficiente é deixar a configuração nativa correta, única e sem ambiguidade.

Neste caso, a mudança de código foi pequena: uma dependência, uma entrada de plugin, um bloco legado removido e um comentário atualizado. O valor não veio de esperteza. Veio de recusar investigar um problema de lançamento nativo de dentro do React.

Esse é um padrão comum em upgrades do Expo. O app pode parecer quase igual enquanto o contrato de build muda por baixo. Quando isso acontece, a correção certa raramente é mais UI. É encontrar a fonte de verdade que ainda participa do binário.