sexta-feira, 12 de fevereiro de 2016

Dominando o Android 2 edição: correções e melhorias

Olá povo,

Quem me vê no dia a dia, sabe que eu ando com um exemplar do meu livro o tempo todo. Além de utiliza-lo como forma de consulta, eu saio anotando nele:
  • as novidades em APIs que saíram após o lançamento do livro; 
  • novos tópicos para colocar em versões futuras; 
  • e obviamente erros que os leitores vão reportando. 
Na primeira edição, o exemplar que eu andava foi muito útil para melhorar a segunda edição, mas quase que eu perco o livro quando ele caiu em uma poça d'água! o.O
Por isso resolvi compartilhar com vocês minhas anotações, assim vocês já vão sabendo o que está errado, o que já mudou em termos de API, e o que pode mudar para a próxima ;) A medida que for achando mais problemas, vou atualizando esse post.
Ah! Antes que vocês perguntem, não tem nenhuma previsão para a próxima edição. Mas provavelmente o que vai acontecer é eu começar a trabalhar no livro após o Google I/O desse ano que vai acontecer entre 18 e 20 de maio.
Segue abaixo o que eu anotei até agora. Eu dividi em duas categorias: melhorias e erros.

Melhorias

Capítulo 0 / Pág. 29 - essa seção deve receber uma atualização com as novidades do emulador 2.0.
http://tools.android.com/tech-docs/emulator

Capítulo 3 - vou adicionar mais uma seção nesse capítulo falando dos layouts percentuais PercentFrameLayout e PercentRelativeLayout que eu falei nesse post aqui.

Capítulo 4 / Pág. 132 - esqueci de falar de uma propriedade bacana do TextInpuLayout que mostra a quantidade de caracteres digitados.
http://developer.android.com/reference/android/support/design/widget/TextInputLayout.html#setCounterEnabled(boolean)
<android.support.design.widget.TextInputLayout
    ...
    app:counterEnabled="true">
    <EditText
        ...
        android:maxLength="100"/>

Capítulo 4 / Pág. 163 - esqueci de mencionar que o método performFiltering() é executado em uma thread separada, enquanto que o método publishResults() é executado na UI thread.

Capítulo 6 / Pág. 254 - deveria ter criado um estilo para a Toolbar.

Capítulo 7 / Pág. 297 - era para ter enfatizado que, se uma classe implementar OnPreferenceChangeListener, caso uma SharedPreference seja modificada em qualquer ponto da aplicação, essa classe será notificada (obviamente se ela estiver instanciada).

Capítulo 7 / Pág. 310 - o trecho do método onClick() deveria estar em negrito.

Capítulo 7 / Pág. 325 - deveria estar realizando as operação com o ContentProvider utilizando a classe AsyncQueryHandler como falei nesse post aqui.

Capítulo 10 / Pág. 388 - substituir a enum Status por annotations como falei nesse post.

Capítulo 14 / Pág. 473 - no método onMessageReceived(), checar se from.equals(senderId) e se !data.isEmpty().

Capítulo 15 / Pág. 519 - o método getMap() tornou-se obsoleto (deprecated), em seu lugar, deve-se usar o método getMapAsync().
https://developers.google.com/android/reference/com/google/android/gms/maps/SupportMapFragment.html#getMapAsync(com.google.android.gms.maps.OnMapReadyCallback)

Capítulo 15 / Pág. 551 - podemos enviar localização fictícia com o emulador 2.0 do Android ou com o Genymotion.

Capítulo 20 - Falar sobre TransitionManager. Depois de ver essa palestra do Lúcio Maciel, me arrependi de não ter colocado esse assunto nesse capítulo.
https://www.youtube.com/watch?v=GKjPf0NI9ps

Capítulo 23 - Muitos fundamentos interessantes que são mostrados no treinamento de Gradle do Udacity seriam interessantes de colocar no livro. Principalmente linha de comando.
https://www.udacity.com/course/gradle-for-android-and-java--ud867

Capítulo 26 / Pág. 843 - Colocar alguns detalhes interessantes no uso da RecyclerView que eu coloquei nesse post.

Capítulo 26 / Pág. 891 - A biblioteca Otto foi recentemente marcada como deprecated em favor do RxJava e RxAndroid. Mas não se preocupem, o Otto continua funcionando. Uma outra opção seria usar o EventBus.

Capítulo 27 - Colocar um exemplo de DataBinding com listas.
http://developer.android.com/intl/pt-br/tools/data-binding/guide.html


