Android

1 set, 2017

Consumindo APIs em Android com Retrofit e Butter Knife

Publicidade

Esses dias, eu estava lendo umas postagens no excelente grupo de programadores Android, do Facebook e me deparei com um comentário de um dev que reclamava que os tutoriais na Internet geralmente ensinavam do jeito mais difícil, sem usar bibliotecas que facilitam o trabalho do desenvolvedor e que são muito utilizadas pelas empresas no dia-a-dia dos projetos. Dentre as bibliotecas citadas por ele que deveriam ser ensinadas estavam Retrofit e ButterKnife.

Sinceramente, eu não usava nenhuma das duas até então. O Retrofit já estava no meu radar há algum tempo, mas não havia tido a necessidade de usá-la, pois sua utilidade é ser um HTTP Client fortemente tipado para Java e Android, permitindo abstrair APIs em código Java muito facilmente.

Já o ButterKnife, eu nunca tinha ouvido falar, mas realmente parece muito útil, pois ele permite fazer o binding automático dos componentes visuais em variáveis Java locais, para uso na sua lógica de aplicação. Imagino que deva ter algumas limitações, mas resolvi testar mesmo assim.

O resultado desses estudos originaram este artigo, onde vou criar um app que consome uma API REST escrita em Node.js e hospedada na Umbler, para fornecer um cadastro de clientes para o usuário.

Vamos lá!

Atenção: se este é o primeiro app Android que programa em sua vida, procure fazer este tutorial primeiro.

1. Preparando a API

Usarei, neste artigo, uma API Node.js que fornece um CRUD sobre uma base de clientes em um MySQL. Se você quiser fazer a API do zero e aproveitar para aprender um pouco de Node.js, esse artigo aqui ensina tudo que você precisa saber.

Mesmo que não queira aprender a programar Node.js, sugiro acessar o respectivo artigo, seguir as instruções para criação do banco, baixar o zip do projeto e testar usando o POSTMAN, assim como eu ensino no artigo (não esqueça de entrar no index.js e editar os dados da conexão MySQL para apontar para o seu banco).

Basicamente, usaremos neste artigo os endpoints abaixo, que você deve testar e se certificar que está funcionando antes de avançar para a próxima etapa:

  • GET /clientes – lista todos os clientes;
  • GET /clientes/{id} – retorna apenas um cliente;
  • POST /clientes – salva um novo cliente (dados no body);
  • PATCH /clientes/{id} – atualizar um cliente existente (dados no body);
  • DELETE /clientes/{id} – exclui um cliente.

O app em si não será muito elaborado, pois o foco é o uso das bibliotecas Retrofit e ButterKnife, mas sim, usaremos todos os endpoints supracitados.

Atenção: não aponte para nenhuma URL que apareça em meus exemplos ou códigos-fonte, pois são bancos e sites de exemplo que geralmente não ficam no ar por muito tempo. Crie a sua infra de teste na sua hospedagem de site, para usar os SEUS endpoints.

Após se certificar de que sua API está funcionando localmente, sugiro hospedá-la em um provedor de hospedagem. Na verdade, eu sempre crio os bancos de dados em provedores de hospedagem também, pois não gosto de ter vários servidores de banco instalados em minha máquina consumindo recursos. Particularmente, para hospedar Node.js e MySQL eu recomendo a Umbler, pois inclusive bancos MySQL pequenos são de graça e o deploy pode ser feito via Git, como mostro nesse vídeo.

Com a API 100%, vamos em frente!

2. Criando o APP

Como faremos uma prova de conceito, esse app não será nem um pouco elaborado. Na parte final deste artigo, darei algumas ideias de como deixá-lo profissional, mas por ora, vamos fazer um app de uma tela só.

Nesta tela, poderemos fazer a pesquisa de cliente por id, o cadastro, a atualização e a exclusão de clientes. Para ver se tudo está sendo persistido no banco de dados, usaremos o endpoint “GET /clientes” no navegador mesmo, para que sejam listados todos os clientes e possamos ver se está tudo funcionando como deveria.

Crie um novo projeto no Android Studio (mas pode ser Eclipse ou NetBeans também, com pequenas variações nos passos que vou mostrar) usando o template de Activity Basic, aquele que vem com um botão flutuante no canto inferior direito. Usarei aqui o ConstraintLayout, que já expliquei como funciona em outro post e o nome da minha activity principal é MainActivity.

