Primeiras aulas do curso Android com Web Service parte 3: sincronização em modo offline

Android com Web Service parte 3: sincronização em modo offline

Introdução ao versionamento - Introdução

Olá, tudo bem? Vamos começar o curso de Android com Web Service Parte 3. Esse curso faz parte de uma sequência de cursos onde temos a Parte 1, e a Parte 2, sendo altamente recomendável que você as faça, já que o projeto é uma continuação.

Em nosso projeto, temos dados de alunos cadastrados em nosso servidor. Quando utilizamos a app para pegar os dados dos alunos, o servidor manda todas as informações para nós.

imagem alunos servidor

O problema é que quando temos uma atualização no servidor, como a inserção de uma novo aluno, em vez de recebermos apenas o novo dado, a nossa app pede todas as informações para o servidor, mesmo que já tenha.

imagem alunos com atulização servidor

A abordagem ideal seria que a nossa app pegasse apenas os dados que ainda não possui. Seria um versionamento de dados.

A app, enquanto está online as operações de inserir, alterar, remover funcionam normalmente. Mas, e se a aplicação estiver offline? As alterações nos dados não são replicados no servidor, apenas localmente.

Veremos uma forma de sincronizar e atualizar os dados com o servidor no momento que conseguirmos recuperar a conexão.

Ainda tem um detalhe, o que acontecerá quando as alterações forem feitas no servidor e na app? Isso é o que chamamos de Merge da informações. Veremos se existe uma prioridade na app Android ou no servidor, ou se devemos fazer algo personalizável, que nos permita escolher a versão que iremos manter.

Atualmente usamos um serviço chamado Firebase Cloud Messaging para fazer com que as requisições feitas nos servidor serem replicadas na app sem a necessidade do cliente precisar solicitar. Precisamos padronizar o serviços para que as informações que criamos e alteramos enquanto estávamos offline não sejam sobrescritas.

A proposta do curso é ter uma app que funcione online e offline. Além de aprender sobre os atalhos padrões do Android Studio.

Vamos começar com o projeto?

Introdução ao versionamento - Introdução ao versionamento

Introdução ao versionamento

Até o momento, a nossa aplicação está funcionado muito bem. Mas tem um detalhe que é importante abordarmos. Quando a aplicação é instalada no celular, a app solicita ao servidor as informações de todos os alunos.

puxando dados pela primeira vez

O que acontece se no servidor adicionarmos um novo aluno? A aplicação solicitará todos os alunos novamente.

puxando dados após a atulização de um dados

É um processo que não faz muito sentido considerando que apenas uma única informação foi alterada no servidor. O ideal seria que o aplicativo solicitasse apenas a informação que ele não possui.

puxando apenas a informação necessária

Como poderíamos implementar essa funcionalidade em nossa aplicação? Para fazermos isso, trabalharemos com um conceito chamado de Versionamento de Dados.

Vamos imaginar novamente o cenário onde a aplicação acabou de ser instalada e ainda não pegou nenhuma informação do servidor.

No servidor, as informações armazenadas vão possuir, por exemplo, um número de versão, que no caso seria 1. Quando o celular solicitar os dados, o servidor vai mandar a versão 1 para o celular. Se acontecer a inserção de um novo aluno, o servidor colocará nessa nova informação a versão 2, assim o celular precisará apenas puxar a versão que ele ainda não possui.

pegando informações com versões

Mas vamos imaginar que no celular as informações estão na versão 1, e o no servidor foram adicionados novos alunos, passando a ter as versões 2, 3 e 4, como o aplicativo vai pegar essas informações? Ele fará a solicitação informando que possui a versão 1, e o servidor vai responder com a versão mais recente enviando todas as outras informações que ele ainda não possui.

pegando versão atalizada com o servidor

Agora que aprendemos como funciona o versionamento de dados vamos começar a implementar de fato em nossa aplicação. Mas antes vamos fazer uma refatoração.

