React Native + CodePush: Atualizações rápidas sem burocracia

Quando trabalhamos com desenvolvimento de aplicativos móveis com React Native, precisamos de tempos em tempos publicar atualizações nas lojas. Publicar uma nova versão pode ser um processo burocrático e demorado, exceto quando usamos alguma ferramenta de CD que simplifique essa atividade. Mas a verdade é que tem vezes que queremos apenas subir uma pequena correção de uma função ou de escrita de uma variável errada na camada JavaScript que está gerando erros de interface e precisamos passar por toda a burocracia da publicação na loja. Para esses casos o CodePush, da Microsoft, pode ajudar. Vamos entender o que é, como funciona e como adicionamos ele ao nosso aplicativo.


O que é CodePush?

É um serviço criado pela Microsoft que permite que aplicativos móveis (criados com React Native ou Cordova) possam ser atualizados sem que precise ser feita uma publicação na loja. Com o CodePush, conseguimos atualizar o bundle de nossa aplicação diretamente nos dispositivos dos usuários, de forma transparente e quase que instantânea.

Como funciona?

O CodePush atua como um repositório onde ficam armazenadas versões de nossos bundles que foram enviados, permitindo que possamos enviar novas versões de acordo com a necessidade. Já o aplicativo, através de um SDK adicionado através do NPM/Yarn, checa se há alguma nova versão do bundle. Quando encontrada uma versão, o CodePush baixa esse novo bundle e passa a usá-lo a partir do próximo início do aplicativo.

Photo by rawpixel.com on Unsplash

Um workflow básico de uma atualização com CodePush seria a seguinte. Disponibilizamos a v1 de nosso bundle. Nosso aplicativo foi publicado na loja e o usuário está com ele instalado. Mas foi identificado que um método em um componente está tentando acessar uma const que não existe, por que subiu com uma falha no nome da const. Ao invés de image foi declarada como imge.

Fazemos a correção no nome da const, roda-se os testes (por algum acaso não foi identificado lá, aceitem) e estando tudo nos conformes, fazemos uma nova publicação no CodePush. É gerada uma nova versão do bundle, a v2. A partir desse momento, quando algum usuário abrir o aplicativo, o CodePush vai identificar que tem uma nova versão do bundle, vai baixá-lo e dar um reload rápido na aplicação para que o novo bundle seja utilizado.

Pronto! A correção foi disponibilizada, não foi necessário enviar novos binários para as lojas e nem sofrer com o processo de avaliação e liberação. Mas então… nunca mais será necessário fazer uma publicação na loja? Infelizmente não, há casos que o CodePush não pode nos ajudar.

Imagine que você adiciona um novo package no seu aplicativo. E esse package necessita que seja feito um react-native link [nome-do-package]. Como devem saber, o comando react-native link faz configurações na camada nativa de sua aplicação. Essas configurações são geralmente inclusão de Frameworks e classes que são incorporadas ao aplicativo e usadas em determinadas situações. Como o CodePush atualiza somente o bundle, ou seja, o arquivo gerado pelo Metro (o packager do React Native) que contém todo nosso código JavaScript, atualizações da camada nativa, só podem ser propagadas gerando um novo binário (ipa para iOS e apk pra Android). Nesse caso, é necessário gerar uma nova versão e enviar para as lojas junto com uma nova versão no CodePush.

Agora vem uma questão interessante: Como o CodePush sabe que houve uma mudança na camada nativa da aplicação e não atualiza o bundle.js?
Para isso ele inteligentemente avalia a versão atual do seu aplicativo através do semver.

Quando enviamos uma nova versão para o CodePush, declaramos qual a versão do binário que a nova release se aplica. Achou complicado? Vejamos um exemplo:

Você lançou seu NovoApp na versão 1.0.0 e o seu bundle no CodePush está na v1. Se você disponibilizar um novo bundle para a mesma versão 1.0.0 do seu aplicativo, o CodePush entende que não houve alteração na camada nativa de seu aplicativo e que ele pode baixar o bundle pois se trata apenas de uma correção de código JavaScript. Caso você lance uma nova versão do seu app, por exemplo 1.1.0, e faça um novo release no CodePush, apenas aqueles que estiverem na mesma versão receberão atualizações do bundle para aquela versão.

Isso torna mais difícil que um bundle seja atualizado para uma versão que ainda não tem alguma dependência nova que foi adicionada recentemente. Mas claro que se você publicar um bundle para uma versão nova especificando uma versão antiga, vai dar merda!

Para saber como o CodePush lida com a questão da versão do seu aplicativo, veja essa parte da documentação.

Como integro o CodePush em meu projeto?

Vou assumir que você já passou pelo Getting Started Guide e que seu ambiente de desenvolvimento já está devidamente configurado.

