Primeiras aulas do curso Android Room parte 2: implementando operações assíncronas

Android Room parte 2: implementando operações assíncronas

Suportando mais de um telefone - Introdução

Sou o Alex Felipe, instrutor da Alura, e apresentarei a segunda parte do curso de Persistência no Android com Room.

Isso significa que durante as aulas avançaremos em conteúdos aprendidos durante o primeiro módulo do curso. Portanto, caso não tenha acompanhado o curso do link Android Room parte 1: Introdução a persistência de dados com ORM recomendo pausar esse vídeo no momento e assistir aos anteriores, evitando dúvidas inesperadas com base em conteúdos que podem já ter sido ensinados.

Agora, saberemos quais serão os novos assuntos a aprender durante o curso.

Anteriormente possuíamos no nosso formulário apenas o nome, telefone e e-mail do aluno. Agora teremos um campo para telefone fixo e um campo para o celular. Se adicionarmos um aluno, teremos a capacidade de adicionar ambos os telefones. Esse será um novo comportamento a ser implementado se tratando da questão visual.

Veremos também que essa abordagem é um pouco inflexível, pois o processo incluirá apenas adicionar um novo campo. Em outro momento, adicionaremos outra variante quanto ao próprio campo de telefone, explorando relacionamentos entre entidades. Em vez de trabalhar apenas com a entidade Aluno, passaremos a ter as entidades Aluno e Telefone, e as relacionaremos.

Veremos que haverá implementações para a entidade Telefone, uma de chave estrangeira, a Foreign Key, e também a de DAO para esse telefone. Para isso, colocaremos novas queries, explorando novas técnicas e quais queries farão sentido para diferentes tipos de situações.

Então, avançaremos bastante no conteúdo técnico e vamos explorar as possibilidades em relação às queries, com ou sem relacionamento, enviando parâmetros, filtros e assim por diante.

Depois que explorarmos os relacionamentos, veremos como trabalhar com nosso banco de dados sem utilizar a chamada que permite às queries ser executadas na thread principal.

Teremos a capacidade de executar as queries usando técnicas de processamento assíncrono. Veremos como ele funciona, por que considerar o uso desse tipo de solução, e que dentro do Android Framework há a entidade Asynctask para facilitar essa abordagem.

Será por meio das asynctasks que pegaremos todas as operações com nosso banco de dados, como buscar um aluno ou telefone para evitar a chamada na thread principal e fazer a operação de maneira assíncrona.

É um conteúdo realmente mais avançado cujo foco será a otimização do código, mais do que a implementação de tela. A expectativa do curso é aprender bastante sobre como trabalhar com o Room quanto ao relacionamento entre entidades e às chamadas diretas ao banco de dados.

Suportando mais de um telefone - Adicionando campo novo

Conforme um aplicativo evolui, é comum a necessidade de adicionar novos campos a um formulário, como tem sido nosso caso. Considerando que estamos trabalhando com o Room, estamos propensos à mudanças constantes. Tendo que lidar com esse desafio, aprenderemos a adicionar um novo campo e modificar nossa entidade.

Além do telefone que será considerado fixo, adicionaremos outro tipo, um celular. Essa mudança parece simples, mas veremos quais complexidades que teremos para lidar no processo.

Começaremos implementando o novo campo e permitindo que nosso formulário Activity tenha a capacidade de buscá-lo e fazer tarefas como adicionar texto a ele. Abriremos nosso FormularioAlunoActivity.java, e modificaremos nosso layout, que abriremos com "Ctrl + B".

Já estamos no nosso XML, logo, podemos copiar o mesmo modelo que temos para o telefone e modificar somente o hint, ou seja, a dica. Vamos pressionar "Ctrl + C" para copiar o código do telefone e "Ctrl + V" para criar uma duplicata.

Em primeiro lugar, o que precisaremos alterar nessa cópia serão os ID, para que sejam exclusivos. Dado que teremos um telefone fixo e um celular, o ID deverá ter os nomes modificados para diferenciá-los. Em seguida, precisaremos alterar a dica. Criaremos um novo resource para cada campo.

<EditText
    android:id="@+id/activity_formulario_aluno_telefone_fixo"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:hint="@string/telefone_fixo"
    android:inputType="phone"
    android:importantForAutofill="no"
    tools:targetApi="o" />

<EditText
    android:id="@+id/activity_formulario_aluno_telefone_celular"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    android:hint="@string/telefone_celular"
    android:inputType="phone"
    android:importantForAutofill="no"
    tools:targetApi="o" />

