Alura > Cursos de Mobile > Cursos de Android > Conteúdos de Android > Primeiras aulas do curso Jetpack Compose: utilizando Migrations e relacionamentos de tabelas com Room

Jetpack Compose: utilizando Migrations e relacionamentos de tabelas com Room

Adicionando contas de usuário - Apresentação

Pessoal, sejam muito bem-vindos e bem-vindas a mais este curso da Alura. Meu nome é Júnior e eu serei o seu instrutor. Eu sou um homem de pele clara, cabelos loiros escuros compridos, que vão até a altura dos ombros, estou utilizando óculos com armação preta e durante os vídeos eu também estou com uma camiseta preta. Eu estou à frente de um fundo degradê do azul para o rosa.

O nosso principal objetivo neste curso será continuar desenvolvendo o nosso aplicativo, que é o HelloApp, que é justamente aquele aplicativo de agenda que nos permite adicionar alguns contatos. Temos esse fluxo principal de login assim que abrimos o nosso app.

Além de toda aquela inserção, alteração e exclusão de contatos que já conhecemos, essa nova versão, que vamos desenvolver, nos permitirá ter algumas contas e ao alternar entre essas contas, clicando na foto de perfil, veremos apenas os contatos específicos daquele perfil de usuário.

Para podermos fazer isso vamos adicionar algumas novas entidades ao nosso banco. Para isso vamos conhecer a estrutura dele, visualizá-la dentro do Android Studio, vamos ver algumas estratégias para poder tanto manter os nossos dados quanto também excluir, se for do nosso interesse, ao realizar essas transações, essas migrations.

Após adicionar essas entidades veremos algumas estratégias inclusive para fazer com que essas tabelas possam conversar, possam se relacionar, entre si. Tanto essa parte de atualização quanto o relacionamento de tabelas é algo fundamental no dia a dia de uma pessoa desenvolvedora Android.

Até porque muitos dos apps mais populares, hoje em dia, estão sempre atualizando, adicionando novas funcionalidades e tem vários conteúdos que conversam entre si ali dentro. É basicamente isso que vamos fazer. Para poder seguir com esse conteúdo, eu recomendo que você tenha feito o nosso curso anterior, de Jetpack Compose armazenamento de dados internos.

Até porque foi lá que fomos apresentados a vários conceitos sobre o armazenamento de informações dentro do Android, também foi lá que conhecemos as bibliotecas data store e Room, que continuaremos utilizando aqui. Essas bibliotecas inclusive fazem parte de um conjunto de técnicas e recomendação de APIs feitas pela própria equipe de desenvolvedores do Android.

Então ao terminar esse curso você terá não só os conhecimentos do relacionamento de tabelas e atualização de bancos, mas também terá tido contato com um projeto com a arquitetura recomendada por desenvolvimento oficial de aplicativos nativos no Android.

A qualquer momento deste curso você pode consultar o nosso canal do Discord para poder conhecer outras pessoas que também estão estudando, trocar algumas ideias. Eu espero que você esteja animado ou animada para começar. Então, vamos lá?

Adicionando contas de usuário - Conhecendo mudanças do projeto

Antes de prosseguirmos de fato com o conteúdo desse vídeo, eu reservei esse primeiro momento para observarmos as mudanças feitas no nosso projeto desde o último curso. Eu tenho aqui o nosso aplicativo anterior aberto, já rodando no Android Studio. Só relembrando, ele é aquele aplicativo que serve para cadastrarmos os nossos contatos.

Eu quero que vocês prestem atenção aqui no canto superior direito, temos aquele botão que serve para deslogar. A nossa nova versão do aplicativo possui um aspecto visual muito semelhante, porém, no canto superior, ao invés do botão de deslogar, temos uma foto de perfil.

Ao ser clicada, ela nos exibe uma caixa de diálogo que por enquanto está meio vazia, vamos preencher ela com algumas informações com o passar do curso. Além dessas mudanças visuais houveram também algumas mudanças no código. Eu vou abrir aqui o nosso GitHub que possui essas mudanças para observarmos o que mudou de fato.

