In-App Updates: Como gerenciar updates no Android

Lucas C. Wottrich
Android Dev BR
Published in
8 min readMay 12, 2021

--

Bem vindes, Lucas Wottrich aqui.

Esse é um artigo escrito em cima de uma solução feita dentro da @warren-tech, empresa onde trabalho atualmente, sugiro que sigam o medium para mais conteúdos voltados a tecnologia.

👶 Introdução

Hoje vamos falar sobre o In-App Updates, uma ferramenta disponibilizada pela Google para gerenciar updates dentro do aplicativo.

Quando você lê “Gerenciar updates dentro do aplicativo” pode parecer um pouco complicado, né? Mas felizmente só parece e na verdade não é!

A ferramenta de In-App Updates tem uma documentação com alguns furos de contexto, e algumas definições que deixam as responsabilidades na mão do desenvolvedor sem realmente explicar o que acontece por de baixo dos panos. Esse artigo vem para desmistificar esses pontos de uma forma simples e ajudar os devs, responsáveis por essa feature, a sair da inércia.

⚠️ Esse artigo é longo e provavelmente você só vai sentar para ler ele quando for tarde demais e já ter perdido horas de trabalho. O intuído não é carregar a verdade absoluta para o In-App Updates, mas tem vários pontos que podem ajudar.⚠️

👼 Por favor, considere deixar sua experiência com In-App Updates nos comentários desse artigo. 👼

👷 Entendendo a ferramenta

As vantagens de implementarmos essa ferramenta é mantermos o usuário sempre atento a novas atualizações e com isso podemos garantir que novos recursos estão sendo testados e diminuir os bugs em versões antigas do aplicativo.

O In-App Updates faz parte do pacote de projetos da biblioteca Google Play Core que é a mesma biblioteca responsável pela features In-App Review, Play Feature Delivery e Play Asset Delivery.

Para implementar essa biblioteca basta adiciona-la às dependências do seu projeto:

dependencies {
implementation 'com.google.android.play:core:1.10.0'
//Extensão para kotlin com soluções utilizando coroutines
implementation 'com.google.android.play:core-ktx:1.8.1'
//...
}

👑 Prioridades

Atualmente existem duas prioridades de atualização no In-App Updates que são separadas por:

  • FLEXIBLE
  • IMMEDIATE

Na biblioteca elas são definidas pelo AppUpdateType.

As prioridades teoricamente podem ser checadas pela função isUpdateTypeAllowed disponibilizado pela própria biblioteca, mas existem alguns problemas que envolvem essa função que serão explicados mais para frente nesse artigo.

Sem mais delongas, essas prioridades representam como devemos lidar com cada tipo de atualização e é muito importante entendermos o fluxo de cada uma antes de continuar.

🚏 FLEXIBLE

Uma atualização flexível pode ser definida quando precisamos mostrar uma atualização que pode ser ignorada, ou seja, o usuário não precisa dessa atualização imediatamente e ela pode ser feita posteriormente.

O fluxograma dessa prioridade pode ser desenhado como:

Fluxograma da prioridade FLEXIBLE

No último passo ainda podemos adicionar uma dialog que informa para o usuário que a atualização foi baixada e ele pode reiniciar o app para instalar ela.

⛔ IMMEDIATE

Uma atualização imediata acontece quando não podemos deixar o usuário continuar sem atualizar o aplicativo, ou seja, o usuário precisa dessa atualização e ela não pode ser feita posteriormente.

O fluxograma dessa prioridade pode ser desenhado como:

Fluxograma da prioridade IMMEDIATE

Nesse ultimo passo, no momento que o usuário escolhe não atualizar, podemos mostrar uma dialog informando que a atualização é obrigatória.

⛺ Verificando atualizações

Agora que já entendemos como funciona as prioridades podemos chamar o serviço responsável por nos informar sobre as atualizações. E isso é bem simples de se fazer:

Esses Resources no código fazem parte da nossa arquitetura de respostas, nós aqui na Warren utilizamos o padrão que foi definido pela Google que pode ser encontrado nesse link