Na classe ListaAlunosActivity, acessaremos o método buscaAlunos(), no qual temos o código que pega os alunos do servidor a traz para a nossa aplicação. Só que esse tipo de comportamento não é responsabilidade da Activity, então, vamos colocar o código em um lugar que realmente seja o responsável por isso.

Extrairemos todo método buscaAlunos(), esse tipo de processo é chamado de Delegate, pois delegaremos todo o comportamento de um método para outra classe. Usando o atalho "Shift + Ctrl + Alt + T", um menu será aberto onde escolheremos a opção 3 - Delegate

menu com a opção 3 - delegate

Após selecionar a opção delegate, cairemos em uma nova janela, onde colocamos algumas informações para a refatoração do método.

O primeiro passo é colocar no campo "Name for new class", o nome da classe para onde vamos refatorar o método, no caso, chamaremos de AlunoSincronizador.

colocando nome da classe

No campo Package name, nós colocaremos o nome do pacote onde essa nova classe será criada. Por padrão, ela vem como o diretório raiz, mas iremos colocar o pacote como br.com.alura.agenda.sinc.

colocando o nome do pacote

Agora na área Members to Extract, nós selecionaremos o método que desejamos refatorar. No caso, trabalharemos apenas o método buscaAlunos():void e depois, clicaremos em "Refactor".

selecionado o método para a refatoração

Um campo surgirá informando que o campo swipe está sendo acessado dentro do método, ou seja, por conta da nova classe não ter o swipe, precisaremos lidar com isso. Mas no momento, vamos cliclar em "Continue" para finalizar a refatoração.

Veja que agora todo o comportamento do buscaAlunos() foi substituído por um objeto alunoSincronizador do tipo AlunoSincronizador que chama o método buscaAlunos().

Vamos refatorar um pouco mais trocando o nome do objeto alunoSincronizador. Colocamos o cursor do editor em cima do objeto alunoSincronizador e usaremos o atalho "Shift + F6", agora é só renomear o objeto para sincronizador.

Nosso próximo passo será analisar o método buscaAlunos() na classe AlunoSincronizador. Veja que ele colocou todo o comportamento do método na nova classe. Mas ainda precisaremos trabalhar em alguns detalhes dessa classe.

O primeiro detalhe é o acoplamento que o Delegate fez no construtor da AlunoSincronizador, desta forma obriga que a classe AlunoSincronizador só funcione com a classe ListaAlunoActivity. Porém queremos que ela seja chamada por outras classes que necessitem de suas funcionalidades.

// ...

private final ListaAlunosActivity listaAlunosActivity;

public AlunoSincronizador(ListaAlunosActivity listaAlunosActivity){
    this.listaAlunosActivity = listaAlunosActivity;
}

// ...

Precisamos generalizar a classe que recebemos no construtor. A forma mais genérica é usar a Context. Vamos alterar o tipo ListaAlunoActivity que recebemos no construtor para o tipo Context e mudamos o nome da variável utilizando o atalho "Alt + F6" para context. Também alteramos o atributo mudando o tipo para Context e o nome com "Alt + F6" para context. Quando utilizamos o atalho "Alt + F6", nós refatoramos a variável, em todos os pontos em que ela foi utilizada na classe.

// ...

private final Context context;

public AlunoSincronizador(Context context){
    this.context = context;
}

// ...

Como o nosso atributo é um Context, o código quebrou em alguns métodos que eram chamados a partir do antigo objeto listaAlunosActivity.

Vamos começar resolvendo o context.carregaLista(). Para resolvermos esse problema sem criar um acoplamento com a classe ListaAlunoActivity, podemos fazer uso do EventBus. Começaremos pegando uma referência do EventBus.

// ...

private final Context context;
private EventBus bus = EventBus.getDefault();

// ...

Com o nosso objeto bus, vamos disparar um evento pedindo para atualizar a lista. Então usamos bus.post(new AtualizaListaAlunoEvent());. Nesse momento, context.carregaLista() não será mais necessário, podendo ser apagado.