Com a nossa página do GitHub aberta, tem dois commits que eu quero comentar com vocês. O primeiro, que é o de projeto inicial, contém justamente o código que nós temos, que foi implementado no curso anterior. Provavelmente, se você terminou aquele curso, você também deve ter esse projeto disponível.

O segundo commit, que é o de “atualizando o projeto”, ao ser clicado, vai nos exibir uma página com todas as mudanças que temos no nosso código em si. Então, após ser aberto, você pode conferir que houveram algumas mudanças de formatação, houveram também algumas mudanças especialmente aqui no nosso arquivo build.gridle, que vai disponibilizar para nós as nossas dependências.

Então houveram algumas atualizações e a fim de manter o nosso projeto sempre o mais atual possível, eu já realizei-as aqui e você pode ter disponível quando baixar o projeto. Houve uma mudança especial aqui com relação ao Room, que é aquela ferramenta que estamos utilizando para acessar o banco de dados.

Ela passou da sua versão 2.4.3 para a versão 2.5.0. Com essa atualização, que inclusive podemos conferir uma lista de mudanças aqui na documentação oficial, houve alguns pontos que mudaram o nosso código em si, e eu quero mostrar daqui a pouco, mas é importante saber que existe essa página detalhando um pouco mais as mudanças, caso você tenha interesse.

Se voltarmos para o nosso código, você pode observar uma dessas mudanças justamente deslizando um pouco mais abaixo do arquivo ContatoDao, ao invés de fazermos o import de algumas configurações, algumas coisas da maneira como fazíamos antes, tem uma pequena mudança, por exemplo, no caso do nosso replace. Agora ele possui uma pequena palavra extra que precisa ser colocada antes do replace para poder funcionar.

Inclusive podemos visualizar essa mudança no nosso código. Eu vou abrir o Android Studio, vou pesquisar pela nossa ContatoDao, vou minimizar o nosso simulador e provavelmente o nosso código, antes de realizarmos essa mudança que o Room 2.5 trouxe, estaria dessa maneira. Se atualizarmos o Room ele vai apresentar o erro, o projeto não vai rodar. Nós podemos optar por fazer algumas coisas para corrigir esse erro.

A primeira delas, que eu recomendo que você faça, seria apenas apagar o import que iria ocasionar o problema, vir no trecho de código onde ele não rodar, não compilar, utilizar o atalho "Alt + Enter" e selecionar então a nova dependência. No nosso caso ela já vem ali com o OnConflictStrategy.Companion., que é a nova palavra, e também fará com que o nosso import funcione de maneira correta.

Uma pequena observação é que se no seu projeto, além dessa dependência do replace, outros pontos também quebrarem, você precisará fazer esse mesmo procedimento de apagar o import que não funciona e fazer um "Alt + Enter" para conseguir de fato importar a nova dependência.

Um outro ponto é que se você estava realizando o import, por exemplo, de replace ou outro código que não funciona mais nessa nova versão, de maneira estática, ou seja, por exemplo, utilizando o androidx.room.OnClonflictStrategy, eu vou até copiar. Vou colocar antes do nosso .REPLACE, e vou colocar um ponto, ele ainda vai funcionar da maneira tradicional, da maneira antiga.

Porém é uma observação que eu quero deixar clara, se você está fazendo dessa forma, talvez se você mudar para esse import, que não é mais de maneira estática, que apenas fazemos um import no topo e utilizamos o restante do código, talvez o seu código possa quebrar.

Dito isso, podemos voltar para o nosso código em si, dentro do GitHub, para podermos observar algumas outras mudanças que foram feitas. São algumas mudanças mais específicas com relação à formatação, à forma como lidamos com os códigos, não é nada que vai impactar de fato a nossa implementação.

Mas podemos continuar deslizando e observando-as, são, como eu disse, algumas mudanças de formatação, exceto uma mudança em especial, que se continuarmos deslizando podemos localizar aqui o arquivo DetalhesContatoNavigation, que nos traz uma mudança que eu fiz com relação às navegações que possuímos dentro do projeto.

