Source: Unsplash

AutoValue em aplicativos Android

Menos é mais. Esta é uma das frases mais repetidas em todas as áreas. Para o mundo de desenvolvimento de software, menos código escrito pode significar mais estabilidade, mais simplicidade e mais agilidade. Neste artigo, apresento uma visão geral de uma excelente ferramenta para desenvolvimento de código: AutoValue e suas extensões.

Visão geral do AutoValue

Para o exemplo, vamos criar uma classe com informações de uma pessoa. A classe PersonData contém algumas informações básicas. Ela guarda as informações em atributos final, que podem ser acessados através de getters. Também sobrescreve os métodos toString(), hashCode() e equals(Object). Esta é uma implementação muito comum em projetos Android (e Java no geral), conhecida como value class (classe de valores). Podemos conferir no trecho abaixo que esta classe já alcança quase 70 linhas.

Este artigo tem vários exemplos de código, apresentados como Gists. Em vários deles, a formatação foi simplificada para reduzir o número de linhas. O projeto de exemplo com o código original, está no GitHub.

Muito código para manter, analisar e revisar.

Agora, para complicar ainda mais, vamos supor que a classe receberá um novo atributo. Para isso, vários passos são necessários: Criar o atributo, incluir o parâmetro no construtor, criar o método getter, incluir o atributo no toString(), incluir o atributo no equals() e incluir o atributo no hashCode(). Caso o desenvolvedor esqueça de uma destas etapas, o código falhará em algum momento importante. Tudo isto apenas para ter o funcionamento básico da classe. Se ela fosse utilizada em algum tipo de serialização como JSON ou implmentasse uma interface como Parcelable, ainda mais código precisaria ser escrito, mantido e revisado.

O propósito do AutoValue e suas extensões é justamente reduzir a quantidade de código que precisa ser escrito para classes simples. O desenvolvedor passa a precisar se preocupar apenas com declarar a classe, todo o resto é feito pelas ferramentas de geração de código. Para isto, precisamos incluir algumas configurações no script de build e aplicar algumas anotações em nossas classes.

Certifique-se de estar utilizando a versão 2.2 do plugin Android Build Tools. Caso esteja usando uma versão anterior, será necessário ativar o plugin android-apt.

Este post indicava anteriormente a obrigatoriedade do uso do plugin android-apt. Com o lançamento da versão 2.2 do Android Build Tools, o plugin não é mais necessario.

A seguir, incluimos as dependências do AutoValue no nosso módulo. A dependência do tipo annotationProcessor indica que o AutoValue será aplicado no momento de execução do plugin de geração de código. A dependência do tipo provided indica que o AutoValue não precisa ser incluído junto com o projeto, já que ela é usada somente na compilação.

Agora já podemos refatorar nossa classe pessoa para um formato muito mais simples. Incluímos a anotação @AutoValue, indicando que ela deve passar pela geração de código. Transformamos também a classe em abstract, uma vez que sua implementação será gerada e não construída manualmente. Removemos também a criação dos atributos, declarando apenas métodos abstratos equivalentes. A declaração de cada método indica qual o formato do atributo associado, quais modificadores e anotações serão definidos para ele. Nossa classe passou a ter apenas pouco mais de dez linhas.

Como a classe PersonData agora é abstrata, precisamos definir uma forma de criar novas instâncias da mesma. Por convenção, as classes AutoValue definem um método create, que instancia um novo objeto e retorna o mesmo de maneira transparente.

Note que invocamos o construtor da classe AutoValue_PersonData. Esta é a classe gerada pelo AutoValue com a implementação concreta de tudo o que definimos. Todos os atributos e getters estão nesta classe. Além disso, esta classe implementa também os métodos básicos toString(), equals() e hashCode().

Ao incluir o método create, poderá ocorrer um erro indicando que a classe AutoValue_PersonData ainda não existe. Lembre-se que ela só será gerada no build, portanto faça um build do projeto para garantir que ela foi criada e está atualizada. Abaixo, temos um trecho do código gerado.

Podemos notar que a classe concreta herda da nossa classe abstrata, como já era de se esperar. Ela inclui um construtor com todos os campos e implementa a inicialização dos mesmos. Será criada também com o mesmo pacote da classe abstrata definida, o que facilita diversos pontos do desenvolvimento.

Em algumas situações, pode não ser tão interessante usar um construtor com uma lista longa de campos. No nosso exemplo, temos alguns parâmetros String que podem causar alguma confusão na chamada do nosso create. Pensando nisso, o AutoValue inclui a possibilidade de definição de Builders. Ou seja, permite a geração de classes que possam ser instanciadas através da utilização do Builder Pattern. Desta forma, podemos criar novas instâncias invocando uma sequência de métodos com os nomes dos atributos. Isto evita confusões como trocar os parâmetros String de ordem. Para criar um Builder, basta definir uma classe interna abstrata na nossa classe PersonData, como no exemplo abaixo.