// ...

public void buscaAlunos(){
    Call<AlunoSync> call = new RetrofitInicializador().getAlunoService().lista();

    call.enqueue(new Callback<AlunoSync>(){
        @Override
        public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
            AlunoSync alunoSync = response.body();
            AlunoDAO dao = new AlunoDAO(context);
            dao.sincroniza(alunoSync.getAlunos());
            dao.close();
            bus.post(new AtualizaListaAlunoEvent());
            context.getSwipe().setRefreshing(false);
        }


        @Override
        public void onFailureCall(Call<AlunoSync> call, Throwable t){
            Log.e("onFailure chamado", t.getMessage());
            context.getSwipe().setRefreshing(false);
        }

    });
}

// ...

O código ainda está quebrado na chamada do método context.getSwipe().setRefreshing(false);. Nós estavamos usando o Swipe para tirar o refreshing do aplicativo.

Mas como vamos fazer para continuar retirando o esse refreshing? No momento em que carregamos a lista, podemos fazer uma verificação dentro do evento que atualiza a lista.

Na classe ListaAlunosActivity, podemos ir no método atualizaListaAlunoEvent() e fazer uma verificação do tipo if(swipe.isRefreshing()) swipe.setRefreshing(false);.

// ...

@Subscribe(threadMode = ThreadMode.MAIN)
public void atualizaListaAlunoEvent(AtualizaListaAlunoEvent event){
    if(swipe.isRefreshing()) swipe.setRefreshing(false);
    carregaLista();
}

// ...

Como o nosso próprio evento que atualiza a lista está cuidando do swipe, podemos removê-lo do método onResponse() na classe AlunoSincronizador.

// ...

@Override
public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
    AlunoSync alunoSync = response.body();
    AlunoDAO dao = new AlunoDAO(context);
    dao.sincroniza(alunoSync.getAlunos());
    dao.close();
    bus.post(new AtualizaListaAlunoEvent());
}

// ...

Agora no método onFailure(), para corrigir o problema do Log, basta importá-lo. E como já não precisamos mais usar o swipe, podemos remover a linha context.getSwipe().setRefreshing(false);, e depois, colocaremos bus.post(new AtualizaListaAlunoEvent());.

// ...

public void buscaAlunos(){
    Call<AlunoSync> call = new RetrofitInicializador().getAlunoService().lista();

    call.enqueue(new Callback<AlunoSync>(){
        @Override
        public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
            AlunoSync alunoSync = response.body();
            AlunoDAO dao = new AlunoDAO(context);
            dao.sincroniza(alunoSync.getAlunos());
            dao.close();
            bus.post(new AtualizaListaAlunoEvent());
        }


        @Override
        public void onFailureCall(Call<AlunoSync> call, Throwable t){
            Log.e("onFailure chamado", t.getMessage());
            bus.post(new AtualizaListaAlunoEvent());
        }

    });
}

// ...

Agora que terminamos a refatoração, vamos rodar a aplicação e ver se está tudo funcionando. Deixamos o Android Monitor aberto para monitorar e abrir o aplicativo, veremos os alunos foram chamados normalmente. Se tentarmos fazer um swipe, constataremos que também está funcionando.

Introdução ao versionamento - Salvando versão com o shared preferences

Salvando a versão com Shared Preferences

Agora que conseguimos fazer a nossa refatoração, precisamos pegar os dados do servidor que representa a versão dos nossos dados e persista na aplicação.

Mas qual é a informação que temos no servidor que representa essa versão dos nossos dados? Se olharmos o Log do Android Monitor, podemos ver que temos uma propriedade chamada "momentoDaUltimaModificacao". É justamente esse campo que vamos salvar na aplicação.

Como podemos pegar essa informação? Nos cursos anteriores, nós vimos que o AlunoSync é um DTO que representa todo o corpo da resposta que vem do servidor.

No método onResponse() da classe AlunoSincronizador chamamos a o método alunoSync.getMomentoDaUltimaVerificação(); e guardamos em uma variável.