Erros

Capítulo 3 / Pág. 110 - a biblioteca de compatibilidade do GridLayout é melhor do que a versão nativa, pois ela teve vários bugs corrigidos.
http://developer.android.com/tools/support-library/features.html#v7-gridlayout
Basta adicionar a dependência no build.gradle.
compile 'com.android.support:gridlayout-v7:+'
E no layout modificar para
<android.support.v7.widget.GridLayout ...

Capítulo 5 / Pág. 170 - a figura 5.6 está errada... ela deveria estar mostrando as marcações na ferramenta draw9patch. Ao invés disso está mostrando o GIMP :P

Capítulo 6 / Pág. 212 - No último parágrafo, a primeira frase deve ser "... o parâmetro savedInstanceState é igual a nulo, ..."

Capítulo 6 / Pág. 255 - o exemplo está com problema na Toolbar quando rodamos a aplicação no KitKat. A correção do bug já está no GitHub nesse commit aqui. E eu fiz até um vídeo explicando a solução :)

Capítulo 10 / Pág. 385 - na definição da tabela Hotel, o campo "estrelas" deve usar o tipo DECIMAL(2,1). Essa modificação já está no GitHub.
https://github.com/nglauber/dominando_android2/blob/master/server/hotel_service/webservice.php

Capítulo 14 / Pág. 511 - no último parágrafo, falei que após o realizar o login com a conta do Google, uma boa opção seria utilizar o ID da conta e enviar para o seu serviço web. Mas a solução recomendada pelo Google é o uso do Token.
http://android-developers.blogspot.com.br/2016/01/using-google-sign-in-with-your-server.html

Capítulo 24 / Pág. 764 - a figura 24.8 ficou "estranha".

Capítulo 25 / Pág. 839 - esse foi o pior erro do livro. Ficou faltando praticamente uma página.
Notei que um trecho grande do capítulo 26 (1ª impressão) acabou ficando de fora do livro. Creio que foi no momento da diagramação do livro na editora. Na página 839, no segundo parágrafo, após o texto "... que estamos executando." deveria vir o texto abaixo:

Biblioteca 1: ButterKnife
O ButterKnife (http://jakewharton.github.io/butterknife/) é um biblioteca desenvolvida por Jake Wharton (Google Developer Expert de Android) que facilita a atribuição de views à classes sem precisar utilizar o método findViewById(int).
Vejamos o exemplo a seguir:
public class MainActivity extends Activity {
  @Bind(R.id.txtTitulo) TextView mTxtTitulo;
  @Bind(R.id.edtNome) EditText mEdtNome;
  @Bind(R.id.listview) ListView mListPessoas;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);
    // Pronto! Pode usar os atributos!
  }
}
Perceba que anotamos os atributos da classe que representam componentes no arquivo de layout com a anotação @Bind(int) passando o id do componente. No método onCreate(Bundle) inicializamos esses atributos simplemente invocando ButterKnife.bind(this). O ButterKnife também funciona em Fragments e Adapters como veremos mais adiante. Essa biblioteca ainda possui outros recursos interessantes como definir evento de clique em botões.
@OnClick(R.id.botao1)
public void onClickBotao1(View view) {
}

@OnClick(R.id.botao2)
public void onClickBotao2() {
}
Perceba que no evento de clique do segundo botão, não colocamos o parâmetro, pois para a biblioteca ele é opcional. Vamos utilizar o ButterKnife em nosso aplicativo. Para isso, adicione a dependência no build.gradle.
dependencies {
    …
    compile 'com.jakewharton:butterknife:7.0.1'
}

Biblioteca 2: OkHttp
No capítulo 8 vimos como acessar um servidor web utilizando a classe HttpUrlConnection. Ela funciona muito bem, o único incoveniente é quando precisamos ler o retorno do servidor,

x.x.x.x.x.x.x.x.x.x.x

Peço desculpas pelo inconveniente, mas pelos menos agora vocês vão entender porque o texto está maluco :)
------------------

Quero deixar meu muito obrigado a todos os leitores que reportam os erros encontrados no livro. E se você adquiriu ou livro, não se esqueça de se cadastrar no grupo de discussão da segunda edição do "Dominando o Android".

4br4ç05,
nglauber

domingo, 31 de janeiro de 2016

Acessando o SQLite de forma assíncrona

Olá povo,