Para trabalharmos com as informações mais facilmente resolvemos fazer uma class de Wrapper que cuida da forma que vamos receber os dados do serviço.

Para solicitar os dados nós precisamos criar uma instância de AppUpdateManagerFactory e logo após chamar o appUpdateInfo, que nesse caso seria uma Task<AppUpdateInfo>, onde temos que coletar a resposta no listener de sucesso(esse foi um dos motivos de termos encapsulado isso em uma suspendCancellableCoroutine).

Para não precisarmos ficar catando todos os dados do AppUpdateInfo nós criamos um AppUpdatePertinentInfo que carrega apenas as informações que precisamos no fluxo:

Agora que temos nosso wrapper pronto podemos utilizar ele para pegar o AppUpdatePertinentInfo:

✌️ Error and Success

Como vimos no snippet anterior, agora temos dois fluxos para resolver, o Error e o Success.

🔴 Error

Vamos começar pelo mais fácil. No Error nós precisamos informar o usuário que ocorreu um erro que não temos controle, pois esse erro vem direto do service do Google Play Core.

Para tomada de decisão você pode fazer duas coisas quando esse erro acontecer:

  • Informar o erro e trancar o usuário
  • Informar o erro e deixar o usuário continuar

🔵 Success

O Success será onde teremos as informações sobre atualizações do nosso aplicativo. É nesse momento que vão vir as informações necessárias para sabermos, quando existe uma atualização disponível, se ela:

  • Precisa ser notificada
  • Se essa atualização é FLEXIBLE
  • Se essa atualização é IMMEDIATE

Como você pode perceber agora nós temos três estados para resolver, com isso, criamos uma classe que represente esses estados:

Como os estados FLEXIBLE e IMMEDIATE são uma atualização que precisa ser notificada, colocamos eles juntos nessa representatividade apenas simplificando para um boolean isImmediate.

NoNeedUpdate: Representa que essa atualização não precisa ser notificada.

Nossos próximos passos ainda envolvem o fluxo de sucesso, então vamos continuar em outro tópico.

🚧 O problema das prioridades

Ainda dentro do fluxo de sucesso nós temos que verificar, como definido anteriormente, qual é o status da nossa atualização e aqui começa o problema das prioridades.

Até o momento nós só falamos e falamos e provavelmente deve ter passado essa pergunta na sua cabeça:

Onde definimos as prioridades IMMEDIATE e FLEXIBLE?

E a resposta é: Em lugar nenhum.

Nós não temos onde definir esses valores, não temos um console, não temos uma API, simplesmente não tem, não existe. Uma IssueTracker foi aberta para tentar buscar uma solução, mas até o dia que esse artigo está sendo publicado ainda não temos nada sobre isso.

🏞 De 0 à 5 | A solução

Como para todo o problema existe uma solução, esse problema que passamos não é diferente mas, de agora em diante, iremos passar por caminhos que precisam de uma tomada de decisão melhor elaborada pra prosseguir, por isso a solução dada a seguir foi a melhor pensada no cenário Warren, e pode ser diferente para outros lugares.

Nesse contexto nós tínhamos que arrumar um outro responsável pra as prioridades e então essa responsabilidade cai para o updatePriority().

🎫 updatePriority

updatePriority é uma função do AppUpdateInfo que nos retorna um Int, que segundo a documentação:

Para determinar a prioridade, o Google Play usa um valor inteiro entre 0 e 5, sendo 0 o padrão e 5 a prioridade mais alta.

Mas só pensar em um numero 0 à 5 não nos ajuda a definir prioridade, com base em uma decisão interna os valores ganham significado:

  • Baixa prioridade(0): Não é uma atualização flexível ou imediata.
    - Pequenas correções e melhorias
    - Bug triviais
  • Prioridade média(1 até 3): Solicita uma atualização flexível.
    - Bugs funcionais
  • Prioridade alta(4 e 5): Exige uma atualização imediata.
    - Bugs funcionais(em casos de maior prioridade)
    - Crashes

Passando isso para código temos:

const val NO_NOTIFY = 0
val FLEXIBLE = 1..3
val IMMEDIATE = 4..5

Aproveitando, podemos começar a fazer um contrato de definições. Essa classe responsável vai se chamar AppUpdateInfoDefinitions, e para começar podemos adicionar 3 definições para ela:

Classe abstrata das definições
Classe de implementação das definições

👮 Utilizando a classe de definições

Agora que temos o AppUpdateInfoDefinitions podemos tomar algumas decisões respeitando nossas regras pré-definidas.

Primeiro, para deixar o fluxo mais simples e não precisar validar todas essas definições sempre que implementarmos o In-App Update, vamos definir que não queremos mais receber um AppUpdatePertinentInfo e sim um InAppUpdateStatus. Essa classe carrega o que precisamos para fazer um fluxo funcionar.

Mas ainda precisamos aplicar as definições, e nesse momento que precisamos de uma classe de interação que vai servir para lidar com as definições e nos retornar um InAppUpdateStatus.

Abstração da classe de interação
Implementação da classe de interação

Trabalhando com abstrações e implementações conseguimos desacoplar responsabilidades mais facilmente e assim deixar o código mais fácil de testar e reutilizável. Nesse caso desacoplamos a responsabilidade de implementar as definições, deixando essa parte para o dev que for utilizar.

🏁 Quase na linha de chegada

Agora só falta juntarmos tudo isso e fazermos uma solicitação!

Para isso vamos adicionar um método dentro do AppUpdateInfoWrapper chamado startUpdateFlow que vai ser responsável por iniciar o fluxo de update:

Depois disso podemos fazer uma ViewModel que fica responsável por lidar com essas informações:

Mas vamos utilizar context na ViewModel?

Sim e não, vamos estar implementando todas as classes mostradas até agora utilizando injeção de dependência(Koin) e como vamos utilizar androidContext não precisamos se preocupar em referenciar contexto na ViewModel:

🚉 Ultima estação

Agora que temos nossa ViewModel que vai cuidar de tudo e nos devolver um status via LiveData, o que precisamos nos preocupar é com alguns retornos depois que o fluxo de update é iniciado.

Vamos fazer um check-in do que precisamos agora:

  • Implementar nossa ViewModel em uma activity
  • Chamar nosso método de verificação
  • Esperar o retorno do status
  • Controlar os resultados pelo onActivityResult

Quando iniciamos um fluxo nós chamamos a função startUpdateFlowForResult que recebe um requestCode onde o resultado será retornado no onActivityResult.

O que você quer fazer quando o resultado retornar também é uma decisão que pode variar, mas seguindo as definições que criamos, quando um usuário negar uma atualização imediata então não podemos deixar ele prosseguir.

A implementação da nossa activity, com todos os pontos que precisamos cobrir, é essa:

🍥 Conclusão

A maior parte das dificuldades nós já passamos juntos nesse artigo, isso você pode ficar tranquilo.😅

De agora em diante as coisas devem ficam um pouco menos complicadas pois envolvem, em grande parte, a ferramenta de In-App Updates.

Alguns pontos importantes que vamos levantar nos próximos artigos:

Deixei alguns link para caso os artigos futuros não cheguem a tempo de sanar suas duvidas :D

❓ Curiosidade

A nível de curiosidade, esse é o fluxo de trabalho do In-App Updates que temos hoje aqui dentro da warren:

🙏 Boa ação do artigo

“Tem gente com fome” é uma campanha nacional de arrecadação de fundos para ações emergências de enfrentamento à fome, à miséria e à violência na pandemia de COVID-19 em 2021.

Link: https://www.temgentecomfome.com.br/

🎉 Obrigado!!

Chegamos no fim desse artigo, muito obrigado por ter separado esse tempo do seu dia para ler :)

Se você gostou e acha que mereço o seu 👏(clap), por favor, sinta-se a vontade. ❤️

Tenha um bom resto de dia!
Até a próxima.

--

--

Lucas C. Wottrich
Android Dev BR

Android Developer at iFood. Descomplicando códigos e ajudando a comunidade a crescer com conteúdos pt-BR.