// ...

@Override
public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
    AlunoSync alunoSync = response.body();
    String versão = alunoSync.getMomentoDaUltimaVerificação();

    AlunoDAO dao = new AlunoDAO(context);
    dao.sincroniza(alunoSync.getAlunos());
    dao.close();
    bus.post(new AtualizaListaAlunoEvent());
    context.getSwipe().setRefreshing(false);
}

// ...

Já temos a nossa versão. Agora poderemos persistir a informação, mas qual seria a forma mais adequada? Estamos persistindo os nossos dados com o SQLite, mas não faz sentido criarmos uma tabela para guardar apenas uma informação, SQLite é mais para entidades.

Felizmente o Android tem diversas opções de armazenamento, as Storage Options. E a opção chamada de Shared Preferences se aplica a nossa situação de armazenar um dado primitivo. O Shared Preferences é um framework feito para armazenar os tipos primitivos.

O Shared Preferences não é o tipo de responsabilidade da classe AlunoSincronizador, já que ela só tem responsabilidade de mandar e buscar informações no servidor, ou fazer alguma comunicação externa. Então vamos criar uma classe resposavel pelo Shared Preferences.

Vamos criar então uma classe chamada AlunoPreferences, onde a sua função é guardar informações de Shared Preferences para aluno. Então vamos instanciar a classe e chamar a ação que queremos com new AlunoPreferences().salvaVersao(versao);.

// ...

@Override
public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
    AlunoSync alunoSync = response.body();
    String versão = alunoSync.getMomentoDaUltimaVerificação();

    new AlunoPreferences().salvaVersao(versao);

    AlunoDAO dao = new AlunoDAO(context);
    dao.sincroniza(alunoSync.getAlunos());
    dao.close();
    bus.post(new AtualizaListaAlunoEvent());
    context.getSwipe().setRefreshing(false);
}

// ...

Como ainda não criamos a classe, podemos usar os recursos da IDE para criá-la. Colocamos o cursor do editor em cima da instancia AlunoPreferences e usamos o atalho "Alt + Enter". Na janela aberta colocaremos o pacote como br.com.alura.agenda.preferences e apertamos "OK".

nomeando pacote na criação da classe com alt + enter

Criamos a nossa nova classe, porém sem o método salvaVersao(). Voltamos a classe AlunoSincronizador e colocamos o cursor do editor em cima do método e usamos novamente o "Alt + Enter", selecionamos a opção Create method 'salvaVersao' e teremos o método criado na classe AlunoPreferences.

create method

Para acessarmos o Shared Preferences na nova classe, precisamo de uma referência do Context, então vamos passar no construtor a variável context.

// ...

@Override
public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
    AlunoSync alunoSync = response.body();
    String versão = alunoSync.getMomentoDaUltimaVerificação();

    new AlunoPreferences(context).salvaVersao(versao);

    AlunoDAO dao = new AlunoDAO(context);
    dao.sincroniza(alunoSync.getAlunos());
    dao.close();
    bus.post(new AtualizaListaAlunoEvent());
    context.getSwipe().setRefreshing(false);
}

// ...

Como não temos nenhum construtor criado na classe AlunoPreferences, então basta colocar o cursor do editor em cima da variável context que estamos passando no construtor e usar o "Alt + Enter".

create constructor

Dentro da classe AlunoPreferences colocamos o cursor em cima do parâmetro context do construtor e usamos mais uma vez o atalho "Alt + Enter", selecionando a opção Create field for parameter 'context'.

create field for parameter context

Pronto, já temos a classe, o construtor, o atributo e o método criados. Nossa classe no momento está assim:

package br.com.alura.agenda.preferences

import android.content.Context;

public class AlunoPreferences{

    private Context context;

    public AlunoPreferences(Context context){
        this.context = context;
    }

    public void salvaVersao(String versao){

    }
}

Dentro do método salvaVersao(), vamos chamar o Shared Preferences usando o context.getSharedPreferences();. Mas não é tão simples assim, precisamos ajustar alguns detalhes.