Não importa o quanto eu estude, sempre tem algo novo sobre Android que eu não sabia. Conversando com os meus amigos Diego Nascimento e André [Mion], descobri a classe AsyncQueryHandler que nos permite realizar as transações de banco de dados em uma Thread separada.
Acho que a maioria de vocês deve saber que quando o usuário está interagindo com o aplicativo, ele está utilizando o que chamamos de Main Thread ou UI Thread. Para que não tenhamos problemas de desempenho na renderização da UI, ela deve executar a 60 fps (frames por segundo). Se fizermos um cálculo rápido, podemos constatar que nenhuma operação que é feita na UI Thread deve demorar que 16 milissegundos. Caso contrário, perderemos frames a aplicação e o usuário começará a notar a aplicação "travando" ou demorando a responder.
Aqui no blog já falei sobre várias técnicas para não utilizar a Main Thread, tais como: AsyncTask, AsyncTaskLoader e LoaderManager. Essa última, em particular resolver parte do problema do título desse post, pois com o CursorLoader + LoaderManager podemos fazer a consulta em Content Provider em background. Perfeito! Mas... E as operações de inclusão, exclusão e atualização?
Para esses casos temos a classe AsyncQueryHandler. Essa classe facilita a realização dessas operações em background. Embora, o SQLite seja muito rápido, caso você esteja fazendo uma transação que utilize várias tabelas, ou mesmo que trabalhe com muitos registros, esse processamento poderá demorar bem mais que 16ms.
A utilização dessa classe é bem simples.
public void inserir() {
    ContentValues values = new ContentValues();
    values.put("titulo", "Lembrete");
    values.put("descricao", "Escrever post");

    Uri uri = Uri.parse("content://seu.content.provider/mensagens");

    // Começa o processo assíncrono
    new MeuAsync(getActivity()).startInsert(0, null, uri, values);
}

class MeuAsync extends  AsyncQueryHandler {
    // Usando WeakReference para evitar leak da Activity
    private WeakReference<Context> mContext;

    public MeuAsync(Context ctx) {
        super(ctx.getContentResolver());
        mContext = new WeakReference<>(ctx);
    }

    @Override
    protected void onInsertComplete(int token, Object cookie, Uri uri) {
        super.onInsertComplete(token, cookie, uri);
        if (mContext.get() != null) {
            Toast.makeText(mContext.get(),
                    "Registro inserido com sucesso!",
                    Toast.LENGTH_SHORT).show();
        }
    }
}
Perceba que estamos utilizando o método startInsert() passando como parâmetro: um identificador para essa transação; um objeto qualquer que podemos utilizar posteriormente no método de callback (aqui passamos null); a URI para o seu Content Provider; e os valores que serão inseridos.
De forma semelhando, poderíamos utilizar os métodos: startUpdate(), startDelete() e startQuery().

Na classe MeuAsync, ficam os métodos que serão chamados após a realização da operação em background. No nosso exemplo utilizamos apenas o o onInsertComplete. Mas nos casos de atualização, exclusão e busca de registros, utilizaríamos os métodos onUpdateComplete(), onDeleteComplete e onQueryComplete().

4br4ç05,
nglauber

sábado, 9 de janeiro de 2016

Percentual Layout

Olá povo,

Quase sempre, quando vou ministrar aula de Android, sempre tem alguém que pergunta: "Professor tem como definir o tamanho de um componente usando percentual?". Normalmente quem faz essa pergunta é algum desenvolvedor web, onde utilizar valores percentuais para elementos de UI é algo bem comum no front-end de uma aplicação.
Mas no caso do Android, a resposta era sempre não, mas podíamos resolver esse problema utilizando o LinearLayout e definir nas views filhas a propriedade android:layout_weight como mostrei nesse post aqui.
Mas finalmente o Google criou para nós, gerenciadores de layout que suportam medidas percentuais. São eles: PercentFrameLayout e PercentRelativeLayout.

Para utiliza-los, basta adicionar a seguinte dependência no build.gradle.
dependencies {
    ...
    compile 'com.android.support:percent:23.1.1'
}

Depois é só utilizar no arquivo de layout.
<android.support.percent.PercentRelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <View
        android:id="@+id/centered_image"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#FF0000"
        app:layout_heightPercent="@fraction/fifty_percent"
        app:layout_widthPercent="@fraction/fifty_percent"/>

    <TextView
        android:id="@+id/caption"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_below="@id/centered_image"
        android:background="#00FF00"
        android:text="Texto"
        app:layout_marginRightPercent="@fraction/twentyfive_percent"
        app:layout_marginLeftPercent="@fraction/twentyfive_percent"
        app:layout_widthPercent="50%"/>