Quando o CodePush foi lançado, ele fazia parte do Visual Studio Mobile Center. Mas percebendo que a comunidade se interessou pelo serviço, e também para agregar novas funcionalidades, a Microsoft mudou para Visual Studio App Center, se tornando uma solução integrada para gerenciar, testar, automatizar tarefas e publicar aplicativos. Dê uma olhada no serviço e na documentação.

Pois bem… temos atualmente 2 packages disponíveis no NPM para usarmos o CodePush: code-push-cli e appcenter-cli. O code-push-cli é o mais usado atualmente, mas ele só terá suporte até outubro/2018, como é possível ver na imagem abaixo retirada do site do App Center.

Com isso avisado, seguiremos todos os procedimentos usando o novo cli do App Center. Para instalar basta digitarmos no terminal:

npm i -g appcenter-cli

Para podermos utilizar o CodePush, é necessário ter uma conta no App Center. Clique aqui para criar sua conta gratuita e vamos seguir com a configuração.

Após ter criado a sua conta, será necessário fazer login no appcenter através do terminal:

appcenter login

Uma nova guia será aberta no seu browser com um token. Basta copiar e colar esse token no terminal para concluir o processo de autenticação. Agora é necessário criar um app no App Center. Isso pode ser feito tanto pelo Dashboard como pelo terminal. Mas nós vamos de terminal:

appcenter apps create [options]

As opções aceitas para um aplicativo são:

-d|--display-name <arg>     O nome do app. Valor pelo qual será referenciado o app no App Center.
-p|--platform <arg>         A plataforma em que o app foi feito. Valores suportados: Java, Objective-C-Swift, React-Native, UWP, Xamarin.
-o|--os <arg>               O sistema operacional em que o app estará rodando. Valores suportados: Android, iOS, Windows.

-n|--name <arg> O nome do aplicativo que será usado internamente nas urls pelo App Center. É opcional, e caso não informado será usado o valor fornecido em display-name.
--description <arg>         Descrição do aplicativo. É opcional.

Vamos adicionar nosso app então:

appcenter apps create -d MeuApp -o iOS -p React-Native
Obs.: Com a migração para o App Center, o CodePush está temporariamente restrito ao React Native, mas em breve deve voltar a ser fornecido para aplicativos Cordova.

Com nosso aplicativo criado, precisamos agora criar deployments para nosso apps. Deployments nada mais são do que as variantes que um app pode ter, ex.: debug, staging, test, production etc. O recomendado é ter pelo menos 2 deployments: Staging e Production. Mas isso varia para cada aplicativo e processo de desenvolvimento. Por hora, vamos apenas nos focar na versão de produção.
Para adicionar um novo deployment:

appcenter codepush deployment add -a <ownerName>/<appName> Production

Uma dica: esse parâmetro -a é para identificar o aplicativo e é necessário fornecer para todos os comandos realizados com o cli do appcenter. Mas é possível omitir esse parâmetro através de:

appcenter apps set-current <ownerName>/<appName>

E com isso, todos os comandos utilizados no appcenter serão aplicados nesse aplicativo. No meu caso seria algo tipo appcenter apps set-current lucianomlima/MeuApp. Para ver qual é o aplicativo atual configurado basta digitar appcenter apps get-current.

Então, considerando que você colocou seu app como padrão através do set-current o comando que você utilizaria seria apenas:

appcenter codepush deployment add Production

Para ver todos os deployments cadastrados:

appcenter codepush deployment list
// ou caso não tenha setado o app padrão
appcenter codepush deployment -a <ownerName>/<appName>

Pronto… agora já temos nosso aplicativo criado, deployments criados, precisamos incluir o SDK no nosso app:

yarn add react-native-code-push
// ou caso use o npm
npm i --save react-native-code-push
É necessário salientar que o CodePush é voltado para aplicativos criados com o comando react-native init. Caso tenha criado um aplicativo com o create-react-native-app será necessário ejetar o projeto com yarn run eject para poder continuar com a configuração. Para aplicativos feitos com Expo, vejam este link.
Fique atento também às versões suportadas do React Native. Dependendo da versão, você deve instalar uma versão específica do CodePush. Para descobrir qual, veja a documentação.

Agora precisamos fazer a configuração na camada nativa. A forma mais fácil é com react-native link react-native-code-push. Caso queria fazer de forma manual ou deseje usar o CocoaPods para o iOS, segue a documentação.

Antes de seguirmos para a próxima parte, vamos pegar o deployment key. Para isso, digite no terminal appcenter codepush deployment list e anote a key para seu app. No cli anterior do CodePush era necessário o seguinte comando code-push deployment ls <ownerName>/<appName> -k para poder ter acesso à key do deployment.