Agora, quem ficará responsável por navegar para todos os destinos que nós temos, é a nossa classe de NavHost, que no nosso projeto é HelloAppNavHost, nós já vamos até ela. Eu fiz justamente isso para mantermos toda a navegação em um único lugar, para ficar um pouco mais fácil de dar manutenção e também para poder tirar um pouco de responsabilidade dos nossos gráficos de navegação em si.

Um outro detalhe, e esse é um pouco mais estético, eu optei por fazer com que todos os nossos eventos, agora eles sejam no imperativo. O que isso quer dizer? Ao invés de ter um onClickVoltar, agora eu tenho um onClickVolta, sem o R no final. Ao invés de ter um onNavegarParaLogin, eu terei um onNavegaParaLogin.

Como eu falei, essa é uma mudança mais estética, foi apenas para tentar padronizar o projeto, não é algo que você precisa implementar se não quiser. Deslizando um pouco mais, podemos conferir essas mudanças que eu mencionei. Mas, deslizando no canto esquerdo, naquela outra barra, podemos localizar a pasta "ui".

Dentro dela possuímos a HelloAppNavHost. Ao clicar nela, vamos então observar que esse sim é o arquivo que vai realizar as nossas navegações. Aqui dentro nós possuímos todos os nossos gráficos e, para cada evento que ele receber, que for elevado para cá, ele vai decidir para qual destino navegar.

Então esse é um arquivo que possui realmente muitas mudanças. Se deslizarmos para o final dele, possuímos as nossas rotas, algumas com argumentos, outras sem. É a partir daqui que vamos para cada tela, cada destino do nosso aplicativo em si.

Eu vou disponibilizar esse link do GitHub para que você possa conferir as mudanças com um pouco mais de calma e ver os detalhes da nossa implementação. Mas, no geral, a seguir veremos como começar a modificar o nosso projeto e conhecer então algumas novas técnicas que vão nos auxiliar no nosso dia a dia como uma pessoa desenvolvedora. Vamos lá.

Adicionando contas de usuário - Criando entidade Usuário

A ideia principal do nosso aplicativo hoje é ser uma agenda de contatos. Ou seja, é um aplicativo que permite que possamos salvar informação de pessoas que conhecemos. Porém, dentro desse tipo de aplicativo é bastante comum podermos ter mais de uma conta e, por exemplo, em uma conta armazenar apenas informações do meu local de trabalho.

Na outra, apenas informações de pessoas que eu conheço da minha faculdade ou de algum outro grupo pessoal. Baseado nessa ideia, vamos implementar algumas novas funções dentro do nosso aplicativo, que vão nos dar algumas ferramentas, alguns conceitos que serão bem importantes também em outros projetos que possamos trabalhar.

Dentro do nosso aplicativo, hoje, podemos clicar na foto de perfil, na parte superior direita, clicar em "Adicionar nova conta" e vamos para um fluxo que já conhecemos, que nos permite poder tanto verificar as nossas informações, como o usuário e a senha, quanto também clicando em "Criar conta" podemos armazenar as informações de um usuário.

Porém, a forma como fazemos isso hoje, é utilizando o data store, então toda vez que criamos um novo usuário, apagamos as informações do usuário anterior. Quando vamos fazer essa consulta, apenas consultamos essa informação, por mais que esteja presente e armazenada no nosso dispositivo, é algo um pouco mais temporário.

Então a nossa ideia será criar uma entidade no banco de dados, que será um usuário, e assim poderemos armazenar quantas contas de usuário quisermos e, a partir de então, trabalhar para separar os nossos contatos pelo tipo de perfil que estiver fazendo o login. Para fazer isso, eu vou abrir uma pasta do nosso Android Studio, que é justamente aquela nossa pasta "data".

Dentro dela eu vou pegar uma nova entidade, com "Alt + Insert", "New Kotlin Class/File", vou deixar selecionado "Data class" e vou nomear essa nossa classe de dados como Usuario. Como ela será uma tabela no banco, já vou anotar como um @Entity, que vem do nosso Room, e eu vou reservar, por enquanto, duas propriedades dentro dessa nossa tabela.