O Android Studio gera dois arquivos de layout para essa Activity. Um chamado activity_main.xml, que serve como “arcabouço” ou “esqueleto” para o layout, e o content_main.xml, que serve como “miolo” ou “área interna” do layout. Isso permite uma organização maior do layout, bem como reuso de código. Focaremos aqui na construção da interface usando o content_main.xml, que, por padrão, vem um Hello World que deve ser apagado.

Arraste para esse formulário um campo de edição de texto (id:txtId) com dois botões à sua direita (id:btnBuscar e id:btnExcluir). Logo abaixo, coloque um campo de edição de texto para o nome do cliente (id:txtNome) e outro mais abaixo para o CPF do cliente (id:txtCPF). No final do formulário, coloque um botão de salvar (id:btnSalvar). Crie as constraints necessárias (ou use o recurso Infer Constraints) para que sua tela se pareça com a minha abaixo:

Caso tenha dificuldade, use e/ou consulte as fontes abaixo, do content_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="br.com.luiztools.apimysql.MainActivity"
    tools:showIn="@layout/activity_main">
 
    <EditText
        android:id="@+id/txtId"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="6"
        android:inputType="number"
        android:hint="ID do Cliente"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/btnBuscar" />
 
    <Button
        android:id="@+id/btnBuscar"
        android:text="Buscar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintLeft_toRightOf="@+id/txtId"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintRight_toLeftOf="@+id/btnExcluir" />
 
    <Button
        android:id="@+id/btnExcluir"
        android:text="Excluir"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintLeft_toRightOf="@+id/btnBuscar"
        app:layout_constraintRight_toRightOf="parent" />
 
    <EditText
        android:id="@+id/txtNome"
        android:hint="Digite o nome do cliente"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        android:inputType="textPersonName"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/btnBuscar"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp" />
 
    <EditText
        android:id="@+id/txtCpf"
        android:hint="Digite o CPF do cliente"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/txtNome" />
 
    <Button
        android:id="@+id/btnSalvar"
        android:text="Salvar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/txtCpf" />
 
</android.support.constraint.ConstraintLayout>

Alguns pontos que merecem atenção nesse layout são:

  • os campos de edição de texto possuem “hints”, o que remove a necessidade de usar TextViews para rótulos de campos;
  • os campos de edição de texto possuem inputType, para agilizar a digitação dos valores já exibindo o teclado virtual correto para cada um.

Já na activity_main.xml, fiz duas alterações bem pequenas: troquei o ícone do floating button e troquei o id dele (de fab para btnNovo). O código XML apenas desse componente que foi alterado está abaixo:

<android.support.design.widget.FloatingActionButton
        android:id="@+id/btnNovo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_input_add" />

Na sequência, vamos fazer esta tela funcionar, mesmo que de maneira incompleta usando ButterKnife!

3. Usando ButterKnife

Agora que temos a nossa tela pronta, é hora de programá-la. Usaremos a biblioteca open-source ButterKnife para nos auxiliar com tarefas repetitivas e muito chatas que são os bindings dos widgets dos layouts XML em variáveis Java locais.

Caso ainda não tenha entendido o ganho que isso traz, imagina que a seguinte linha de código:

EditText txtNome = (EditText)findViewById(R.id.txtNome);

é substituída por:

@BindView(R.id.txtNome) EditText txtNome;

Visualmente pode não parecer grande coisa, mas esta declaração é feita no topo da classe da Activity, o que permite que esse componente seja usado em toda a classe. Além disso, essa biblioteca é extremamente performática, pois não usa Reflection, mas sim um transpilador das anotações para código Java nativo de binding, que não serve apenas para binding de campos, mas também de cores, strings, imagens etc.

Mas antes de sairmos aproveitando tudo o que a biblioteca tem a oferecer, precisamos adicionar a sua dependência ao nosso projeto, modificando nosso arquivo build.gradle (do Module:app) para adicionar as seguintes linhas dentro da seção dependencies:

compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'

Essas linhas adicionam uma nova dependência ao projeto, bem como um processador de annotations (aquelas palavras precedidas por @ que fica sobre os métodos e variáveis).

Com essa dependência adicionada, vá na sua MainActivity.java e adicione as seguintes variáveis no escopo da sua classe, fora do onCreate:

@BindView(R.id.txtId)
EditText txtId;

@BindView(R.id.txtNome)
EditText txtNome;

@BindView(R.id.txtCpf)
EditText txtCpf;

O tipo EditText irá exigir a adição de imports na sua classe, bem como a annotation @BindView. Apenas use o recurso ALT+ENTER e siga em frente.