Precisamos passar como argumento uma string que vai ser o nome do conjunto do Shared Preferences que queremos acessar, no caso como é apenas a classe AlunoPreferences que vai acessar esse conjunto, então passaremos como chave "br.com.alura.agenda.preferences.AlunoPreferences".

Qual o motivo de passar o contexto do pacote e o nome da classe como chave? Caso alguma outra classe ou biblioteca que faz uso do Shared Preferences, isso evita que elas acessem o nosso conjunto, já que ele vai estar usando outro tipo de chave.

Agora precisamos colocar o módulo, que também é acessado por meio do context também. Então vamos passar como segundo argumento o context.MODE_PRIVATE. Escolhemos privado para apenas a nossa aplicação tenha acesso.

Nossa classe no momento:

package br.com.alura.agenda.preferences

import android.content.Context;

public class AlunoPreferences{

    private Context context;

    public AlunoPreferences(Context context){
        this.context = context;
    }

    public void salvaVersao(String versao){
        context.getSharedPreferences("br.com.alura.agenda.preferences.AlunoPreferences", context.MODE_PRIVATE);
    }
}

Repare que a nossa string é grande e prejudica na leitura do método. Como ela vai ser um valor constante, vamos extraí-la com o atalho "Ctrl + Alt + C". A IDE cria uma constante public static final, e para melhorar a legibilidade, vamos alterar o nome para ALUNO_PREFERENCES. Como não vamos acessá-la de outras classes, vamos mudar o modificador de acesso para private.

package br.com.alura.agenda.preferences

import android.content.Context;

public class AlunoPreferences{

    private static final String ALUNO_PREFERENCES = "br.com.alura.agenda.preferences.AlunoPreferences";
    private Context context;

    public AlunoPreferences(Context context){
        this.context = context;
    }

    public void salvaVersao(String versao){
        context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
    }
}

Vamos associar o objeto retornado pelo método getSharedPreferences() em uma variável do tipo SharedPreferences chamada de preferences.

// ...

public void salvaVersao(String versao){
    SharedPreferences preferences = context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
}

// ...

E a partir do variável preferences chamamos o método preferences.edit();. Esse método retorna um objeto do tipo SharedPreferences.Editor que guardaremos em um variável chamada editor. Esse objeto retornado é o próprio editor do Shared Preferences.

// ...

public void salvaVersao(String versao){
    SharedPreferences preferences = context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
    SharedPreferences.Editor editor = preferences.edit();
}

// ...

Com o editor em mãos, podemos enviar para o Shared Preferences a informação que queremos. Existe diversas opções para armazenar tipos primitivos, mas como queremos armazenar uma string, então vamos utilizar o editor.putString() passando dois argumentos, uma chave e um valor. Como chave, utilizaremos a string "versao_do_dado" e o valor é a própria variável versao.

// ...

public void salvaVersao(String versao){
    SharedPreferences preferences = context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
    SharedPreferences.Editor editor = preferences.edit();
    editor.putString("versão_do_dado", versao);
}

// ...

Mais uma vez, vamos extrair a string "versao_do_dado" para uma constante usando o atalho "Ctrl + Alt + C", e colocar a nova constante como private.

Ainda não é o suficiente para persistir a informação, precisamos de mais um detalhe que é chamar o método editor.commit();.

package br.com.alura.agenda.preferences

import android.content.Context;

public class AlunoPreferences{

    private static final String ALUNO_PREFERENCES = "br.com.alura.agenda.preferences.AlunoPreferences";
    private static final VERSAO_DO_DADO = "versão_do_dado";
    private Context context;

    public AlunoPreferences(Context context){
        this.context = context;
    }

    public void salvaVersao(String versao){
        SharedPreferences preferences = context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("versão_do_dado", versao);
        editor.commit();
    }
}

Agora conseguimos persistir nossas informações. Mas em nenhum momento de nossa aplicação, estamos conseguindo ter certeza de que os dados estão realmente sendo salvos.