Vou declarar a primeira, que será val nomeDeUsuario:, que será do tipo : String =, que por padrão vamos colocar o valor dela como vazio, = " ",. Vou fazer o mesmo para a nossa propriedade senha que, por padrão, também será do tipo string, que começará com o valor padrão vazio, val senha: String = " ",.

Como precisamos que pelo menos uma dessas propriedades seja uma chave primária, eu vou anotar o nome de usuário, então acima do val NomeDeUsuario eu vou colocar o nosso @PrimaryKey. Porque o nome de usuário, nesse caso, será aquela informação específica que apenas existirá uma dentro do banco, então cada conta terá um nome de usuário específico, assim como algumas redes sociais e outros apps também já possuem.

Depois que criamos a nossa entidade, eu vou criar uma dao para ela. Dentro de "database", eu vou dar um "Alt + Insert", selecionar "Kotlin Class/File", vou deixar selecionado "Interface" e vou criar a UsuarioDao. Eu vou anotá-la já com o nosso @Dao, que vem do Room. Eu acho que ele não vai fazer o import automático, então vou dar um "Alt + Enter" e import androidx.room.Dao.

Eu vou criar, pelo menos por enquanto, dois comportamentos de acesso ao nosso banco. Vou começar com uma suspend fun, que servirá para inserirmos, então suspend fun insere(). Ela vai receber um usuário do tipo usuário, (usuario: Usuario), e não vai retornar nada. Vou anotá-la com o @Insert.

Eu também vou criar uma função que irá servir para buscarmos os nossos usuários. Eu vou chamar de fun buscaTodos():, que não vai receber nada, mas vai nos retornar um : Flow<List<Usuario>>. O flow não foi importado automaticamente, vou posicionar o cursor em cima, "Alt + Enter" e vou selecionar esse que vem do coroutines, é o que vem do Kotlin, kotlinx.coroutines.flow e vou dar um "Enter".

Vou fazer a anotação do nosso buscaTodos com o @Query(). A query que eu vou passar aqui dentro é um string que tem o ("SELECT * FROM Usuario"), eu vou pegar tudo da nossa tabela de usuário. basicamente é isso o que precisamos fazer. Agora que eu tenho a nossa entidade e nossa dao, eu vou colocar a nossa dao disponível no banco.

Eu vou abrir o arquivo HelloAppDatabase. Abaixo de abstract fun contatoDao eu vou colocar uma vírgula. Na verdade, acho que aqui nem precisamos colocar uma vírgula, eu vou chamar aqui o nosso abstract fun e vou falar que será uma usuarioDao():, que será do tipo : UsuarioDao.

Agora que declaramos a nossa UsuarioDao dentro do HelloAppDatabase, precisamos explicar para o banco como ele vai criá-la quando precisarmos de fato utilizá-la. Para isso, eu vou abrir a nossa pasta "di.module", dentro dela eu vou selecionar o nosso arquivo DatabaseModule.

Esse aqui é aquele arquivo que podemos então prover uma espécie de instância da nossa ContatoDao. Eu vou criar uma função abaixo da nossa provideContatoDao, que eu vou chamar de fun provideUsuarioDao(), que terá aqui uma propriedade que eu vou chamar de DB, que será do tipo (db: HelloAppDatabase):.

O que vamos retornar será do tipo : UsuarioDao{}. Vou abrir aqui as nossas chaves. O retorno dela eu vou fazer com que seja return, vou pegar o nosso db., vou falar que será a nossa return db.usuarioDao() que acabamos de fazer a implementação. Deixa só eu apagar essa linha anterior e a de baixo também. Não vamos esquecer de anotar essa função que criamos, com o @Provides.

Agora sim. Para podermos, de fato - deixa eu só renomear aqui, porque o provideUsuarioDao ficou com um A maiúsculo, eu vou trocar para um A minúsculo, agora sim, para que possamos utilizar então, pelo menos para esse primeiro teste, o nosso banco de usuário. Eu vou abrir o nosso FormularioLoginViewModel.

