Hive + Getx — Uma combinação poderosa — PARTE 2

Hive + Getx — Uma combinação poderosa — PARTE 2

Fala pessoal!

Antes de começar a segunda parte do artigo, eu quero agradecer ao grande Rodrigo Rahman, meu mentor e professor da http://academiadoflutter.com.br/. Sem ele nenhum artigo meu teria sido possível.
Recomendo muito que sigam ele no instagram e nas suas redes sociais de telegram e etc

Hoje vamos completar nosso primeiro projeto do primeiro artigo abaixo!

Hive + Getx — Uma combinação poderosa — PARTE 1

No primeiro artigo nós fizemos um todo list totalmente em Getx, porém não tinha nenhum tipo de persistência de dados, o que na teoria não faz o menor sentido, uma todo list que você fecha o aplicativo e tudo some.

É ai que vai entrar nossa segunda parte do artigo, para demonstrar como é fácil implementar uma persistência de dados utilizando o Hive em um projeto já existente.

BUILD RUNNER — Um ‘mal’ necessário

Iremos precisar adicionar o build_runner no nosso projeto para gerar os códigos do hive. Eu digo que o build_runner é um mal necessário, pois em algumas máquinas (tipo a minha), quando existe muita coisa para gerar acaba demorando demais, porém sem ele nossa vida dev iria complicar, e MUITO!

Então na parte do dev_dependencies do seu projeto vc vai inserir o build_runner

dev_dependencies:
  build_runner:
  • build_runner: (vazio mesmo) — Serve para geração de código automaticamente, normalmente gerando um arquivo .g
    Deixando vazio, ele vai pegar a melhor versão para os eu SDK

MODIFICANDO NOSSO MODELO

Para utilizarmos o hive, teremos que fazer algumas alterações no nosso task_model.dart.

A primeira coisa que devemos fazer é incluir um part, informando que esse arquivo faz parte de outro, que será o nosso .g criado.

Depois disso precisamos extender nosso TaskModel com o HiveObject para usar uma classe do próprio hive para podermos trabalhar diretamente com o nosso objeto. Assim poderemos utilizar o objeto.save() , objeto.delete()

Depois disso nós iremos precisar indexar nosso model na ‘memória’ do hive numericamente, portanto cada model deverá ter um número incremental único que é definido pelo annotation HiveType.

Depois disso para cada campo vamos ter que associar um número incremental para o índice, assim o hive pode associar o campo como se fosse uma lista, e para isso utilizaremos o HiveField, podendo inclusive colocar um valor default de inicialização da propriedade.
Com toda essa informação em mãos, nosso model ficará assim:

Como vocês podem ver, nosso task_model é o primeiro (pois começa do 0) model indexado ao hive, e cada campo tem seu número incremental começando de 0. Caso algum campo que exista que você não quer que persiste, que por exemplo seja um cálculo em tempo real ou algo do tipo é só não colocar o HiveField no campo que ele não é persistido!

Ta, mas o que eu faço com esse model agora?

Com esse model alterado, teremos que usar finalmente o build_runner para gerar o arquivo.g do seu model. o próprio hive_generator que incluímos lá no primeiro artigo, será responsável por criar o arquivo.g do seu model criando um Adapter para poder ser incluído no projeto.

Iremos executar o comando

flutter pub run build_runner build  --delete-conflicting-outputs

Esse comando será importante pois o ele irá gerar os arquivos uma única vez, ou você ao invés do `build` utilizar o `watch`, ai ele vai ficar em espera, e toda vez que você salvar algo no seu projeto ele vai tentar gerar algum arquivo que tenha mudado, então use com consciência, se seu projeto vai mudar constante, use o watch, senão, use o build e gere novamente a cada mudança.

O build_runner irá gerar um código parecido com esse:

Ainda bem, que o hive gera esse tipo de código pra gente, pois iria ser extremamente trabalhoso desenvolver algo desse nível.