No onCreate desta Activity, logo abaixo o setContentView, você deve inicializar o ButterKnife para que ele faça as conversões corretas de código (aproveite para jogar fora o código velho do FloatingActionButton):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
 
    ButterKnife.bind(this);
}

E é aqui que a magia acontece. Com muito menos repetição de código, os meus componentes estarão carregados nas variáveis locais que defini no trecho de código anterior à esse, permitindo o seu uso sem qualquer receio nos métodos de onClick dos botões, os quais faremos na sequência, também usando ButterKnife pra fazer o binding do método ao componente apropriado (quem já fez isso com Listeners vai ver muito valor aqui).

Primeiro, vamos programar o btnNovoOnClick (o floating button) para apenas limpar todos os campos do formulário, incluindo o id, sinalizando um novo cadastro (apenas coloque esse método no escopo da classe MainActivity.java):

@OnClick(R.id.btnNovo)
public void btnNovoOnClick(){
    txtId.setText("0");
    txtNome.setText("");
    txtCpf.setText("");
}

Note a annotation @OnClick acima do método, que informa qual o id do componente que queremos mapear o click. Você pode inclusive colocar diversos ids para o mesmo método tratar, pode omitir o parâmetro View no método (como eu fiz) ou mesmo substituir o view pelo tipo correto do componente (pra evitar castings no código, embora eles ainda ocorram no código compilado).

Na sequência, vamos programar o btnBuscarOnClick: ele deve pegar o id que foi digitado pelo usuário e usar esse id como filtro para trazer os dados do usuário e preencher o formulário. Aqui, não iremos na API ainda, então faremos uma falsa busca por enquanto:

@OnClick(R.id.btnBuscar)
public void btnBuscarOnClick(){
    int id = Integer.parseInt(txtId.getText().toString());
    //vai na API com esse id e traz o cliente
    if(true) { //se encontrou um cliente com aquele id
        //preenche os campos do formulário com os dados do cliente
        txtNome.setText("Nome Falso");
        txtCpf.setText("12345678901");
    }else{
        Toast.makeText(this, "Nenhum cliente encontrado com o id " + id, Toast.LENGTH_LONG).show();
    }
}

Quase finalizando, vamos programar, também de maneira fake, o click do nosso botão de excluir. A ideia aqui é que o usuário digite o id e clique em excluir, o app pede uma confirmação e avisa se conseguiu excluir ou não:

@OnClick(R.id.btnExcluir)
public void btnExcluirOnClick(){
    final int id = Integer.parseInt(txtId.getText().toString());
 
    //cria o dialog de confirmação
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setTitle("Você tem certeza que deseja excluir este cliente?")
           .setPositiveButton("Sim", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    //vai na API com esse id e exclui o cliente
                    if(true){
                        Toast.makeText(getApplicationContext(), "Cliente excluído com sucesso!", Toast.LENGTH_LONG).show();
                        txtId.setText("0");
                        txtNome.setText("");
                        txtCpf.setText("");
                    }else{
                        Toast.makeText(getApplicationContext(), "Nenhum cliente encontrado com o id " + id, Toast.LENGTH_LONG).show();
                    }
                }
            });
    builder.create().show();
}

E por fim, o click do botão de salvar. Esse botão também é um pouquinho mais “esperto” que os outros, pois ele deve diferenciar o cadastro de um novo cliente de uma edição de um cliente já existente. Isso é feito analisando o campo id, que deve estar vazio ou com um número inferior a 1.

@OnClick(R.id.btnSalvar)
public void btnSalvarOnClick(){
    String idStr = txtId.getText().toString();
    final int id = idStr.equals("") ? 0 : Integer.parseInt(idStr);
    boolean sucesso = true;
    if(id > 0){ // edição
        //PATCH na API enviando o id junto para editar
    }else{ // novo cadastro
        //POST na API sem enviar o id que será gerado no banco
    }
 
    if(!sucesso){
        if(id > 0)
            Toast.makeText(this, "Não foi encontrado nenhum cliente com esse id para atualizar ou a atualização não foi permitida.", Toast.LENGTH_LONG).show();
        else
            Toast.makeText(this, "Não foi possível salvar esse cliente.", Toast.LENGTH_LONG).show();
    }
    else
        Toast.makeText(this, "Cliente salvo com sucesso!", Toast.LENGTH_LONG).show();
}

Se você mandar este app executar agora e fazer alguns testes, verá os comportamentos esperados uma vez que ainda não estamos consumindo a API, o que faremos na próxima parte deste artigo usando Retrofit!

E aí, gostou da primeira parte?