Um atalho "Shift + Shift", vou pesquisar por "FormLogin" e vou selecionar o FormularioLoginViewModel. Esse código é o código que quando abrimos o nosso emulador, temos a tela de criar uma conta. Quando eu clico em "Criar", salvamos a informação no data store, eu vou aproveitar esse momento para pegar as informações digitadas, quando vamos criar uma nova conta de usuário, e também já vou criar uma no banco.

Então vou minimizar o nosso emulador. Por enquanto eu vou optar por não apagar o código do data store, eu vou manter os dois. Então abaixo do nosso bloco de data store eu vou chamar a nossa usuarioDao, que por enquanto não temos o acesso disponível dentro do view model.

No topo, no construtor, abaixo do nosso private val dataStore, eu vou colocar uma vírgula e eu vou criar uma private val usuarioDao: UsuarioDao, que será do tipo UsuarioDao. Agora que eu tenho acesso à nossa dao, eu posso chamar o usuarioDao.insere() e criar um usuário novo, (Usuario()). Dentro dos nossos parênteses eu vou até já pular uma linha.

Eu vou fazer com que nomeDeUsuario = _uiState.value.usuario,. E a nossa senha será senha = _uiState.value.senha. Vou dar um "Ctrl + Alt + L" e já podemos tentar rodar o nosso aplicativo e ver se quando clicamos em "Criar" inserimos um novo usuário dentro do banco. "Shift + F10".

Ao tentar rodar o nosso aplicativo temos o primeiro erro que veremos quando tentamos realmente fazer uma modificação dentro do banco. Esse erro diz que ainda não temos uma tabela de usuário dentro da estrutura do banco em si. Para corrigir esse erro eu vou minimizar a nossa janela, vou voltar emHelloAppDatabase.

Aqui, nas nossas entidades, dentro da anotação de database, além da nossa Contato::class, eu vou passar a nossa Usuario::class. Vou tentar rodar o nosso aplicativo mais uma vez com o "Shift + F10". O nosso aplicativo, ele começou a inicializar, porém ele não conseguiu, obtivemos um outro erro.

Se deslizarmos para o topo desse erro, vai aparecer uma mensagem aqui, basicamente falando que o Room não foi capaz de verificar a integridade dos nossos dados. Isso aconteceu porque quando fazemos uma modificação na estrutura do banco, por exemplo adicionando uma tabela nova ou modificando uma tabela antiga - eu vou minimizar o nosso emulador - precisamos informar para o banco que houve uma mudança da versão que ele estava.

Para isso, aqui dentro do nosso HelloAppDatabase, podemos alterar o version de 1 para version = 2. Ao rodar o nosso aplicativo obtivemos um erro um pouco diferente. Se deslizarmos para o topo dele, é dito que uma migração da 1 para a 2 é requerida, mas não foi encontrada. Esse erro basicamente quer dizer que, agora, o banco sabe que houve uma mudança de uma versão para outra, ele precisa saber como fazer essa transição.

Temos algumas maneiras de solucionar esse erro. Pelo menos, por agora, o que podemos fazer é apenas minimizá-lo e apagar a versão anterior dos dados do nosso aplicativo. Podemos fazer isso, por exemplo, inclusive fechando o erro que está aparecendo, selecionando o nosso aplicativo no emulador, clicando e segurando, indo em "Informações" e limpando os seus dados, por exemplo, na opção "Desinstalar".

Clicar em "Ok", então não teremos mais os dados anteriores no nosso app. Nós podemos voltar a rodar o emulador com "Shift + F10" e o nosso aplicativo agora vai abrir sem nenhum problema. Essa maneira, que acabamos de realizar, para atualizar a nova versão do banco, ela funciona durante o período de desenvolvimento.

Mas está longe de ser a maneira que queremos que o nosso app funcione no mundo real, forçar o usuário a desinstalar e ter que reinstalar o aplicativo para obter as atualizações não faz muito sentido e é por isso que, a seguir, veremos outras técnicas para realizar esse mesmo comportamento.

Sobre o curso Jetpack Compose: utilizando Migrations e relacionamentos de tabelas com Room

O curso Jetpack Compose: utilizando Migrations e relacionamentos de tabelas com Room possui 132 minutos de vídeos, em um total de 55 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!

Conheça os Planos para Empresas