INCLUINDO O ADAPTER NO PROJETO

Assim que o adapter for criado, você já pode instanciar ele no seu projeto.
Ao invés de fazer como a maioria das pessoas fariam, instanciar esse objeto no main e fazer a busca e jogar para a home já com os dados prontos, iremos somente instanciar e utilizar o StateMixin do Getx. Passando bem rápido por essa ‘feature’ , o StateMixin é um jeito legal de lidar com a view, pois com ele você pode tratar de uma maneira bem genérica o loading e assim não dá a impressão do seu sistema estar travado se seu banco contiver muitos registos para serem trazidos para a home do main

Para ficar mais fácil e separado, vamos separar esse método de chamada dos adapters e vamos chamar de _initHive(), pois precisamos iniciar o hive propriamente dito e também indicar quais são os adapters que ele vai utilizar no projeto.

Note que estamos iniciando o Hive.initFlutter (que está no package hive_flutter) e estamos registrando o adapter que foi gerado pelo build_runner. Depois disso nós abrimos o ‘container’ chamado ‘tasks’ e injetamos esse box direto no nosso Getx como permanente, assim ele será visível e utilizável em qualquer parte do nosso sistema.

ALTERANDO NOSSA CONTROLLER (HOME)

Levando em consideração que vocês estão com o codigo do primeiro artigo, vocês terão o seguinte cabeçalho do HomeController

class HomeController extends GetxController {
final todoList = <TaskModel>[].obs;
.....
}

Iremos alterar nossa controller para informar à ela a seguinte situação:

Queremos que o nosso controller tenha um StateMixin que esteja esperando uma lista de TaskModel

Que é o que a gente precisa, uma lista de TaskModel para popular nossa lista de tasks

class HomeController extends GetxController with StateMixin<List<TaskModel>?> {
final _todoList = <TaskModel>[];
...
}

Vale lembrar que antes o nosso _todoList era um
final todoList = <TaskModel>[].obs, pois para usar o obx era necessário que a propriedade fosse observável, mas não será mais necessário utilizando o stateMixing!

Como nosso sistema já esta iniciando e indo para a controller de qualquer jeito, podemos fazer a chamada do método no onInit do próprio controller, assim, quando o app entrar na tela do home, ele já vai instanciar o HomeController e obrigatoriamente ele passa pelo onInit e assim o onInit é encarregado de fazer as devidas pesquisar e avisar a view que os dados estão prontos!

Para ficar melhor estruturado iremos quebrar o código em um novo método para ser inserido dentro do onInit que chamaremos de _buildTaks() , que vai ser encarregado de dar a primeira injeção de valores no nosso todoList vindas do Hive.

Quando você usa o StateMixin vc precisa indicar pra sua view em que momento está o seu estado propriamente dito, que pode variar entre:

RxStatus.loading(); // Informa para a sua view que está carregando
RxStatus.success(); // Informa para a sua view que foi completado
RxStatus.empty(); //   Informa para a sua view que o resultado que veio da sua consulta está vazio
RxStatus.error('message');// Informa para a view que houve um erro e qual erro foi (pode usar o próprio exception se for o caso)

Como ficou nosso controller até aqui

Então aqui nós simplesmente pegamos lá da nossa injeção de dependências lá do main o Box de TaskModel e iteramos e jogamos dentro da nossa propriedade todoList que já tínhamos antes no nosso primeiro projeto.

O que acontece ai é: basicamente quando a tela é direcionada para o Home lá no main ela automaticamente já instancia a nossa HomeController, que no momento que é instanciada chama o onInit, e o nosso on Init chama a _buildTaks() que pega todas as TaskModel do Hive e joga para a nossa propriedade privada _todoList( antes essa propriedade era observável, mas como vamos utilizar o obx do stateMixing não é mais necessário!)

ALTERANDO O ADICIONAR E ATUALIZAR

