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.
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 legadosplashsumiu;- 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:
- Identificar se a tela quebrada é o lançamento nativo ou a UI de loading renderizada pelo React.
- Verificar se
expo-splash-screenestá instalado no pacote do app. - Verificar se
expo-splash-screenestá configurado no arrayplugins. - Mover imagem, largura, resize mode e cor de fundo para a configuração do plugin.
- Remover o bloco legado de splash se o plugin virou a fonte de verdade.
- Atualizar comentários e docs que ainda citam a fonte antiga.
- Rodar
expo config --type publice conferir a config resolvida. - 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.