</android.support.percent.PercentRelativeLayout>

Perceba que para evitar problemas de compilação adicionamos a largura e altura do componente como 0dp, mas as propriedades que realmente definirão o tamanho do componente são app:layout_heightPercent e app:layout_widthPercent.
Estamos utilizando uma referência para @fraction ao invés do valor hard-coded. Desta forma defina esses valores, por exemplo, no res/values/dimens.xml.
<resources>
    ...
    <fraction name="twentyfive_percent">25%</fraction>
    <fraction name="fifty_percent">50%</fraction>
</resources>
Outro detalhe importante é que podemos definir margens utilizando valores percentuais, recurso que não conseguíamos no LinearLayout.
O resultado ficará como a seguir:


Se tiver dúvidas, consulte a documentação oficial:
Ou deixe seus comentários aqui :)

4br4ç05,
nglauber

terça-feira, 5 de janeiro de 2016

RecyclerView no Android

Olá povo,

Devia ter escrito esse post a muito tempo, mas finalmente saiu :) Desde a primeira versão do Android em 2008 até o começo de 2014, para criarmos telas de listagem utilizávamos a classe ListView juntamente com alguma subclasse de BaseAdapter (ArrayAdapter ou CursorAdapter por exemplo) e esse conjunto funcionava muito bem. Mas conforme o tempo foi passando, ele foi apresentando algumas "limitações" que atrapalhavam sua conformidade aos requisitos/padrões de UI/UX atuais. Nada que fosse impossível de ser implementado, mas que eram mais trabalhosos de serem feitos, tais como:
  • Performance na atualização de itens: a ListView está ligada ao Adapter, que possui uma lista de objetos. Se inserirmos um objeto nessa lista, temos que invocar o método notifyDatasetChanged() que fará com que toda a lista seja refeita/redesenhada. Com a RecyclerView podemos atualizar só um item da lista (inserindo/atualizando/excluindo) ou um intervalo específico.
  • Layouts diferenciados para cada situação: com a RecyclerView podemos configurar gerenciadores de layouts, indicando por exemplo, que a lista terá uma única coluna quando o aparelho estiver em portrait e duas quando estiver em landscape. Ou ainda dizer que o primeiro item da lista será diferente dos demais.
  • Animações e gestos: Com a RecyclerView, à medida que os itens vão sendo adicionados ou removidos, uma animação é realizada dando um feedback visual para o usuário do que aconteceu. A utilização de gestos também ficou bastante simples. Ações como o swipe sobre um item da lista é algo trivial de ser feito.
  • Scroll em ambos os sentidos: a RecyclerView permite o scroll em na horizontal e na vertical, o que não era possível nativamente na ListView.
  • Baixa curva de aprendizagem: o conceito utilizado pela RecyclerView é muito parecido com o que temos na ListView. Então quem já a conhece não terá muitos problemas.
Estes são os mais importantes para mim. Mas já pode te convencer a mudar para a RecyclerView. Se é que não já mudou? Você já mudou, certo? ;)

Pondo em prática

Veremos um exemplo simples de uma listagem de mensagens, mas que obviamente pode ser ajustado para qualquer outro propósito. Adicione as seguintes dependências no build.gradle do projeto.
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.android.support:design:23.1.1'
    compile 'com.android.support:cardview-v7:23.1.1'
    compile 'com.jakewharton:butterknife:7.0.1'
}
A RecyclerView está biblioteca appcompat. Já a biblioteca de design foi adicionada aqui para podermos utilizar o FloatActionButton. Cada item da lista será um CardView, então adicionamos a dependência. E por fim, utilizamos a ButterKnife que falei nesse post aqui.

Vamos agora para a implementação começando pela classe básica.
public class Mensagem {
    public String titulo;
    public String texto;

    public Mensagem(String titulo, String texto) {
        this.titulo = titulo;
        this.texto = texto;
    }
}
Nada a comentar sobre essa classe, então vamos adicionar o arquivo de layout item_mensagem.xml que representará cada item da lista.
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="4dp"
    android:foreground="?android:attr/selectableItemBackground"
    app:cardCornerRadius="5dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical"
        android:padding="8dp">
        <TextView
            android:id="@+id/txtTitulo"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textAppearance="?android:attr/textAppearanceLarge"/>
        <TextView
            android:id="@+id/txtMensagem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"/>
    </LinearLayout>