Para cada plataforma, é necessário fazer algumas modificações extras. Todas essas etapas estão bem esclarescidas na documentação, mas aqui vai um resumo para Android e iOS:

Android

Para fins de otimização de aprendizado, vamos focar apenas nas versões mais recentes do React Native para Android. Caso queira saber o procedimento para versões anteriores à 0.29 veja a documentação.

No arquivo MainApplication.java localizado em android/app/src/main/java/[your/package/namespace] faça as seguintes mudanças:

...
// 1. Importe o package.
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
// 2. Sobrescreva o método getJSBundleFile para que o
// CodePush saiba onde obter a localização do bundle JS
// em cada inicialização do aplicativo
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
        @Override
protected List<ReactPackage> getPackages() {
// 3. Na instância do CodePush informe o seu deployment
// key copiado anteriormente
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new CodePush("deployment-key-here", MainApplication.this, BuildConfig.DEBUG)
);
}
};
}

iOS

  1. Abra o arquivo AppDelegate.m e adicione #import <CodePush/CodePush.h> no início do arquivo.
  2. Encontre a linha onde está jsCodeLocation = [[NSBundle ... e substitua por:
NSURL *jsCodeLocation;
#ifdef DEBUG
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#else
jsCodeLocation = [CodePush bundleURL];
#endif

3. No seu arquivo Info.plist você vai adicionar uma nova chave chamada CodePushDeploymentKey e como valor deverá passar a chave copiada um tempo atrás.

Para uma configuração mais complexa envolvendo múltiplos deployments, sugiro ver a documentação. =)

Por fim, basta apenas agora configurar o CodePush na camada JavaScript do seu aplicativo. Basta fazer a seguinte alteração:

// Importe o package do CodePush
import codePush from 'react-native-code-push';
class MyApp extends Component {
}
// Exporte o aplicativo envolvendo-o com o HOC
export default codePush(MyApp);

Por padrão o CodePush verifica a existência de atualização toda vez que o app é iniciado. Ao identificar uma nova versão, o bundle é baixado e somente na próxima inicialização do app, o mesmo será usado. Caso queira uma verificação mais rápida, pode adicionar a seguinte configuração:

// Importe o package do CodePush
import codePush from 'react-native-code-push';
const codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME };
class MyApp extends Component {
}
// Exporte o aplicativo envolvendo-o com o HOC
export default codePush(codePushOptions)(MyApp);

Isso fará com que a verificação de novo bundle seja feito em todo RESUME do app (que é diferente de inicialização do app). E finalmente temos nosso aplicativo configurado com CodePush! \o/

Como faço um release no CodePush?

Agora vem a parte de gerenciamento de releases. Relaxe que vai ser bem curtinha. Para fazer um novo release você precisa:

  1. Gerar um bundle js
  2. Disponibilizar para seus usuários

Agora você está pensando… nossa, vou precisar gerar o bundle manualmente toda vez que precisar mandar uma nova release para o CodePush?

Com apenas 1 comando o CodePush irá gerar o bundle e logo em seguida subir a nova versão para o serviço. Vamos destrinchá-lo:

appcenter codepush release-react -t "1.0.0"
// ou caso você não tenha configurado o aplicativo padrão
appcenter codepush release-react -a <ownerName>/<appName> -t "1.0.0"

Sim, só isso. Apenas com uma linha o bundle será gerado e enviado para os servidores da Microsoft. Mas caso você queira saber, seria o mesmo que fizéssemos isso:

mkdir ./CodePush
react-native bundle --platform ios \
--entry-file index.js \
--bundle-output ./CodePush/main.jsbundle \
--assets-dest ./CodePush \
--dev false
appcenter codepush release -a <ownerName>/<appName> -c ./CodePush -t "1.0.0"

O que ocorre no código acima é que:

  1. É criado um diretório CodePush
  2. O bundle gerado é salvo nesse diretório
  3. É passado através do parâmetro -c a localização do bundle para o comando release que é usado para enviar uma nova versão.

Percebeu o parâmetro -t sendo passado tanto para o comando release-react quanto para o release? Esse é o targetBinaryVersion e é com ele que você delimita quais versões devem receber esse novo bundle. Se por acaso fosse passado um *, significaria que todas as versões receberiam o novo bundle. Mais informações sobre esse e outros parâmetros você encontra na documentação.


Enfim, o artigo foi longo mas espero que tenha sido esclarescedor e que possa ajudar vocês a gerenciar melhor os aplicativos feitos com React Native. Na documentação é possível aprender a modificar uma release, fazer rollback, estabelecer uma release como mandatária (obrigatória) e até fazer distribuição controlada (liberar apenas para 25% dos usuários).

Nos vemos no próximo artigo. See ya o/