Todos os atributos tem métodos equivalentes definidos. Além disso, trocamos nosso create pela definição de um novo método static, como no exemplo a seguir.

Agora os clientes de nossa classe podem definir novas instâncias bem mais facilmente, definindo os atributos através de uma sequência de métodos.

Voltando ao código da nossa classe gerada, podemos ver que algumas consistências são executadas no construtor. Por padrão, uma classe AutoValue não permite valores null, lançando exceções em tempo de execução.

Caso a classe definida aceite valores nulos em algo atributo, precisamos fazer esta declaração usando a anotação @Nullable em nossos atributos e no nosso Builder. Desta forma, as consistências não serão geradas e os valores serão aceitos.

Além de campos nulos, podemos também definir métodos concretos na nossa classe abstrata sem problemas. Estes métodos podem ser usados para facilitar a manipulação de informações e estarão disponíveis nas subclasses geradas.

Podemos incluir um método para verificar se o campo que pode ser nulo está presente, por exemplo. Ao incluir este método, o AutoValue irá gerar um alerta no build. Isto porque este tipo de implementação fere a definição de value class. Não encontrei problemas para usar estes métodos mesmo assim. Não recomendo, porém, incluir muitos métodos deste tipo, já que a classe abstrata perderá seu sentido.

Nossa classe PersonData agora está muito mais simples do que em sua primeira versão. Podemos declarar novos atributos com facilidade e ainda temos funcionalidades úteis como um builder disponíveis. Mas sua utilidade em um projeto Android ainda é limitada, uma vez que a classe concreta gerada ainda tem só o básico de uma classe Java.

Em suas versões mais recentes, o AutoValue incluiu uma funcionalidade bastante útil para projetos Android, as Extensions. Com elas, é possível incluir classes geradoras de código customizadas. Novas classes intermediárias entre a classe abstrata e a concreta são geradas, permitindo que as extensões gerem código com facilidade e flexibilidade.

Neste artigo vamos apresentar o uso de duas extensões interessantes para os projetos Android: AutoValue-Parcel e AutoValue-GSON. Existem diversas outras extensões disponíveis, mas vamos dar atenção a estas duas.

Para começar, vamos configurar a extensão para permitir classes Parcelable. Vamos incluir outra dependência apt no build.gradle do nosso módulo.

Agora basta declarar que nossa classe PersonData implementa Parcelable. Todos os métodos referentes a interface e os auxiliares serão gerados pela extensão, não sendo necessária nenhum código adicional.

Todos os métodos da interface serão gerados pela extensão, não sendo necessário nenhum código adicional. Nossa classe AutoValue_PersonData tem toda a implementação de Parcelable. Simples, não?

Já a utilização da extensão para integração com Gson exige um pouco mais de código, mas não muito. Vamos começar incluindo as dependências da extensão do AutoValue e do Gson.

Agora, para indicar que extensão de Gson deve ser aplicada a nossa classe, precisamos definir o método typeAdapter.

Novamente, ocorrerá um erro de compliação antes do primeiro build, uma vez que as classes ainda não foram geradas.

Para que a integração das classes AutoValue com o Gson ocorra corretamente, precisamos registrar a classe AutoValueGsonTypeAdapterFactory na instância de Gson utilizada. Esta classe é gerada pelo plugin do AutoValue-Gson e contém referências para todas as classes geradas e seus adaptadores.

Atualização: a versão anterior deste post indicava que era necessário desenvolver a classe adapter manualmente. Isto não é mais necessário nas versões mais novas da extensão.

Este tipo de integração é muito útil para comunicação entre aplicativos e backend, inclusive com Retrofit. A extensão do AutoValue faz com que não seja mais necessário definir intermináveis classes equivalentes aos arquivos Json gerados. Basta definir as classes abstratas e registrá-las, agilizando o desenvolvimento.

Estas são duas extensões úteis para o mundo Android, porém existem diversas outras para diferentes situações: Moshi Extension, Cursor Extension, With Extension eRedact Extension são algumas delas.

No geral, a utilização de classes com AutoValue nos projetos agiliza o desenvolvimento de classes simples e que não exigem grande complexidade. A engine padrão e as extensões permitem que o desenvolvedor foque nas questões relevantes do projeto e deixe tarefas simples e repetitivas para serem executadas pelo build.

Gostou do AutoValue? Tem outro uso para a ferramenta? Não deixe de comentar aqui no Medium ou de mandar um ping no Twitter.