</android.support.v7.widget.CardView>
Um ponto interessante a ressaltar nesse layout é a propriedade foreground. A RecyclerView, por padrão, não fornece o feedback de toque como faz a ListView, pois ela parte do pressuposto que seus itens não são clicáveis, ao contrário da ListView que mesmo que não haja tratamento do evento de clique, um feedback visual é dado ao usuário.

O arquivo de layout da activity (activity_main.xml) é exibido a seguir:
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:padding="8dp">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/titulo"
            android:id="@+id/edtTitulo"/>
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/texto"
            android:id="@+id/edtTexto"/>
        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/recyclerView"/>
    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_marginRight="16dp"
        android:layout_marginBottom="16dp"
        android:id="@+id/fab"
        android:src="@drawable/ic_add_white_24dp"/>
</FrameLayout>
O único detalhe a comentar sobre esse layout é a imagem do ícone do botão. Ela pode ser encontrada no site de ícones do Material Design, que fornece mais de 800 ícones gratuitos que você pode utilizar no seu aplicativo.

Vamos agora implementar o adapter a ser utilizado por nossa RecyclerView.
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;

public class MensagemAdapter extends 
    RecyclerView.Adapter<MensagemAdapter.VH> {

    List<Mensagem> mMensagens;
    AoClicarNaMensagem mListener;

    public MensagemAdapter(List<Mensagem> mensagens, 
                           AoClicarNaMensagem listener) {
        mMensagens = mensagens;
        mListener = listener;
    }

    @Override
    public VH onCreateViewHolder(ViewGroup parent, 
                                 int viewType) {
        View v= LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_mensagem, 
                         parent, false);

        final VH vh = new VH(v);
        vh.itemView.setOnClickListener(
          new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              if (mListener != null) {
                int pos = vh.getAdapterPosition();
                Mensagem mensagem = mMensagens.get(pos);
                mListener.mensagemClicada(mensagem);
              }
            }
          });
        return vh;
    }

    @Override
    public void onBindViewHolder(VH holder, int pos) {
        Mensagem msg = mMensagens.get(pos);
        holder.textTitulo.setText(msg.titulo);
        holder.textTexto.setText(msg.texto);
    }

    @Override
    public int getItemCount() {
        return mMensagens != null ? 
            mMensagens.size() : 0;
    }

    public static class VH extends 
        RecyclerView.ViewHolder {

        @Bind(R.id.txtTitulo)   
        public TextView textTitulo;
        @Bind(R.id.txtMensagem) 
        public TextView textTexto;

        public MensagemViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    public interface AoClicarNaMensagem {
        void mensagemClicada(Mensagem mensagem);
    }
}
Esssa sem sombra de dúvida é a classe mais importante desse exemplo. Então vamos aos detalhes sobre ela:
  • Para definir um adapter para a RecyclerView devemos criar uma subclasse de RecyclerView.Adapter, e como podemos observar, ela é "tipada". Ou seja, ela necessita de um tipo que deve ser uma subclasse de RecycleView.ViewHolder. No nosso exemplo, essa classe é chamada simplesmente de VH e está declarada dentro da própria classe MensagemAdapter.
  • Para criar uma instância do nosso adapter, além da lista de objetos, podemos passar um objeto que tratará o evento de clique do item da lista. Diferentemente do que é feito na ListView, onde temos o OnItemClickListener, o evento de clique em um item da lista é definido no próprio item, ou seja, no adapter. 
    • No nosso caso, quem estiver interessado em ouvir o evento de clique no item da lista, deverá passar como parâmetro para o adapter, um objeto que implemente a interface AoClicarNaMensagem. Na prática, a activity passará esse objeto para o adapter.
  • Obrigatoriamente devemos implementar 3 métodos: 
    • O método onCreateViewHolder criará a instância do ViewHolder baseado no arquivo de layout que representa cada item (no nosso caso item_mensagem.xml). Esse método já implementa, por padrão, a abordagem de um adapter eficiente que expliquei nesse post, e esse é o momento ideal para definirmos o evento de clique do item da lista. Percebam que por meio do atributo itemView (que já é da classe ViewHolder) definimos o evento de clique. E usando o método getAdapterPosition() podemos obter o índice da posição da lista que foi clicada.
    • No onBindViewHolder() é onde preenchemos cada View do ViewHolder. Essa abordagem de create/bind já era usada no CursorAdapter como falei nesse post.
    • O getItemCount() é quantidade de itens que sua lista exibirá, como já é de costume em qualquer adapter.
  • Percebam que estamos utilizando o ButterKnife no ViewHolder, mas fique a vontade em usar o bom e velho findViewById() :)