A princípio não teremos esse resource, então, pressionaremos "Alt + Enter" para que o programa abra uma janela e façamos a sua criação. Nomearemos a criação com o próprio nome do ID, mas para isso, escreveremos "Telefone fixo" no campo "Resource value". Faremos o mesmo para o celular: com um "Alt + Enter" abriremos a janela para a criação da resource e a nomearemos como "Telefone celular" no campo "Resource value".

Percebemos que essa não é uma implementação complicada. Só precisaremos fazer cada um dos ajustes conforme os campos de telefone que devemos ter a partir de então.

Agora, vamos para o formulário, pois ele precisará oferecer suporte para esses dois novos elementos. Não teremos mais apenas um campo de telefone, mas sim dois campos específicos separados. Por isso, vamos modificar o trecho do código em que está private EditText campoTelefone. Nele, substituiremos campoTelefone por campoTelefoneFixo. Essa mudança já terá sido feita em outros campos mais abaixo do código a partir de nossa alteração no private.

Precisaremos ajustar a nova referência de campoTelefoneFixo em inicializacaoDosCampos(), trocando activity_formulario_aluno_telefone por activity_formulario_aluno_telefone_fixo. E então teremos que aplicar os mesmos comportamentos para o celular. Vamos criar o campo private EditText campoTelefoneCelular. Buscaremos o campo e o preencheremos conforme as edições.

Embaixo da linha contendo o campoTelefoneFixo em preencheCampos(), inseriremos a linha campoTelefoneCelular.setText(aluno.getTelefoneCelular()). Ainda teremos que fazer algumas modificações nessa parte do nosso código, pois se observarmos, o campoTelefoneFixo localizado acima ainda está com "Telefone" na instrução get em vez de "TelefoneFixo". Mas vamos fazer essa alteração depois.

Com o mouse sobre getTelefoneCelular, pressionaremos "Alt + Enter". Algumas opções se abrirão e clicaremos em "Create properly 'TelefoneCelular' in 'Aluno'". Assim, será criado para nós um atributo com seus getters e setters.

Pressionaremos "Enter". Será perguntado o tipo, pois na opção de setar o texto, o programa solicitará saber se esse texto deve ser int, byte, char, short, CharSequence, etc. Nesse caso, estamos criando o campo para o celular com valor de string.

public String getTelefoneCelular() {    
      return telefoneCelular;
}

Agora, alteraremos telefone por meio do atalho "Shift + F6" para nosso novo campo de telefone fixo:

@PrimaryKey(autoGenerate = true)
private int id = 0;
private String nome;
private String telefoneFixo;
private String email;
private Calendar momentoDeCadastro = Calendar.getInstance();
private String telefoneCelular;

O Android Studio nos alertará de que estamos fazendo a modificação de um atributo com getter e setter, e nos questionará se queremos que esses elementos também sejam alterados. Pediremos para que sim, e o alerta ainda insistirá que isso estará sendo utilizado em muitos pontos, e se queremos de fato alterar. Então, escolheremos por prosseguir com a modificação clicando em "Do Refactor".

Daremos continuidade na inclusão do campo de celular. Em inicializacaoDosCampos() também teremos que incluir campoTelefoneCelular = findViewById(R.id.activity_formulario_aluno_telefone_celular). Quando começarmos a digitar, se pressionarmos "Ctrl" e a tecla de espaço, o nome do campo se autocompletará para nós.

Precisaremos preencher essa alteração no código para quando queremos criar um novo aluno. Em preencheAluno() adicionaremos telefoneCelular. No campo acima, que estará como telefone, selecionaremos, pressionaremos "Shift + F6" e renomearemos para telefoneFixo.

Teremos que programar para que esses valores sejam extraídos a partir dos campos. Por fim, precisaremos setar esse campo para nosso aluno.

private void preencheAluno() {
    String nome = campoNome.getText().toString();
    String telefoneFixo = campoTelefoneFixo.getText().toString();
    String telefoneCelular = campoTelefoneCelular.getText().toString();
    String email = campoEmail.getText().toString();

    aluno.setNome(nome);
    aluno.setTelefoneFixo(telefoneFixo);
    aluno.setTelefoneCelular(telefoneCelular);
    aluno.setEmail(email);
}

Tendo feitas essas modificações para transformar o campo "Telefone" em "Telefone fixo" e acrescentar um campo para "Celular", definindo para ambos os mesmos comportamentos, precisaremos fazer um teste de funcionamento. Porém, dado que modificamos nossa entidade, será necessário fazer todo o processo típico do Room, da Migration, para poder adaptá-la a esse novo modelo, o que faremos a seguir.

Suportando mais de um telefone - Criando a migration do campo novo