Vamos verificar qual é o valor que está sendo salvo. Na classe AlunoSincronizador dentro do método onResponse() vamos colocar um Log.i(). Antes precisamos isolar a referência do AlunoPreferences em uma variável.

// ...

@Override
public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
    AlunoSync alunoSync = response.body();
    String versão = alunoSync.getMomentoDaUltimaVerificação();

    AlunoPreferences preferences = new AlunoPreferences(context);
    preferences.salvaVersao(versao);

    AlunoDAO dao = new AlunoDAO(context);
    dao.sincroniza(alunoSync.getAlunos());
    dao.close();
    bus.post(new AtualizaListaAlunoEvent());
}

// ...

Agora usaremos o Log.i("versao", preferences.getVersao());.

// ...

@Override
public void onResponse(Call<AlunoSync> call, Response<AlunoSync> response){
    AlunoSync alunoSync = response.body();
    String versão = alunoSync.getMomentoDaUltimaVerificação();

    AlunoPreferences preferences = new AlunoPreferences(context);
    preferences.salvaVersao(versao);

    AlunoDAO dao = new AlunoDAO(context);
    dao.sincroniza(alunoSync.getAlunos());
    dao.close();

    Log.i("versao", preferences.getVersao());

    bus.post(new AtualizaListaAlunoEvent());
}

// ...

Mas na classe AlunoPreferences não existe esse método, então colocamos o cursor em cima da chamada do método e usamos o atalho "Alt + Enter" escolhemos a Create method 'getVersao'. Pronto o método está criado.

Para retornar a versão persistida pelo Shared Preferences precisamos antes ter acesso a ao que persistiu a versão do aluno. No caso vamos ter que repetir o SharedPreferences preferences = context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);. Repetir código não é legal, podemos extrair para um método com "Ctrl + Alt + N" e na janela aberta é só clicar em "OK".


// ...

private SharedPreferences getSharedPreferences(){
    return context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
}

public String getVersao(){
    return null;
}

No método getVersao() vamos chamar o método getSharedPreferences() armazenando em uma variável chamada preferences, e a partir dela, chamamos o método preferences.getString() passando a chave VERSAO_DO_DADO e um segundo argumento para caso a operação não de certo, passaremos uma string vazia.


// ...

private SharedPreferences getSharedPreferences(){
    return context.getSharedPreferences(ALUNO_PREFERENCES, context.MODE_PRIVATE);
}

public String getVersao(){
    SharedPreferences preferences = getSharedPrederences();
    return preferences.getString(VERSAO_DO_DADO, "");
}

Agora podemos executar a nossa aplicação e verificar se tudo está funcionando. Se analisarmos o Android Monitor veremos que a aplicação sempre está buscando a versão mais recente.

Sobre o curso Android com Web Service parte 3: sincronização em modo offline

O curso Android com Web Service parte 3: sincronização em modo offline possui 150 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de Android em Mobile, ou leia nossos artigos de Mobile.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Android acessando integralmente esse e outros cursos, comece hoje!

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

Premium

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$75
à vista R$900
Matricule-se

Premium Plus

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$100
à vista R$1.200
Matricule-se

Max

  • 1266 cursos

    Cursos de programação, UX, agilidade, data science, transformação digital, mobile, front-end, marketing e infra.

  • Certificado de participação

    Certificado de que assistiu o curso e finalizou as atividades

  • App para Android e iPhone/iPad

    Estude até mesmo offline através das nossas apps Android e iOS em smartphones e tablets

  • Acesso à Alura Start

    Cursos de introdução a tecnologia através de games, apps e ciência

  • Acesso à Alura Língua

    Reforço online de inglês e espanhol para aprimorar seu conhecimento

12X
R$120
à vista R$1.440
Matricule-se
Conheça os Planos para Empresas

Acesso por 1 ano

Estude 24h/dia onde e quando quiser

Novos cursos todas as semanas