Vamos utilizar esse adapter na Activity.
public class MainActivity extends AppCompatActivity {
    @Bind(R.id.edtTitulo)
    EditText mEdtTitulo;
    @Bind(R.id.edtTexto)
    EditText mEdtMensagem;
    @Bind(R.id.recyclerView)
    RecyclerView mRecyclerView;

    List<Mensagem> mMensagens;
    MensagemAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mMensagens = new ArrayList<>();
        mAdapter = new MensagemAdapter(
                mMensagens, mListener);
        mRecyclerView.setAdapter(mAdapter);

        GridLayoutManager layoutManager =
                new GridLayoutManager(this, 2);

        layoutManager.setSpanSizeLookup(
                new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int pos) {
                        return pos == 0 ? 2 : 1;
                    }
                });
        mRecyclerView.setLayoutManager(layoutManager);
    }

    @OnClick(R.id.fab)
    public void onClick(View view) {
        Mensagem mensagem = new Mensagem(
                mEdtTitulo.getText().toString(),
                mEdtMensagem.getText().toString());
        mMensagens.add(mensagem);
        mAdapter.notifyItemInserted(
                mMensagens.size() - 1);

        mEdtTitulo.getText().clear();
        mEdtMensagem.getText().clear();
    }

    private MensagemAdapter.AoClicarNaMensagem mListener=
        new MensagemAdapter.AoClicarNaMensagem() {
            @Override
            public void mensagemClicada(Mensagem msg) {
                String s = String.format(
                    "%s %s", msg.titulo, msg.texto);
                Toast.makeText(MainActivity.this, 
                    s, Toast.LENGTH_SHORT).show();
            }
        };
}
Vamos agora as explicações relevantes:
  • Estamos utilizando o gerenciador de layout GridLayoutManager que nos permite dividir nossa lista em colunas. 
    • No nosso caso, estamos dizendo que teremos duas colunas. O método setSpanSizeLookup() indica quais itens da linha vão ocupar mais de uma coluna. Para esse exemplo, estipulei que apenas o primeiro item da lista vai ocupar duas colunas, os demais ocuparão uma (das duas colunas) da lista.
    • Existem ainda os gerenciadores de layout LinearLayoutManagerStaggeredGridLayoutManager. O primeiro organiza os itens da lista de forma linear enquanto o segundo organiza os itens em forma de grid, mas suporta itens de tamanhos completamente diferentes e os exibe como se fosse um "mosaico". 
    • E você pode criar seus próprios gerenciadores de layout.
  • Percebam que no evento de clique, ao inserirmos um registo, utilizamos o método notifyItemInserted(). E como sempre adicionamos o item ao final da lista, passamos o tamanho da lista menos um. 
    • Existem também os métodos notifyItemRemoved(), notifyItemChanged(), notifyItemMoved() para quando um item da lista foi respectivamente removido, alterado ou movido de uma posição para a outra da lista.
    • Podemos utilizar os métodos notifyItemRangeInserted(), notifyItemRangeRemoved() e notifyItemRangeChanged(). Que indicam que um intervalo de itens da lista foi inserido, removido ou alterado.
    • Obviamente existe o notifyDatasetChanged(), que possui o mesmo comportamento da ListView.
Ao executar o exemplo e adicionar alguns itens, você terá um resultado similar ao da figura abaixo.
O aplicativo deve estar adicionando os itens na lista. Mas e para remover? Que tal usar o gesto de swipe? ;) Com a RecyclerView isso ficou bem simples. Vejamos o código a seguir.