Para testar as modificações que fizemos tanto no formulário quanto no aluno, precisaremos aplicar uma Migration capaz de suportar os campos de "Telefone fixo" e de "Celular".

Primeiro precisaremos atualizar a versão do nosso banco de dados, e na sequência implementaremos a Migration.

Para isso, pressionaremos "Ctrl + N" e buscaremos por "AgendaDatabase", a Agenda em que está nosso banco de dados. Atualizaremos o banco da versão 4 para a versão 5.

Logo depois, vamos na constante TODAS_MIGRATIONS pressionando o "Ctrl + B" e aplicaremos a nova Migration.

Adicionaremos um novo valor dentro desse array TODAS_MIGRATIONS. Dentro dele estão contidas todas as migrations que fizemos até então. Digitaremos uma vírgula (,) e MIGRATION_4_5, ou seja da versão 4 para a versão 5.

Pressionaremos "Alt + Enter" e solicitaremos que o Android Studio crie essa constante. Ela será do tipo Migration e precisaremos ainda implementar uma instância de Migration.

Teremos que especificar que essa será uma Migration nova e que estaremos partindo de uma versão para outra, assim, em AgendaMigrations.java teremos o código:

private static final Migration MIGRATION_3_4 = new Migration(3, 4) { 
   @Override 
   public void migrate(@NonNull SupportSQLiteDatabase database) { 
      database.execSQL("ALTER TABLE Aluno ADD COLUMN momentoDeCadastro INTEGER)
   }
};

private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
   @Override
   public void migrate(@NonNull SupportSQLiteDatabase database)

}

Selecionando Migration no trecho específico new Migration(4, 5), vamos ainda selecionar o método que deverá ser implementado, "migrate".

Então, teremos uma nova Migration para atender às necessidades desses novos campos. Se fosse apenas um único campo, poderíamos usar a opção ALTER_TABLE, porque só estaríamos adicionando um novo elemento. Mas nesse caso, editaremos um campo que já existe, "Telefone".

Precisaremos, então, fazer o procedimento de copiar a tabela atual para a tabela nova, posteriormente removendo a atual e mantendo apenas a nova. O que poderemos fazer para facilitar nosso trabalho será pegar o código gerado pelo Annotation Processor do Room ao gerar a nova tabela, evitando erros de sintaxe.

Mas antes disso, geraremos o novo código pressionando "Ctrl + F9". Depois vamos à implementação do AgendaDatabase criado pelo Room através do Annotation Processor.

A princípio, o programa conseguiu executar a tarefa sem problemas. Novamente pressionaremos "Ctrl + N", e quando pesquisarmos por "AgendaDatabase" teremos AgendaDatabase_Impl, o código gerado pelo Room.

Copiaremos todo o código usado para criar a tabela nova de aluno usando "Ctrl+ C" e colaremos na aba AgendaMigrations.java. Ele ficará todo na mesma linha, desorganizado e não terá nem o Syntax Highlighting. Isso porque estaremos usando uma referência que não existe em Migration, a _db.

Apagaremos a referência e a substituiremos por database na chamada database.execSQL(). Agora o Android Studio poderá analisar se não há nenhum problema de sintaxe e conseguiremos avaliar se teremos as instruções necessárias.

Antes disso, pularemos algumas linhas do código para facilitar a identificação das mudanças feitas enquanto ele foi gerado. Pularemos uma linha depois do parênteses aberto, quando começa nosso ID, a primeira coluna.

O código diz que o ID é AUTOINCREMENT NOT NULL, o que estará correto. Teremos o campo de nome, do tipo TEXT, o que também estará compatível com o esperado.

O telefoneFixo também será um TEXT, assim como o e-mail. O momento de cadastro será um INTEGER, como já convertemos anteriormente, e o celular, um Text. Portanto, os campos estarão disponíveis corretamente.

private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
   @Override
   public void migrate(@NonNull SupportSQLiteDatabase database)  {
      database.execSQL("CREATE TABLE IF NOT EXISTS `Aluno` (" +
                          "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
                          "`nome` TEXT, " +
                          "`telefoneFixo` TEXT, " +
                          "`e-mail` TEXT, " +
                          "`momentoDeCadastro` INTEGER, " +
                          "`telefoneCelular` TEXT)");
}

Teremos que fazer o próximo passo, a cópia. Usaremos a mesma instrução feita da primeira vez que foi necessário copiar os dados da tabela e colaremos embaixo, para alterarmos conforme as novas demandas. Temos a instrução comentada:

//Copiar dados da tabela antiga para a nova
database.execSQL("INSERT INTO Aluno_novo (id, nome, telefone, e-mail)" +
         "SELECT id, nome, telefone, e-mail FROM Aluno");

