Tema
Migrar do CodePush
A Microsoft desligou o App Center em 31 de março de 2025, e o CodePush saiu do ar junto. Apps com o react-native-code-push continuam abrindo com o último bundle que receberam, mas não recebem mais nada pelo ar.
Este guia mostra como trocar o CodePush pelo Pipa no Ar num app React Native: o que corresponde a quê, o que tirar do projeto e o que colocar no lugar.
A build nativa vai primeiro
O SDK de update fica dentro da build nativa. Aparelhos com uma build antiga, que ainda tem o CodePush, não recebem nenhum update do Pipa no Ar até instalar pela loja uma build que já tenha o SDK do hot-updater.
Por isso a ordem é: publicar na loja a build nova com o SDK do hot-updater, esperar a adoção subir e só então contar com os updates pelo ar para todo mundo. Se o seu app já tem um aviso de versão mínima, use-o para levar os usuários para a build nova.
O que corresponde a quê
| CodePush | Pipa no Ar |
|---|---|
| App no App Center (um para iOS, outro para Android) | Um app no painel cobre iOS e Android |
Deployment (Staging, Production) | Canal (production, beta, o nome que quiser) |
Deployment key no Info.plist e no strings.xml | Canal gravado na build nativa e API key do app no HotUpdater.wrap |
codePush(options)(App) ou codePush.sync() | HotUpdater.wrap({ ... })(App) |
checkFrequency: ON_APP_START | Comportamento padrão do HotUpdater.wrap: checa ao abrir o app |
checkFrequency: MANUAL com codePush.sync() | HotUpdater.init() com HotUpdater.checkForUpdate() (fluxo manual) |
InstallMode.ON_NEXT_RESTART | Comportamento padrão: baixa agora e aplica no próximo início |
Update mandatory com InstallMode.IMMEDIATE | Atualização forçada (-f no deploy ou no painel) |
appcenter codepush release-react | npx hot-updater deploy -p ios e -p android |
--target-binary-version | -t com updateStrategy: "appVersion", ou a estratégia fingerprint |
--rollout | -r no deploy, ou a porcentagem no painel |
appcenter codepush patch | Abrir a release no painel, ou npx hot-updater bundle update |
appcenter codepush rollback | Desativar a release |
appcenter codepush promote | Promote entre canais |
Rollback automático quando o app não chama notifyAppReady | O SDK descarta sozinho um bundle que faz o app crashar ao abrir |
| Code signing com chave própria | Assinatura gerenciada |
Duas diferenças que mudam o dia a dia:
- Um app para as duas plataformas. No App Center você tinha um app por plataforma. No Pipa no Ar o mesmo app recebe releases de iOS e de Android, e o deploy escolhe a plataforma com
-p. - O canal é da build, não da chave. No CodePush a deployment key decidia de onde o app buscava updates. Aqui o canal fica gravado na build nativa (padrão
production), e a API key só identifica o app. Para gerar uma build de outro canal, veja Canais e promote.
1. Crie o app e o token
Siga os passos 1 e 4 do guia rápido: crie o app no painel, anote o appId e crie um token de deploy (PIPANOAR_TOKEN). Crie também uma API key do app, que vai no código.
2. Tire o CodePush do projeto
Pacote
sh
npm uninstall react-native-code-pushJavaScript
Remova o import de react-native-code-push e tudo o que usa o codePush:
- o HOC
codePush(options)(App)em volta do componente raiz; - chamadas a
codePush.sync(),codePush.notifyAppReady(),codePush.getUpdateMetadata()e similares; - opções como
checkFrequency,installMode,mandatoryInstallModeedeploymentKey.
O componente raiz volta a ser exportado sem o HOC. No passo 4 ele ganha o HotUpdater.wrap.
iOS
No AppDelegate, o CodePush trocava o endereço do bundle de release:
swift
// AppDelegate.swift (antes)
import CodePush
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
CodePush.bundleURL()
#endif
}Em projetos com AppDelegate.mm, o equivalente é #import <CodePush/CodePush.h> e return [CodePush bundleURL];.
Apague o import do CodePush. A linha do bundleURL é trocada no passo 3.
No Info.plist, apague a chave CodePushDeploymentKey (e CodePushServerURL, se você usava um servidor próprio). Depois rode pod install na pasta ios.
Android
No MainApplication, o CodePush entregava o arquivo do bundle:
kotlin
// MainApplication.kt (antes)
import com.microsoft.codepush.react.CodePush
override fun getJSBundleFile(): String = CodePush.getJSBundleFile()Apague o import do CodePush. A linha do getJSBundleFile é trocada no passo 3.
Também saem:
- no
android/app/build.gradle, a linhaapply from: "../../node_modules/react-native-code-push/android/codepush.gradle"; - no
android/settings.gradle, as linhas doreact-native-code-push, se o projeto foi configurado com link manual; - no
strings.xml, a stringCodePushDeploymentKey(eCodePushServerUrl, se existir).
3. Instale o hot-updater e ligue o bundle nativo
Instale os pacotes como no guia rápido e crie o hot-updater.config.ts como no passo 3. O Pipa no Ar pede Hermes ligado.
No lugar onde estava o CodePush, o bundle de release passa a vir do hot-updater.
iOS, no AppDelegate.swift:
swift
import HotUpdater
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
HotUpdater.bundleURL()
#endif
}Em AppDelegate.mm: #import <HotUpdater/HotUpdater.h> e return [HotUpdater bundleURL];.
Android, no MainApplication.kt:
kotlin
import com.hotupdater.HotUpdater
override fun getJSBundleFile(): String = HotUpdater.getJSBundleFile(applicationContext)O lugar exato depende da versão do React Native do seu projeto. Para conferir, rode:
sh
npx hot-updater doctorO doctor avisa se o AppDelegate não usa HotUpdater.bundleURL() ou se o MainApplication não usa HotUpdater.getJSBundleFile().
Em projetos Expo com prebuild, essas mudanças nativas ficam a cargo do plugin @hot-updater/expo. Veja o guia do Expo.
4. Troque o codePush pelo HotUpdater.wrap
tsx
// Antes
import codePush from "react-native-code-push";
export default codePush({
checkFrequency: codePush.CheckFrequency.ON_APP_START,
installMode: codePush.InstallMode.ON_NEXT_RESTART,
})(App);tsx
// Depois
import { HotUpdater, insights } from "@hot-updater/react-native";
import { pipanoarClient } from "@pipanoar/hot-updater/client";
export default HotUpdater.wrap({
...pipanoarClient({
appId: "app_xxxxxxxxxxxxxxxxxxxx",
apiKey: "COLE_A_API_KEY_AQUI",
}),
updateStrategy: "appVersion",
plugins: [insights()],
})(App);O HotUpdater.wrap checa ao abrir o app, baixa o update e aplica no próximo início. Releases com atualização forçada recarregam o app assim que o download termina.
Checar em outros momentos
Se você usava checkFrequency: MANUAL e chamava codePush.sync() num botão ou ao voltar do segundo plano, use o fluxo manual. Ele troca o HotUpdater.wrap pelo HotUpdater.init: não use os dois juntos.
tsx
import { HotUpdater, insights } from "@hot-updater/react-native";
import { pipanoarClient } from "@pipanoar/hot-updater/client";
HotUpdater.init({
...pipanoarClient({
appId: "app_xxxxxxxxxxxxxxxxxxxx",
apiKey: "COLE_A_API_KEY_AQUI",
}),
plugins: [insights()],
});
export async function checarUpdate() {
const update = await HotUpdater.checkForUpdate({ updateStrategy: "appVersion" });
if (!update) return;
await update.updateBundle();
if (update.shouldForceUpdate) {
await HotUpdater.reload();
}
}
export default App;5. Escolha como casar versões
No CodePush, o --target-binary-version dizia para quais versões da loja a release valia. No Pipa no Ar isso depende do updateStrategy, que precisa ser o mesmo no hot-updater.config.ts e no app:
appVersionfunciona como o CodePush. Por padrão a release vale para a versão do projeto. Para outra faixa, passe-t:shnpx hot-updater deploy -p ios -t "1.4.x"fingerprintcompara a parte nativa da build. Se o nativo mudou, o update não chega na build antiga, sem você precisar lembrar de faixas de versão.
Detalhes em Releases e rollout.
6. Publique a build nova e depois os updates
- Gere a build de release com o hot-updater e publique nas lojas. Ela já leva o JavaScript atual, então não precisa de um update logo em seguida.
- Troque o
appcenter codepush release-reactdo seu CI pornpx hot-updater deploy. Veja Deploy pelo CI. - Acompanhe a adoção da build nova na App Store Connect e no Google Play Console. O Insights só mostra os aparelhos que já estão na build com o hot-updater, porque a build com o CodePush não manda eventos. Quem ainda estiver nela segue sem receber updates pelo ar.
Equivalências comuns no terminal:
sh
# Release para 20% dos aparelhos, no canal production
npx hot-updater deploy -p android -r 20
# Release forçada (antes: --mandatory)
npx hot-updater deploy -p ios -f
# Testar no beta e depois levar para production
npx hot-updater deploy -p ios -c beta
npx hot-updater bundle promote <id-da-release> -t production
# Voltar para a release anterior (antes: appcenter codepush rollback)
npx hot-updater bundle disable <id-da-release>Todas as opções em Comandos da CLI.
Comparar antes de decidir
Se você ainda está escolhendo para onde ir, veja Pipa no Ar e alternativas.