private void configuraSwipe() {
  ItemTouchHelper.SimpleCallback swipe =
      new ItemTouchHelper.SimpleCallback(0, 
          ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {

          @Override
          public boolean onMove(
              RecyclerView recyclerView,
              RecyclerView.ViewHolder viewHolder,
              RecyclerView.ViewHolder target) {
            return false;
          }

          @Override
          public void onSwiped(
              RecyclerView.ViewHolder viewHolder, 
              int swipeDir) {

            final int position = 
              viewHolder.getLayoutPosition();
            mMensagens.remove(position);
            mAdapter.notifyItemRemoved(position);
          }
      };
  ItemTouchHelper itemTouchHelper = new ItemTouchHelper(swipe);
  itemTouchHelper.attachToRecyclerView(mRecyclerView);
}
Com a classe ItemTouchHelper estamos configurando o gesto de swipe para esquerda e para direita. E no método onSwiped() removemos o item da lista e notificamos o adapter. Perceba que com essa classe também podemos fazer o evento de mover itens na lista.
Para concluir, chame esse método no onCreate() da activity.
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    configuraSwipe();
}
Execute a aplicação novamente e insira alguns itens. Em seguida, tente exclui-los utilizando o gesto de swipe. Perceba que após um item ser excluído, os demais itens são reorganizados com uma animação.

Conclusão

Se você ainda não usa a RecylerView, você está perdendo diversos recursos bacanas que estão sendo adicionados a esse componente. Entretanto ele ainda tem algumas melhorias a serem feitas e o próprio pessoal do Google admitiu isso para mim no Android Dev Summit.
Coisas simples como a empty view já poderiam estar implementadas (nada demais, eu convivo com isso sem problemas). Definir um divisor entre as linhas da RecyclerView é um absurdo. Isso já deveria estar abstraído no componente. Outra coisa que eu sentia falta era um CursorAdapter para a RecyclerView. Mas depois de ver a implementação interna dele, achei melhor fazer o meu mesmo :)
No final das contas, sem sombra de dúvidas o RecyclerView é bem mais flexível que a ListView e o seu resultado final vale muito a pena.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

P.S.: Meus agradecimentos a André Mion que me ajudou a discutir os tópicos desse post.

terça-feira, 29 de dezembro de 2015

2015 foi fantástico

Olá povo,

Criei esse blog para disseminar conhecimento, mas também como uma forma de registrar coisas importantes que aconteceram na minha vida profissional. E esse post é basicamente um resumo de mais um ano fantástico na minha vida, repleto de alegrias e realizações. Graças a Deus!
Conheci muita gente e reforcei os laços de amizade com outros, e tudo isso foi possível graças aos vários eventos que participei, das aulas que ministrei e as viagens que eu fiz.

No primeiro mês do ano aconteceu o lançamento do meu livro "Dominando o Android", e eu estava com um medo danado que o pessoal não gostasse... Mas felizmente o livro foi um sucesso (pelo menos para mim :) pois vendeu 2 mil exemplares em 7 meses. Eu não esperava vender nem os primeiros mil (sério!). Meu muito obrigado a todos que adquiriram!
Em março fui para Salvador participar do Linguágil onde falei de produtividade no Android por meio da utilização de bibliotecas. Palestra que explorei bastante durante o ano.
Nesse mesmo mês, começaram as aulas da primeira turma do curso de pós-graduação em mobile da Unibratec, onde ministrei a disciplina de Android.

Em maio, estava em João Pessoa no Androidos. Um evento muito bacana organizado pelos meus colegas Josias e Eduardo (entre outros). Parabéns e muito obrigado pelo convite!

Ainda em maio, tive o orgulho de participar mais uma vez do Google I/O (e eu não escrevi nenhum post!!! pqp) onde tivemos como grande novidade o lançamento do Android M. Sem dúvida o evento mais incrível do mundo. Mas antes, participei do GDG Dev Summit que reuniu o pessoal dos GDGs de todo o mundo!

Em junho, voltei a Salvador para participar do Intel Software Day e tive a honra de receber o convite para participar do programa de Innovators da Intel. Muito obrigado pela confiança Carrara e George! Espero reforçar essa parceria em 2016 falando das diversas ferramentas que a Intel possui para facilitar a vida dos desenvolvedores.

No mês de julho participei do The Developers Conference (ou simplesmente TDC) em São Paulo. Um dos maiores eventos do Brasil voltados para desenvolvedores. Na oportunidade ainda participei de um mini-curso muito legal de Intel Real Sense ministrado pelo meu amigo Felipe Pedroso.

Voltei a São Paulo em agosto para participar do Android Dev Conference. Organizado pela iMasters, foi maior evento sobre desenvolvimento Android da América Latina. Conteúdo de alto nível com uma organização impecável.
O evento seguinte foi o DevFest Nordeste em outubro aqui mesmo em Recife (ou Hellcife) :) Na oportunidade, falei sobre Google Play Services. Uma palestra que pretendo explorar um pouco mais ano que vem. Depois foi a vez do DevFest Sudeste em Belo Horizonte que foi show de bola!