Nossa nova tabela teremos que manter o nome anterior, "Aluno_novo", em migrate(), para que a cópia seja feita sem problemas. O próximo passo será informar exatamente quais valores adicionaremos na nova tabela. Antes tínhamos o ID, nome, telefone e e-mail.

Haverá algumas peculiaridades: não teremos mais um campo "Telefone", que passará a ser o "Telefone Fixo", e será adicionado o "Telefone Celular". Também não devemos esquecer agora do campo "Momento de cadastro".

Então faremos a alteração inserindo telefoneFixo por meio da referência do que era o "Telefone". Na sequência, inseriremos também o momentodeCadastro fazendo a cópia da maneira esperada.

database.execSQL("INSERT INTO Aluno_novo (id, nome, telefoneFixo, email, momentoDeCadastro") +
    "SELECT id, nome, telefone, email, momentoDeCadastro FROM Aluno");

Haverá comentários instruindo sobre como apagar a tabela da qual não precisaremos mais e como renomear a tabela nova com o mesmo nome da tabela antiga. Apagaremos os comentários, mas copiaremos e colaremos esses códigos para executar essas ações.

Finalmente, teremos o seguinte código:

private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
   @Override
   public void migrate(@NonNull SupportSQLiteDatabase database)  {
      database.database.execSQL("CREATE NEW TABLE IF NOT EXISTS `Aluno` (" +
                          "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                          "`nome` TEXT," +
                          "`telefoneFixo` TEXT," +
                          "`e-mail` TEXT," +
                          "`momentoDeCadastro` INTEGER," +
                          "`telefoneCelular` TEXT)");

database.execSQL("INSERT INTO Aluno_novo (id, nome, telefoneFixo, email, momentoDeCadastro") +
    "SELECT id, nome, telefone, email, momentoDeCadastro FROM Aluno");

database.execSQL("DROP TABLE Aluno");

database.execSQL("ALTER TABLE Aluno_novo RENAME TO Aluno");

Tendo feito nossa Migration, vamos testá-la para saber se tudo funcionará da maneira esperada. Pressionaremos "Shift + F10" e aguardaremos a execução.

Finalizado o teste, não seremos alertados sobre nenhum problema a princípio. Veremos se há suporte ao novo campo que adicionamos tentando adicionar um novo aluno.

Veremos que já existirão os campos de "Telefone fixo" e "Telefone celular" no layout. Adicionaremos o novo aluno "Gui", na parte de telefone fixo colocaremos "12345678", e no celular o inverso, "87654321". Também colocaremos um e-mail e conseguiremos salvá-lo.

Clicaremos nos telefones para fazer a edição. Adicionaremos um "0" nos dois e ele será mantido, indicando que essa implementação terá funcionado.

Porém, deveremos observar um detalhe — o tipo de mudança que fizemos, adicionando uma nova coluna para um campo novo, não apresenta uma modificação flexível. Faria mais sentido ter uma entidade que representasse todos os telefones do aluno, trabalhando com uma ideia de relacionamento, conforme o paradigma relacional de banco de dados e a ideia de entidades.

O ideal seria que tivéssemos um único aluno com vários telefones, assim como vários telefones seriam relacionados a um único aluno. Esse é o tipo de relacionamento conhecido como um para todos ou todos para um. Essa abordagem permite uma maior otimização em caso de necessidade de mudanças.

Assim conseguiremos adicionar telefones de trabalho, por exemplo, ou de outros tipos, sem precisar de uma Migration e de todos os passos que fizemos. Ou seja, significa que fazer uma implementação utilizando o Room pode apresentar desafios, e por isso é importante não conhecer apenas o banco de dados, mas também a forma como o Room funciona utilizando o conceito de ORM para trabalhar com as entidades.

Esse será o tema dos vídeos a seguir: como promover o relacionamento entre entidades, no nosso caso, as entidades "Aluno" e a "Telefone".

Sobre o curso Android Room parte 2: implementando operações assíncronas

O curso Android Room parte 2: implementando operações assíncronas possui 178 minutos de vídeos, em um total de 54 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!

Plus

  • Acesso a TODOS os cursos da plataforma

    Mais de 1200 cursos completamente atualizados, com novos lançamentos todas as semanas, em Programação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

12X
R$85
à vista R$1.020
Matricule-se

Pro

  • Acesso a TODOS os cursos da plataforma

    Mais de 1200 cursos completamente atualizados, com novos lançamentos todas as semanas, em Programação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

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

Acesso completo
durante 1 ano

Estude 24h/dia
onde e quando quiser

Novos cursos
todas as semanas