Agora chegamos na melhor parte e de como é poderoso o Getx. o nosso addTask antigo que antes só tinha um todoList.add(_task) , agora precisaremos transformar o método em assíncrono, para que a gente consiga salvar as alterações no Hive, pois é necessário inserir um await na gravação da informação. Além disso, vamos informar para a view que tem novas informações para serem atualizadas e assim a task recém cadastrada aparecerá na sua lista!

Então nesse caso aqui, nós estamos fazendo basicamente do mesmo jeito da outra versão sem hive, porém agora estamos adicionando essa task ao hive E informando para a view que existe alteração, antes o obx direto na view já dava conta disso, porém como o stateMixing não fica observável precisamos informar para a view com o change, assim como fizemos no onInit.
Feito isso você já pode dar refresh, fechar o aplicativo e abrir quantas vezes quiser que a task inserida será persistida até que o aplicativo seja removido do seu equipamento.

Em contrapartida o updateTask e o changeStatus são mais fáceis ainda de atualizar para utilização do Hive, pois como ele já foi salvo dentro do ambiente do hive, você pode utilizar o objeto.save(), assim ele faz de um modo fácil a atualização dos dados dentro do objeto selecionado.

o changeStatus e o updateTask também precisarão informar à view que houve alteração porém a atualização da informação no Hive ficou muito mais fácil com o .save()

DELETANDO UMA TASK!

Não podemos esquecer que podemos deletar uma task da nossa lista, então vamos colocar a opção de excluir lá no nosso DialogTodo, e também utilizaremos um widget interessante chamado Visibility.

O Visibility é um widget que tem um child como widget filho para ser inserido e também tem uma condição visible booleana que se for false, não aparece e caso true, o widget é mostrado na tela. Vamos usar essa condição para somente mostrar o “DELETE” quando vier de uma edição, ou seja se o DialogTodo estiver carregando um objeto TaskModel no seu construtor. Então iremos criar um dialog simples perguntando se o usuário quer ou não excluir a Task e depois disso sair da tela normalmente .

Então nós criaremos lá no nosso HomeController o método de excluir tanto do todoList quanto do nosso hive, a única diferença é que iremos, após a exclusão, verificar se a lista está vazia ou não para ver que tipo de gatilho iremos enviar para a view

O nosso controller completo ficará assim

TA, MAS E A VIEW?

Como disse ai pra cima, a view está sendo ‘avisada’ que houve alteração de informação na controller e ela deve tomar a ação de alterar os dados de acordo com o tipo de informação que ela recebe (seja o empty , quando não tem dado ou com dados para serem populados na tela).

E mais uma vez o StateMixin entra em ação. Com o StateMixin adicionado na sua controller, sua view ganha um componente chamado obx iniciado pela controller:

controller.obx(
        (todoList)=>MyList(todoList),
        onLoading: Center(child:Text('Loading your list...')),
        onEmpty: Text('There is nothing to show!'),
        onError: (error)=>Text(error),
      )

esse componente é um Widget que pode ser acoplado a qualquer lugar dependendo da situação. No nosso caso como já tinhamos o obx(), dentro do body do scaffold, vamos somente substituir o nosso obx pelo nosso controller e não iremos precisar fazer absolutamente nenhuma alteração

O que nós fizemos aqui foi trocar o body: Obx(() => ListView.separated()), pelo componente do controller.obx, e com isso ganhamos o loading o empty e o error para melhorar o tratamento de informações para a view.

CONSIDERAÇÕES FINAIS

Se você seguiu passo a passo certinho pegando o projeto do da primeira versão do artigo e ir incrementando o código novo desse segundo artigo, você foi plenamente capaz e será plenamente capaz de migrar qualquer sistema que não esteja usando Hive para o Hive, e os ganhos são imensos.

Espero que eu tenha ajudado de alguma forma e até um próximo artigo, se se você chegou até aqui, segue o git do projeto modificado!

GitHub – brasizza/hive-medium-p2