Novembro foi a vez de visitar o Google em Mountain View pela terceira vez :) E continua sendo surreal para mim a cada visita. Quem sabe um dia não vou pra lá todo dia para trabalhar? \o/
No Google, participei do encontro mundial dos GDEs (Google Developer Experts) onde tive a fantástica surpresa de ser chamado ao palco para receber o reconhecimento do Google pelo meu trabalho no livro. Aconteceram alguns codelabs muito interessantes como: Beacons, Project Tango, CastBrilloe Weave.

Também participei do Android Dev Summit no Computer History Museum. Sem dúvida a melhor agenda de Android que eu vi na minha vida. Só os melhores caras do Google palestrando. Foi para fechar 2015 com chave de ouro.

Isso sem contar o lançamento da segunda edição do Dominando o Android. Revisado, ampliado e atualizado! ;)

Que venha 2016! E que ele seja tão bom (ou melhor) que 2015.
Feliz ano novo para todos!!!

4br4ç05,
nglauber

segunda-feira, 14 de dezembro de 2015

Android Support Annotations

Olá povo,

Vou tentar atualizar o blog com mais frequência com posts menores como este. É um assunto relativamente novo, mas pouco utilizado (e conhecido) pelos desenvolvedores (inclusive eu).

Por questões de performance, é recomendado que evitemos a estrutura "enum" em aplicações Android, substituindo-as por um inteiro (int).
Mas aí sempre vem a pergunta: e como restringir os valores? Por exemplo: digamos que a classe Encomenda possui um atributo chamado status que pode receber apenas os valores "aberto", "em trânsito", "cancelado" e "entregue".

Com as anotações do Android podemos definir uma constante do tipo int para cada status e depois informar no atributo que ele só poderá receber um desses valores.

Para utilizar as anotações do Android, adicione a seguinte dependência no build.gradle.
dependencies {
 ...
 compile 'com.android.support:support-annotations:23.+'
}

Agora vejamos o código a seguir:
class Encomenda {
    public static final int STATUS_ABERTO      = 0;
    public static final int STATUS_EM_TRANSITO = 1;
    public static final int STATUS_CANCELADO   = 2;
    public static final int STATUS_ENTREGUE    = 3;
    
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
            STATUS_ABERTO, 
            STATUS_EM_TRANSITO, 
            STATUS_CANCELADO,
            STATUS_ENTREGUE
    })
    public @interface Status {
    }

    @Status
    public int status;
}
A anotação @Retention indica onde a anotação atuará. No nosso caso, indicamos que ela é usada apenas no processo de compilação. Mas poderíamos utiliza-la em runtime. E com a anotação @IntDef estabelecemos os valores que podem ser utilizados pelo elemento que utilizar essa anotação. Por fim, anotamos o atributo "status" o @Status para restringir os valores que podem ser atribuídos a ele.
Desta forma, o código a seguir, daria erro pois 10 não é um valor de status válido:
Encomenda encomenda = new Encomenda();
encomenda.status = 10;
Com essa abordagem, você deixa de utilizar as enums e ainda deixa o seu código robusto evitando valores inválidos.

Mas isso é só o começo! A biblioteca de Android Suppport Annotations permite:
  • Validar se um parâmetro de método pode ser nulo ou não com @Nullable ou @NonNull;
  • Informar com @StringRes que um atributo do tipo int receberá o id de um texto do values/strings.xml;
  • Determinar se um método deve executar na main thread (com @UiThread ou @MainThread) ou em uma thread separada @WorkerThread;
  • Determinar que uma variável/atributo/método receberá o RGB de uma cor utilizando @ColorInt.
  • Informar que uma chamada necessita de uma permissão no AndroidManifest.xml utilizando @RequiresPermission.
E ainda tem muito mais! Mais informações vocês podem encontrar nos links abaixo:

4br4ç05,
nglauber

domingo, 25 de outubro de 2015

DevFest Sudeste 2015


Olá povo,

No dia 24 de outubro de 2015 participei do GDG DevFest Sudeste. Eu participei do evento apresentando uma palestra sobre desenvolvimento para Android Wear.
Mas sem sobra de dúvida, a melhor parte do evento é rever os amigos e conhecer gente nova e falar de tecnologia com o pessoal :)
Gostaria de agradecer o convite parabenizar todos os organizadores e participantes do evento.



4br4ç05,
nglauber