AndroidAnnotations, Entendendo e Utilizando

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação. Você receberá um email de confirmação. Somente depois de confirmar é que poderei lhe enviar o conteúdo exclusivo por email.

Email inválido.
Blog /Android /AndroidAnnotations, Entendendo e Utilizando

AndroidAnnotations, Entendendo e Utilizando

Vinícius Thiengo11/12/2016
(1745) (10) (99) (9)
Go-ahead
"Se você está planejando em fazer alguma coisa diferente, você será criticado por causa disso. Tenha fé de que o que você está fazendo é o certo e bloqueie qualquer negatividade."
Herb Kelleher
Código limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas598
Comprar Livro
Conteúdo Exclusivo
Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Opa, blz?

Nesse artigo vamos está implementando a library AndroidAnnotations em um projeto Android para que seja possível minimizar a quantidade de código, principalmente aqueles que são padrões e que não fazem parte da lógica de negócio do domínio do problema de nossos aplicativos Android.

O AndroidAnnotations é uma library bem conhecida entre desenvolvedores Android (mais de 8 mil estrelas no GitHub), provavelmente devido a facilidade de uso e a redução de código que ela nos ajuda a ter.

Antes de prosseguir, não deixe de assinar o blog, logo acima na área de emails, assim receberá o conteúdo em primeira mão e com ainda mais comentários em email.

Abaixo os tópicos que estaremos abordando para entender como utilizar o AndroidAnnotations:

AndroidAnnotations, visão geral

Como informado logo no início do artigo, o objetivo do AndroidAnnotations é fazer com que programadores Android consigam mais de suas lógicas de negócio. Evitando a necessidade de codificar trechos padrões e necessários em seus aplicativos.

Ok, mas como essa library faz isso?

O AndroidAnnotations utiliza o Java Annotation Processing Tool, com isso ele adiciona um passo a mais, em tempo de compilação, para poder adicionar todo o código relativo as anotações que estão no projeto.

Alias, esse é o principal ponto fraco do AndroidAnnotations. Dependendo da quantidade de anotações e como você as utiliza, o tempo de compilação pode sofrer um delay ainda maior que o comum.

Para melhor entender o que são as anotações, veja o código abaixo:

@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {

@ViewById(R.id.lv_articles)
protected ListView listView;

@InstanceState
@NonConfigurationInstance
public ArrayList<Article> articles = new ArrayList<>();

@ViewById
protected Toolbar toolbar;

@Bean
protected ArticlesAdapter articlesAdapter;


@AfterViews
protected void viewsInitialized(){
setSupportActionBar(toolbar);
retrieveData();
listView.setAdapter( articlesAdapter );
}

@ItemClick(R.id.lv_articles)
public void itemClickedListener( Article article ){
ContentActivity_.intent(this).article( article ).start();
}

private void retrieveData(){
if( articles.size() == 0 ){
workingInBackgroundThread();
}
}

@Background
protected void workingInBackgroundThread(){
/* TODO */
}

@UiThread
protected void workingInUiThread(){
/* TODO */
}
}

 

Simples, não? Vamos descrever um pouco melhor o que está acontecendo. Veja o trecho de código a seguir:

...
@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity
...

 

Nele estamos indicando ao AndroidAnnotations que nossa MainActivity pode trabalhar com anotações dessa library e que o layout XML que é para ser carregado como layout dessa activity é o R.layout.activity_main.

O trecho de código anterior nos permite descartar o uso de um método de ciclo de vida, o onCreate(). Mas é possível utilizar ambos, não há problemas quanto a isso.

Note que o AndroidAnnotations, para a adição de código em tempo de compilação, na verdade ele cria uma nova classe da classe atual que utiliza as anotações dele.

O que?

Por exemplo. Com a anotação @EActivity nós sinalizamos que annotations poderão ser utilizadas, com isso o AndroidAnnotations vai criar uma classe com o mesmo rótulo da classe atual, porém com um "_" (underscore ou underline) no final desse rótulo. No código anterior será criada a classe MainActivity_.

Nós não temos acesso a essas novas classes e mesmo se tivéssemos não faria sentido altera-las, pois elas serão construídas (ou reconstruídas) em tempo de compilação.

O motivo dessas novas classes serem criadas é que o Java Annotation não permite que códigos existentes sejam alterados, é possível somente criar novos códigos.

Sabendo disso, sobre as novas entidades criadas, temos de acessar o AndroidManifest.xml e atualiza-lo. Veja abaixo:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.futebolblog">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".MainActivity_"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>

 

O Android Studio, mais precisamente o Code Inspector dele, vai acusar erro quando você realizar essa alteração, não se preocupe, prossigua com a compilação, pois ela vai ocorrer sem problemas.

Assim, podemos prosseguir com o restante do código. Agora vamos ao código de declarações de instâncias:

...
@ViewById(R.id.lv_articles)
protected ListView listView;

@InstanceState
@NonConfigurationInstance
public ArrayList<Article> articles = new ArrayList<>();

@ViewById
protected Toolbar toolbar;
...

 

A variável de instância listView será preenchida com a referência a ListView de ID lv_articles do layout da MainActivity, mais precisamente a ListView do XML abaixo:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.futebolblog.MainActivity"
tools:showIn="@layout/app_bar_main"
android:background="@drawable/background">

<ListView
android:id="@+id/lv_articles"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/transparent"
android:dividerHeight="10dp"/>
</RelativeLayout>

 

O mesmo para a variável de instância toolbar. Porém no caso desta variável não foi necessário explicitar o ID, pois o ID e o nome da variável têm exatamente o mesmo rótulo. Essa é uma outra vantagem no uso do AndroidAnnotations.

A variável de instância articles vai conter uma lista de objetos do tipo Article. Essa lista pode ser "pesada", digo, pode ter uma construção que consome muitos recursos do Android SO.

Nesse caso optamos por não reconstruí-la quando o aplicativo aciona a reconstrução da Activity.

A anotação @NonConfigurationInstance substitui o código abaixo:

...
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
articles = savedInstanceState.getParcelableArrayList( ARTICLES );
}
...

 

Com isso mantemos a referência a lista articles sem precisar da quantidade de linhas de código como acima. A anotação @InstanceState permite o uso do código referente ao método onSaveInstanceState(), código como o abaixo:

...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

if (savedInstanceState != null) {
articles = savedInstanceState.getParcelableArrayList(ARTICLES);
}
...
}

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putParcelableArrayList(ARTICLES, articles);
super.onSaveInstanceState(savedInstanceState);
}
...

 

Com a anotação @InstanceState (e todas as outras anotações da library) conseguimos passar ao AndroidAnnotations a responsabilidade de construir o código anterior em tempo de compilação.

Caso não conheça os assuntos em torno dos métodos onSaveInstanceState()onRestoreInstanceState(), não deixe de, logo depois desse artigo, acessar os conteúdos (com vídeo) sobre estas entidades. Links a seguir:

Assim, podemos prosseguir com o restante do código. Veja o trecho abaixo:

...
@Bean
protected ArticlesAdapter articlesAdapter;
...

 

A anotação @Bean indica que a variável articlesAdapter deve ser inicializada com uma instância de ArticlesAdapter, o que indica que essa classe tem um construtor com nenhum ou apenas um parâmetro. Caso tenha um construtor com apenas um parâmetro, esse deve ser do tipo Context.

Note que para o @Bean poder ser utilizado em algum tipo de dado, esse tipo deve ter a anotação @EBean nele, como abaixo:

@EBean
public class ArticlesAdapter {
...
}

 

Agora o próximo trecho de código, o referente a anotação @AfterViews. Segue:

...
@AfterViews
protected void viewsInitialized(){
setSupportActionBar(toolbar);
retrieveData();
listView.setAdapter( articlesAdapter );
}
...

 

Essa anotação indica que o método será invocado somente depois de todas as Views referenciadas via AndroidAnnotations terem sido inicializadas. Com isso temos a garantia que nenhuma delas é nula.

O próximo trecho de código é no contexto do ListView:

...
@ItemClick(R.id.lv_articles)
public void itemClickedListener( Article article ){
ContentActivity_.intent(this).article( article ).start();
}
...

 

O código acima, caso já conheça o ListView, mais precisamente o listener de clique dele, substitui o seguinte código:

...
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
ContentActivity_.intent(MainActivity.this).article( articles.get(i) ).start();
}
});
...

 

O único parâmetro de entrada do método com o @ItemClick é o objeto retornado pelo método getItem() (já com o cast aplicado) do adapter do ListView.

O argumento de @ItemClick é o ID do ListView que utiliza este listener. Com esta anotação evitamos ainda mais código necessário, mas que pode ser acrescentado depois, em tempo de compilação.

O conteúdo do método itemClickedListener() é equivalente ao código abaixo:

...
Intent intent = new Intent(MainActivity.this, ContentActivity_.class);
intent.putExtra("article", articles.get(i) );
startActivity(intent);
...

 

Note que nesse caso sabemos que a activity ContentActivity também está trabalhando com o AndroidAnnotations, por isso a necessidade de referenciarmos a classe ContentActivity_ que será criada. O método article() do código abaixo:

...
ContentActivity_.intent(this).article( article ).start();
...

 

Nos permite acesso ao conteúdo dele, na ContentActivity, exatamente como a seguir:

...
public class ContentActivity extends AppCompatActivity {
...
@Extra
Article article;
...
}

 

O AndroidAnnotations se encarregará de colocar todo o código para que a variável article esteja preenchida assim que for necessário o uso dela.

Note que o nome do método utilizado para envio do dado deve corresponder ao nome da variável, pois a anotação @Extra não permite o uso de argumentos.

Com isso podemos prosseguir com o restante do código apresentado no início da seção. Agora o código referente ao uso de Thread via anotações:

...
private void retrieveData(){
if( articles.size() == 0 ){
workingInBackgroundThread();
}
}

@Background
protected void workingInBackgroundThread(){
/* TODO */
}

@UiThread
protected void workingInUiThread(){
/* TODO */
}
...

 

A anotação @Background faz com que todo o código do método com essa anotação seja executado em uma Thread de background.

A anotação @UiThread faz com que o código do método seja executado na Thread principal ou Thread UI. Isso tudo sem precisar de códigos como runOnUiThread() e new Thread(){}.start().

Caso não conheça os assuntos Thread de background e Thread principal no Android, logo depois desse artigo, veja os conteúdos dos links listados abaixo:

Notou algo nos códigos apresentados até aqui? Isso mesmo. O AndroidAnnotations não trabalha com o modificador de acesso private. Ao menos em todas as anotações utilizadas nesse artigo e no projeto de exemplo.

Algo que vale ressaltar é que na documentação não ficou explicíto se a anotação @Background garante uma referência fraca a instâncias de componentes do Android (Activity, por exemplo). Isso para evitar memory leak.

Porém há um trecho na documentação que mostra a preocupação da library quanto ao problema de memory leak, logo vamos assumir que o AndroidAnnotations não daria esse "vacilo".

Com isso finalizamos essa seção de visão geral e introdução ao AndroidAnnotations.

Essa library é muito maior do que o que foi apresentado aqui, mas com o descrito você já consegue fazer bastante coisa em seus projetos.

Siga com o conteúdo do artigo, pois vamos implementar o AndroidAnnotations em um projeto real. Em Fontes você terá acesso ao link da página desta library.

Projeto de exemplo - parte Web

O projeto completo, um aplicativo Android de Blog sobre Futebol, envolve o consumo de conteúdos em um servidor Web PHP.

O código é bem simples e não tem interface com o usuário, é somente um conjunto de classes que permitem a obtenção de uma lista de artigos.

Apesar de ser apresentado todo o conteúdo da parte Web do projeto logo em seguida, você pode acessar esse trecho no GitHub a seguir: https://github.com/viniciusthiengo/aa-futebol-blog-web.

Configuração e estrutura

O backend roda em um servidor PHP, no pacote MAMP, com configurações:

  • Apache 2.2.29;
  • PHP 5.6.2;
  • MySQL 5.5.38.

A estrutura do projeto está como a seguir:

A classe Article (explicada na seção de domínio do problema) tem a exata mesma estrutra de campos que a classe Article do código Android, isso para facilitar parse JSON no Android.

A parte Web do projeto foi construída com um IDE pago, mas você pode utilizar qualquer IDE PHP, um como o Eclipse, por exemplo.

Você também é livre para utilizar o tipo de backend que melhor se adeque ao seu conhecimento. Python ou Java, por exemplo.

Domínio do problema

Como classe de domínio do problema temos somente a classe Article:

class Article
{
public $id;
public $title;
public $content;
public $imageUrl;
public $summary;
public $numViews;
public $numComments;
public $timeToRead;

public function __construct(
$id,
$title,
$content,
$imageUrl,
$summary,
$numViews,
$numComments,
$timeToRead )
{
$this->id = $id;
$this->title = $title;
$this->content = $content;
$this->imageUrl = $imageUrl;
$this->summary = $summary;
$this->numViews = $numViews;
$this->numComments = $numComments;
$this->timeToRead = $timeToRead;
}
}

Classe de negócio

Como classe de negócio, também há somente uma. Essa seria a área de acesso a camada de dados do sistema, porém aqui, com dados fake, estes são retornados diretamente pela classe de negócio AplAdmin:

class AplAdmin
{
public static function getArticles(){
$content = '<strong>Lorem Ipsum</strong> é simplesmente uma simulação de texto da indústria tipográfica e de impressos, e vem sendo utilizado desde o século XVI, quando um impressor desconhecido pegou uma bandeja de tipos e os embaralhou para fazer um livro de modelos de tipos. Lorem Ipsum sobreviveu não só a cinco séculos, como também ao salto para a editoração eletrônica, permanecendo essencialmente inalterado. Se popularizou na década de 60, quando a Letraset lançou decalques contendo passagens de Lorem Ipsum, e mais recentemente quando passou a ser integrado a softwares de editoração eletrônica como Aldus PageMaker.<br><br>Ao contrário do que se acredita, Lorem Ipsum não é simplesmente um texto randômico. Com mais de 2000 anos, suas raízes podem ser encontradas em uma obra de literatura latina clássica datada de 45 AC. Richard McClintock, um professor de latim do Hampden-Sydney College na Virginia, pesquisou uma das mais obscuras palavras em latim, consectetur, oriunda de uma passagem de Lorem Ipsum, e, procurando por entre citações da palavra na literatura clássica, descobriu a sua indubitável origem. Lorem Ipsum vem das seções 1.10.32 e 1.10.33 do \'de Finibus Bonorum et Malorum\' (Os Extremos do Bem e do Mal), de Cícero, escrito em 45 AC. Este livro é um tratado de teoria da ética muito popular na época da Renascença. A primeira linha de Lorem Ipsum, \'Lorem Ipsum dolor sit amet...\' vem de uma linha na seção 1.10.32.<br><br>O trecho padrão original de Lorem Ipsum, usado desde o século XVI, está reproduzido abaixo para os interessados. Seções 1.10.32 e 1.10.33 de \'de Finibus Bonorum et Malorum\' de Cicero também foram reproduzidas abaixo em sua forma exata original, acompanhada das versões para o inglês da tradução feita por H. Rackham em 1914.';
$articles = [];

$articles[] = new Article( 1,
'Flamengo tem novo artilheiro',
$content,
'http://imguol.com/c/esporte/39/2016/09/20/o-atacante-emerson-sheik-durante-treino-do-flamengo-1474412535965_615x300.jpg',
'Flamengo fez um investimento promissor no novo atacante de 18 anos',
1795,
45,
'5 min' );

$articles[] = new Article( 2,
'Corinthians na Europa',
$content,
'http://imguol.com/c/esporte/b1/2016/11/28/fabio-carille-conversa-com-oswaldo-de-oliveira-1480358216984_615x300.jpg',
'Corinthians está participando da copa Audi junto a outros grandes clubes da Europa',
2589,
122,
'3 min' );

$articles[] = new Article( 3,
'Artilheiro francês no Brasil',
$content,
'http://cdn.record.xl.pt/2016-12/img_770x433$2016_12_09_13_50_34_1195022.jpg',
'De férias em Manguinhos - ES. Zidane Pensa em comprar casa no balneário',
1688,
26,
'8 min' );

$articles[] = new Article( 4,
'Novo técnico no coxa',
$content,
'http://www.gazetadopovo.com.br/ra/grande/Pub/GP/p4/2016/12/10/Esportes/Imagens/Cortadas/treino%20coxa%20458%20albari%20rosa-8045-kmcE-U201285752750k9C-1024x683@GP-Web.jpg', 'Curitiba contratou um novo técnico para a temporada 2017 do brasileirão',
2130,
49,
'4 min' );

$articles[] = new Article( 5,
'Santos tenta negociação milionária',
$content,
'http://imguol.com/c/esporte/1f/2016/03/27/camarones-joel-do-santos-comemora-seu-gol-contra-o-sao-paulo-em-classico-na-vila-belmiro-pelo-campeonato-paulista-1459120201592_615x300.jpg', 'Santos tenta vender suas duas novas jóias por valores ainda não praticados no Brasil',
7559,
655,
'6 min' );

$articles[] = new Article( 6,
'Desportiva Ferroviária inicia trabalhos',
$content,
'http://s2.glbimg.com/PyquA5F-53Mx7K6x1yEnJYoCXzU=/322x0:1132x1080/300x400/s.glbimg.com/es/ge/f/original/2016/12/09/15387360_1367078876676185_200371169_o.jpg', 'A Desportiva Ferroviária iniciou os trabalhos para a temporada 2017 do Capixabão',
988,
6,
'2 min' );

return $articles;
}
}

Controlador

O controlador é ainda mais simples que os apresentados em trechos Web de projetos Android aqui do Blog, dessa vez já é retornada a lista de artigos em formato JSON sem uso de condicionais:

include '../domain/Article.php';
include '../apl/AplAdmin.php';
ini_set('display_errors', 0);

sleep(2);
$articles = AplAdmin::getArticles();
echo json_encode($articles);

 

Caso esteja você utilizando o mesmo projeto PHP apresentado aqui e desconfie que o problema atual que está enfrentando é nele (se tiver algum bug na execução em sua máquina), altere o método ini_set('display_errors', 0) para ini_set('display_errors', 1) e rode seu controlador direto do navegador.

Os erros, se existirem, serão apresentados na tela.

Assim, podemos prosseguir com a parte Android do projeto.

Projeto de exemplo - parte Android

Na parte Android do projeto já vou apresentar e comentar o código utilizando as anotações do AndroidAnnotations. Isso, pois a base do AndroidAnnotations já foi explicada na seção AndroidAnnotations, visão geral.

Porém, sempre que enxergada a necessidade, vou colocar o código original, o código completo antes do uso das anotações, para facilitar o entendimento.

Como já informado, o projeto é sobre um aplicativo de Blog de Futebol, o APP Futebol Blog: 

Para já acessar o projeto, parte Android, por completo, entre no GitHub a seguir: https://github.com/viniciusthiengo/aa-futebol-blog-android. Mesmo assim estaremos apresentando todo o código dele no conteúdo seguinte do artigo.

Note que para a construção dessa parte do projeto foi utilizado o Android Studio 2.2.2.

Se for seguir o exemplo do zero, como será apresentado aqui no artigo, crie um novo projeto Android com o nome Futebol Blog. De início escolha a activity que tenha o NavigationDrawer já configurado.

Configurações Gradle

As configurações para utilizar o AndroidAnnotations são simples, caso você siga elas aqui ou em qualquer outro artigo que as mostre em cada Gradle (Project e APP level), pois na documentação da library elas estão em apenas um grande arquivo, pouco intuitivo.

Segue código do Gradle Top Level ou build.gradle (Project: FutebolBlog):

buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}

repositories {
mavenCentral()
mavenLocal()
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

Os trechos de código destacados são referentes as partes alteradas / adicionadas ao estado padrão do Gradle Top Level no início de um projeto.

Independente da data que você acesse esse artigo, busque sempre utilizar as versões mais atuais das libraries e plugins referenciados em todo o projeto.

Abaixo a configuração do Gradle APP Level ou build.gradle (Module: app):

apply plugin: 'com.android.application'
apply plugin: 'android-apt'
def AAVersion = '4.2.0'

android {
compileSdkVersion 25
buildToolsVersion "24.0.3"
defaultConfig {
applicationId "br.com.thiengo.futebolblog"
minSdkVersion 10
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.1'
compile 'com.android.support:design:25.0.1'
testCompile 'junit:junit:4.12'
compile 'com.android.support:cardview-v7:25.0.1' /* CARDVIEW */
compile 'com.squareup.picasso:picasso:2.5.2' /* PICASSO SQUARE */

apt "org.androidannotations:androidannotations:$AAVersion"
compile "org.androidannotations:androidannotations-api:$AAVersion"
}

 

Note que os trechos destacados são referentes ao AndroidAnnotations, pois dessa vez, devido ao domínio do problema do projeto, estamos também referenciando outras libraries que não são iniciadas em um projeto padrão Android.

No caso, são as libraries referentes ao CardView e ao Picasso Square.

Acompanhando o projeto aqui do exemplo, você deve utilizar todas as configurações e não somente as destacadas.

Para finalizar, sincronize o projeto.

Configurações AndroidManifest

O código do AndroidManifest.xml já está com as devidas configurações para o funcionamento do AndroidAnnotations:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.futebolblog">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".MainActivity_"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".ContentActivity_"
android:label="@string/title_activity_content"
android:theme="@style/AppTheme.NoActionBar"/>

</application>
</manifest>

 

Ambas as activities com o "_" no rótulo, referenciando as activities que realmente serão utilizadas depois da compilação.

A permissão de INTERNET é para que seja possível acessar os dados do servidor Web e para que a library Picasso consiga carregar as imagens externas.

Domínio do problema

São três classes nesse pacote do sistema: Article, ArticlesAdapter e ArticleItemView.

Note que vou primeiro apresentar o código do domínio do problema, pois o restante do projeto precisa do entendimento desse pacote como pré-requisito.

Segue código de Article:

public class Article implements Parcelable {
public static final String URL = "http://seuIpLocal:portaDeSeuLocalhost/futebol-blog/package/ctrl/CtrlAdmin.php";
public static final String KEY = "article";

private long id;
private String title;
private String content;
private String imageUrl;
private String summary;
private int numViews;
private int numComments;
private String timeToRead;

public Article(){}

public Article( long id, String title, String content, String imageUrl, String summary, int numViews, int numComments, String timeToRead ){
this.id = id;
this.title = title;
this.content = content;
this.imageUrl = imageUrl;
this.summary = summary;
this.numViews = numViews;
this.numComments = numComments;
this.timeToRead = timeToRead;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public String getImageUrl() {
return imageUrl;
}

public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

public String getSummary() {
return summary;
}

public void setSummary(String summary) {
this.summary = summary;
}

public int getNumViews() {
return numViews;
}

public void setNumViews(int numViews) {
this.numViews = numViews;
}

public int getNumComments() {
return numComments;
}

public void setNumComments(int numComments) {
this.numComments = numComments;
}

public String getTimeToRead() {
return timeToRead;
}

public void setTimeToRead(String timeToRead) {
this.timeToRead = timeToRead;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(this.id);
dest.writeString(this.title);
dest.writeString(this.content);
dest.writeString(this.imageUrl);
dest.writeString(this.summary);
dest.writeInt(this.numViews);
dest.writeInt(this.numComments);
dest.writeString(this.timeToRead);
}

protected Article(Parcel in) {
this.id = in.readLong();
this.title = in.readString();
this.content = in.readString();
this.imageUrl = in.readString();
this.summary = in.readString();
this.numViews = in.readInt();
this.numComments = in.readInt();
this.timeToRead = in.readString();
}

public static final Creator<Article> CREATOR = new Creator<Article>() {
@Override
public Article createFromParcel(Parcel source) {
return new Article(source);
}

@Override
public Article[] newArray(int size) {
return new Article[size];
}
};

public void setFromJson(JSONObject json) throws JSONException {
this.id = json.getInt("id");
this.title = json.getString("title");
this.content = json.getString("content");
this.imageUrl = json.getString("imageUrl");
this.summary = json.getString("summary");
this.numViews = json.getInt("numViews");
this.numComments = json.getInt("numComments");
this.timeToRead = json.getString("timeToRead");
}
}

 

O código ficou um pouco longo, pois foi preciso deixar a classe como uma classe POJO (todos os getters e setters dos atributos, presentes) e também implementar o Parcelable, este último para permitir o envio de um objeto Article por meio de Intent, além de o Parcelable permitir o uso da lista de Articles no SaveInstanceState.

Caso não conheça o Parcelable, logo depois desse conteúdo acesse o artigo dele aqui no blog: Parcelable no Android, Entendendo e Utilizando.

O método setFromJson() é para facilitar o código que recebe os dados do backend Web, para que esse trecho de script não fique direto no código cliente da conexão remota. Aqui, o código da MainActivity.

Agora o código de ArticleItemView, uma View personalizada do projeto:

@EViewGroup(R.layout.article_item)
public class ArticleItemView extends CardView {
@ViewById(R.id.iv_thumb_article)
ImageView ivThumbArticle;
@ViewById(R.id.tv_title)
TextView tvTitle;
@ViewById(R.id.tv_summary)
TextView tvSummary;
@ViewById(R.id.tv_views_count)
TextView tvViewsCount;
@ViewById(R.id.tv_comments_count)
TextView tvCommentsCount;
@ViewById(R.id.tv_time_reading)
TextView tvTimeReading;

public ArticleItemView(Context context){
super(context);
}

public void bind( Article a ){
tvTitle.setText( a.getTitle() );
tvSummary.setText( a.getSummary() );
tvViewsCount.setText( String.valueOf( a.getNumViews() ) );
tvCommentsCount.setText( String.valueOf( a.getNumComments() ) );
tvTimeReading.setText( a.getTimeToRead() );

Picasso
.with(tvTitle.getContext())
.load( a.getImageUrl() )
.into(ivThumbArticle);
}
}

 

Como já explicado na seção AndroidAnnotations, visão geral, A anotação @ViewById injeta a referência da View na variável logo abaixo da anotação.

O extends CardView é porque o layout R.layout.article_item, que será utilizado por ArticleItemView, tem como View root um CardView. Criamos esse vinculo pela anotação @EViewGroup. Abaixo o código XML desse layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
card_view:cardCornerRadius="2dp">

<RelativeLayout
android:padding="8dp"
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_thumb_article"
android:layout_width="80dp"
android:layout_height="100dp"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:contentDescription="@null"
android:scaleType="centerCrop"
android:src="@null"/>

<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@+id/iv_thumb_article"
android:layout_toRightOf="@+id/iv_thumb_article"
android:ellipsize="end"
android:maxLines="2"
android:textSize="18sp" />

<TextView
android:layout_marginTop="12dp"
android:id="@+id/tv_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_toEndOf="@+id/iv_thumb_article"
android:layout_toRightOf="@+id/iv_thumb_article"
android:ellipsize="end"
android:maxLines="3" />

<ImageView
android:id="@+id/iv_thumb_views"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_below="@+id/tv_summary"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_marginTop="5dp"
android:layout_toEndOf="@+id/iv_thumb_article"
android:layout_toRightOf="@+id/iv_thumb_article"
android:contentDescription="Thumb número de visualizações"
android:src="@drawable/ic_views_count" />

<TextView
android:id="@+id/tv_views_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_toEndOf="@+id/iv_thumb_views"
android:layout_toRightOf="@+id/iv_thumb_views"
android:textSize="@dimen/icon_font_size" />

<ImageView
android:id="@+id/iv_thumb_comments"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_toEndOf="@+id/tv_views_count"
android:layout_toRightOf="@+id/tv_views_count"
android:contentDescription="Thumb número de comentários"
android:src="@drawable/ic_comments_count" />

<TextView
android:id="@+id/tv_comments_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_toEndOf="@+id/iv_thumb_comments"
android:layout_toRightOf="@+id/iv_thumb_comments"
android:textSize="@dimen/icon_font_size" />

<ImageView
android:id="@+id/iv_thumb_time_reading"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_toEndOf="@+id/tv_comments_count"
android:layout_toRightOf="@+id/tv_comments_count"
android:contentDescription="Thumb tempo da leitura"
android:src="@drawable/ic_time_reading" />

<TextView
android:id="@+id/tv_time_reading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_toEndOf="@+id/iv_thumb_time_reading"
android:layout_toRightOf="@+id/iv_thumb_time_reading"
android:textSize="@dimen/icon_font_size" />

</RelativeLayout>
</android.support.v7.widget.CardView>

 

Note que @EViewGroup é similar, em comportamento, as anotações @EBind e @EActivity já apresentadas no artigo. Ou seja, @EViewGroup também permite o uso de anotações dentro da classe além de sinalizar ao AndroidAnnotations para criar uma classe com o "_" no final do rótulo, aqui, ArticleItemView_.

O código do método bind() é bem simples. Coloca os dados de algum Article nas Views do layout de item, também utiliza o Picasso para carregar a imagem externa no ImageView.

O construtor de ArticleItemView é padrão de acordo com as configurações sendo utilizadas.

Note que devido ao uso de uma View personalizada, nossa View ArticleItemView, com injeção de Views pelo AndroidAnnotations, não é necessário o código de um ViewHolder em nossos adapters, digo, adapters que utilizam a ArticleItemView. Isso, pois ela já trabalha como um ViewHolder.

Assim, podemos prosseguir com o código de ArticlesAdapter. A classe que será vinculada ao ListView da MainActivity. Segue:

@EBean
public class ArticlesAdapter extends BaseAdapter {
private ArrayList<Article> articles;

@SystemService
protected LayoutInflater inflater;

@AfterInject
public void injectInFields(){
articles = ((MainActivity)inflater.getContext()).articles;
}

@Override
public int getCount() {
return articles.size();
}

@Override
public Object getItem(int i) {
return articles.get(i);
}

@Override
public long getItemId(int i) {
return articles.get(i).getId();
}

@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ArticleItemView articleItemView;

if( view == null ){
articleItemView = ArticleItemView_.build( inflater.getContext() );
}
else{
articleItemView = (ArticleItemView) view;
}

articleItemView.bind( articles.get( i ) );

return articleItemView;
}
}

 

Note o @EBean sendo utilizado. É a melhor anotação para essa classe, mesmo ela trabalhando com View e Context. Esta também terá uma classe ArticlesAdapter_ criada em tempo de compilação.

A anotação @SystemService garante que a variável inflater será preenchida com o tipo de recurso do sistema Android que é indicado pelo tipo da variável, LayoutInflater.

Essa anotação é utilizada para substituir, em tempo de desenvolvimento de projeto, o código que utiliza o método getSystemService().

Abaixo o exemplo de código substituído, em desenvolvimento, pela annotation @SystemService e que será colocado em tempo de compilação:

...
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
...

 

A anotação @AfterInject garante que o método somente será invocado depois que todos os atributos com anotações tenham sido inicializados.

Todos os outros métodos da classe são padrões e de implementação obrigatória, pois são abstratos da classe pai, BaseAdapter.

Somente note como acessamos, em getView(), uma nova instância de ArticleItemView. Nesse caso, utilizando a versão ArticleItemView_ com o método build().

Fique atento a esses detalhes quando for utilizar o AndroidAnnotations. Se a library continuar evoluindo é capaz que eles consigam mudar isso, mas até lá, a utilização desta é como apresentado.

Em alguns casos terá de referenciar as entidades que ainda serão criadas pela library, as com o sufixo "_".

Com isso finalizamos com as classes de domínio do problema, deixando nosso ArticlesAdapter com menos código que o normal e mesmo assim trabalhando com o padrão ViewHolder.

Atividade principal (MainActivity)

Com a MainActivity vamos iniciar pelo layout, pois esse não (e nenhum outro layout do projeto) passará por nenhuma modificação.

Segue código XML de activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:openDrawer="start">

<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />
</android.support.v4.widget.DrawerLayout>

 

Agora o código de nav_header_main.xml que é referenciado pelo NavigationView:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/nav_header_height"
android:background="@drawable/header"
android:gravity="bottom"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:theme="@style/ThemeOverlay.AppCompat.Dark">

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/nav_header_vertical_spacing"
android:text="thiengo.com.br"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

<TextView
android:id="@+id/textView"
android:textSize="18sp"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Futebol Blog Brasil" />
</LinearLayout>

 

Em seguida o código de app_bar_main.xml que, ainda no layout activity_main.xml, é referenciado pela tag <include>:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.futebolblog.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main" />

<ProgressBar
android:id="@+id/pb_load"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:visibility="gone" />

</android.support.design.widget.CoordinatorLayout>

 

Note os IDs. Esses serão referenciados pelas anotações em MainActivity, assim que começarmos com os códigos dela. Ainda falta o layout content_main.xml referenciado pela tag <include> em app_bar_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.futebolblog.MainActivity"
tools:showIn="@layout/app_bar_main"
android:background="@drawable/background">

<ListView
android:id="@+id/lv_articles"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@android:color/transparent"
android:dividerHeight="10dp"/>
</RelativeLayout>

 

Thiengo, mas esse código, digo, o ListView, poderia ter sido incluído diretamente no código do app_bar_main.xml. Não poderia?

Sim, você está certo. Devido a pressa e ao foco do exemplo (AndroidAnnotations) que era em outro conteúdo da APP, passei "batido". Mas sinta-se à vontade para aplicar essa melhoria.

Assim podemos prosseguir com o código Java da MainActivity. Vamos em partes, começando pelas declarações das instâncias:

@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {

@ViewById(R.id.lv_articles)
protected ListView listView;

@InstanceState
@NonConfigurationInstance
public ArrayList<Article> articles = new ArrayList<>();

@ViewById
protected Toolbar toolbar;

@ViewById(R.id.drawer_layout)
protected DrawerLayout drawer;

@ViewById(R.id.nav_view)
protected NavigationView navigationView;

@ViewById(R.id.pb_load)
protected ProgressBar progressBar;

@Bean
protected ArticlesAdapter articlesAdapter;
...
}

 

Assumindo que você chegou até esse ponto do artigo passando ao menos pela seção AndroidAnnotations, visão geral, as anotações acima dispensam comentários, pois já foram explicadas. De qualquer forma, estamos injetando referências de Views quando utilizando @ViewById.

Também injetando referencias a instâncias de domínio do problema quando utilizando @Bean.

E permitindo o uso dos métodos onSaveInstanceState()onRestoreInstanceState() a variáveis especificas quando utilizando, respectivamente, @InstanceState e @NonConfigurationInstance.

O código a seguir é do método viewsInitialized(), que garante que as variáveis de visualizações somente serão utilizadas depois de inicializadas. No caso, utilizando a anotação @AfterViews no método:

@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
...
@AfterViews
protected void viewsInitialized(){
setSupportActionBar(toolbar);

ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();

navigationView.setNavigationItemSelectedListener(this);

retrieveData();
listView.setAdapter( articlesAdapter );
}

@ItemClick(R.id.lv_articles)
public void itemClickedListener( Article article ){
ContentActivity_.intent(this).article( article ).start();
}
...
}

 

Também deixei o método de listener, itemClickedListener(), descrito no código anterior. A anotação utilizada nele evita o uso de códigos de classes e instâncias extras para vincular um listener de clique aos itens de um ListView.

O único parâmetro de entrada do método é o objeto retornado pelo método getItem() do adapter ArticlesAdapter, porém já convertido para o tipo de dados na coleção vinculada ao adapter, tipo Article.

A ContentActivity_ é uma outra activity que faz o uso do AndroidAnnotations. Na próxima seção vamos abordar essa activity.

Note que em viewsInitialized() podemos seguramente utilizar o código listView.setAdapter( articlesAdapter ) sem chances de articlesAdapter ser nulo.

Pois segundo a documentação do AndroidAnnotations, mais precisamente o informe na página @AfterXXX call order, quando o acesso a um método anotado com @AfterViews acontece, todos os outros campos, digo, os que não têm a referência @ViewById e sim outras anotações de injeção de dependência sendo utilizadas, todos eles já foram inicializados.

No caso do articlesAdapter a anotação é a @Bean.

Assim, podemos prosseguir com o restante do código. Segue:

@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
...
private void retrieveData(){
if( articles.size() == 0 ){
progressBarLoad(true);
workingInBackgroundThread();
}
}

private void progressBarLoad( boolean activated ){
progressBar.setVisibility( activated ? View.VISIBLE : View.GONE );
}

@Background
protected void workingInBackgroundThread(){
try {
String answer = serverRequest();

JSONArray jsonArray = new JSONArray( answer );
for( int i = 0, tam = jsonArray.length(); i < tam; i++ ){
JSONObject jsonObject = jsonArray.getJSONObject(i);
Article a = new Article();
a.setFromJson(jsonObject);

articles.add( a );
}
workingInUiThread();
}
catch(Exception e){}
}

@UiThread
protected void workingInUiThread(){
progressBarLoad(false);
articlesAdapter.notifyDataSetChanged();
}

private String serverRequest() throws IOException {
URL url = new URL( Article.URL );
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
BufferedReader r = new BufferedReader(new InputStreamReader(in));

StringBuilder jsonString = new StringBuilder();
String line;
while ((line = r.readLine()) != null) {
jsonString.append(line).append('\n');
}

urlConnection.disconnect();
return jsonString.toString();
}
...
}

 

A anotação @Background faz com que o método dela trabalhe em uma Thread de background, sendo assim é nesse trecho que utilizamos uma conexão remota com nosso servidor Web.

Logo depois, com o retorno e o parse JSON realizados, é invocado o método workInUiThread(), isso dentro do método workInBackgroundThread() que está executando em uma Thread secundária.

A anotação @UiThread permite que o conteúdo do método dela trabalhe na Thread principal. Essas duas anotações, em meu ponto de vista, são as que trouxeram maior ganho ao código original do projeto, utilizado anteriormente.

Segue o código anterior desse trecho de conexão remota e volta a Thread UI:

public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
...
private void retrieveData(){
if( articles.size() == 0 ){
progressBarLoad(true);
new RequestAsync(this).execute();
}
}

private static class RequestAsync extends AsyncTask<Void, Void, String> {
private WeakReference<MainActivity> activityWeak;

RequestAsync( MainActivity activity ){
activityWeak = new WeakReference<>(activity);
}

@Override
protected String doInBackground(Void... voids) {
try {
String answer = activityWeak.get().serverRequest();

JSONArray jsonArray = new JSONArray( answer );
for( int i = 0, tam = jsonArray.length(); i < tam; i++ ){
JSONObject jsonObject = jsonArray.getJSONObject(i);
Article a = new Article();
a.setFromJson(jsonObject);

activityWeak.get().articles.add( a );
}
}
catch(Exception e){}
return null;
}

@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);

if( activityWeak.get() != null ){
activityWeak.get().progressBarLoad(false);

ArticlesAdapter adapter = (ArticlesAdapter) activityWeak.get().listView.getAdapter();
adapter.notifyDataSetChanged();
}
}
}
...
}

 

Junto ao código acima coloque os métodos serverRequest() e progressBarLoad() que lhe poupei.

Assim, falta somente o código padrão de uso do NavigationDrawer e outras entidades, como o menu, por exemplo:

@EActivity(R.layout.activity_main)
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
...
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}

@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.nav_camera) {}
else if (id == R.id.nav_gallery) {}
else if (id == R.id.nav_slideshow) {}
else if (id == R.id.nav_manage) {}
else if (id == R.id.nav_share) {}
else if (id == R.id.nav_send) {}

drawer.closeDrawer(GravityCompat.START);
return true;
}
...
}

 

Apesar do código acima ter o uso de anotações, nenhuma delas é referente a library AndroidAnnotations, somente coloquei esse código aqui para você terminar a MainActivity.

O único efeito do AndroidAnnotations no código anterior é a remoção do método findViewById(), pois as variáveis todas já estão sendo preenchidas via @ViewById.

Com isso podemos passar a nossa última parte do projeto, a activity ContentActivity.

A outra activity, a ContentActivity

O código da ContentActivity é muito mais simples que o da MainActivity. E como para a activity anterior, vamos começar com o layout, pois esse é padrão e não sofrerá modificações devido ao uso do AndroidAnnotations.

Segue código de activity_content.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
android:fitsSystemWindows="true"
tools:context="br.com.thiengo.futebolblog.ContentActivity">

<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">

<ImageView
android:id="@+id/iv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:scaleType="centerCrop"
app:layout_collapseMode="parallax"/>

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_content" />

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_anchor="@id/app_bar"
app:layout_anchorGravity="bottom|end"
app:srcCompat="@drawable/ic_share" />
</android.support.design.widget.CoordinatorLayout>

 

E agora o código de content_content.xml, referenciado na tag <include> do layout anterior:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.NestedScrollView 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.thiengo.futebolblog.ContentActivity"
tools:showIn="@layout/activity_content">

<RelativeLayout
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">

<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginTop="15dp"
android:ellipsize="end"
android:maxLines="2"
android:textSize="28sp" />

<ImageView
android:id="@+id/iv_thumb_views"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_below="@+id/tv_title"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_marginTop="5dp"
android:contentDescription="Thumb número de visualizações"
android:src="@drawable/ic_views_count" />

<TextView
android:id="@+id/tv_views_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_toEndOf="@+id/iv_thumb_views"
android:layout_toRightOf="@+id/iv_thumb_views"
android:textSize="@dimen/icon_font_size" />

<ImageView
android:id="@+id/iv_thumb_comments"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_toEndOf="@+id/tv_views_count"
android:layout_toRightOf="@+id/tv_views_count"
android:contentDescription="Thumb número de comentários"
android:src="@drawable/ic_comments_count" />

<TextView
android:id="@+id/tv_comments_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:layout_toEndOf="@+id/iv_thumb_comments"
android:layout_toRightOf="@+id/iv_thumb_comments"
android:textSize="@dimen/icon_font_size" />

<ImageView
android:id="@+id/iv_thumb_time_reading"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_marginEnd="3dp"
android:layout_marginRight="3dp"
android:layout_toEndOf="@+id/tv_comments_count"
android:layout_toRightOf="@+id/tv_comments_count"
android:contentDescription="Thumb tempo da leitura"
android:src="@drawable/ic_time_reading" />

<TextView
android:id="@+id/tv_time_reading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/iv_thumb_views"
android:layout_toEndOf="@+id/iv_thumb_time_reading"
android:layout_toRightOf="@+id/iv_thumb_time_reading"
android:textSize="@dimen/icon_font_size" />

<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/tv_time_reading"
android:layout_marginTop="20dp"
android:textSize="18sp" />
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

 

Novamente, fique atento aos IDs.

Podemos prosseguir com o código Java de ContentActivity. Começando com as declarações de variáveis:

@EActivity(R.layout.activity_content)
public class ContentActivity extends AppCompatActivity {

@ViewById
FloatingActionButton fab;

@ViewById
Toolbar toolbar;

@ViewById
TextView tvTitle;

@ViewById
TextView tvViewsCount;

@ViewById
TextView tvCommentsCount;

@ViewById
TextView tvTimeReading;

@ViewById
TextView tvContent;

@ViewById(R.id.iv_header)
ImageView imageView;

@ViewById(R.id.toolbar_layout)
CollapsingToolbarLayout collapsingToolbarLayout;

@Extra
Article article;

...
}

 

A anotação @Extra permite que acessemos dados do Intent enviado a essa activity somente utilizando ela, a anotação.

É certo que em qualquer método com a anotação @AfterViews a variável referenciada pela annotation @Extra já terá sido inicializada (se houver o dado na Intent).

O nome da variável tem que corresponder ao nome do método que foi utilizado para colocar o dado na Intent. Em nosso caso corresponde.

Voltando ao trecho de código da MainActivity que aciona a ContentActivity:

...
@ItemClick(R.id.lv_articles)
public void itemClickedListener( Article article ){
ContentActivity_.intent(this).article( article ).start();
}
...

 

Assim, vamos ao restante do código, digo, o código que ainda utiliza anotações do AndroidAnnotations:

@EActivity(R.layout.activity_content)
public class ContentActivity extends AppCompatActivity {
...
@AfterViews
protected void init(){
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);

fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Compartilhar conteúdo", Snackbar.LENGTH_LONG)
.setAction("Compartilhar", null).show();
}
});

if( article == null ){
return;
}

collapsingToolbarLayout.setTitle( article.getTitle() );

Picasso
.with(this)
.load( article.getImageUrl() )
.into(imageView);

tvTitle.setText( article.getTitle() );
tvViewsCount.setText( String.valueOf( article.getNumViews() ) );
tvCommentsCount.setText( String.valueOf( article.getNumComments() ) );
tvTimeReading.setText( String.valueOf( article.getTimeToRead() ) );
tvContent.setText( String.valueOf( Html.fromHtml(article.getContent() ) ) );
}
...
}

 

Nada de mais no método acima. Somente para mostrar onde as variáveis de visualizações são realmente acessadas assim que todas elas, incluindo a variável do dado da Intent, são inicializadas.

O código que falta para terminar com o algoritmo de ContentActivity é o do método onOptionsItemSelected(), que não utiliza o AndroidAnnotations. Segue:

@EActivity(R.layout.activity_content)
public class ContentActivity extends AppCompatActivity {
...
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

 

Com isso podemos rodar os testes, verificar se a aplicação ainda funciona.

Testes e resultados

Executando a aplicação em um emulador do AVD, com Google APIs, temos os seguintes resultados.

Aplicação abrindo, carregando dados do backend Web:

Assim que os dados são carregados:

Testando o NavigationDrawer:

Rotacionando a tela para certificar-se de que não haverá recarregamento de conteúdo:

Não há recarregamento. @InstanceState@NonConfigurationInstance funcionando sem problemas.

E por último, a abertura de um artigo na ContentActivity_:

Com isso finalizamos o projeto de exemplo, com o AndroidAnnotations sendo utilizado. Estudando a documentação dessa library você consegue aplicar ainda mais melhorias, conseguindo remover mais boilerplate code.

Vídeo com implementação passo a passo do exemplo

No vídeo a seguir é apresentado o passo a passo da implementação do AndroidAnnotations no aplicativo de Futebol Blog do projeto de exemplo:

Para acessar a versão Android do Projeto, entre no GitHub a seguir: https://github.com/viniciusthiengo/aa-futebol-blog-android.

Para acessar a versão Web, entre no seguinte GitHub: https://github.com/viniciusthiengo/aa-futebol-blog-web.

Conclusão

Utilizar o AndroidAnnotations vai, provavelmente, melhorar seu código para posterior manutenção. Para um aproveitamento eficiente da library, recomendo que você destrinche a documentação dela.

Algo que é bom ficar alerta é a dependência que você pode criar do AndroidAnnotations, ou seja, você somente utiliza-lo a ponto de não mais saber o básico de criação de código de uma Thread de background, por exemplo.

Digo isso, pois a algum tempo relataram a mim problemas com o EventBus na versão mais atual do Android na época do contato.

A library não era compatível ainda. Consequência? Não era possível enviar a atualização da APP antes da correção do código, removendo o EventBus, ou a atualização do EventBus, algo completamente fora do alcance do usuário da library.

Uma dica: caso perceba que sua implementação nativa tem melhor performance do que a injetada pelo AndroidAnnotations, utilize a sua, a performance, para o usuário final, é mais importante do que como o código está dividido.

Se estiver com o tempo para estudo limitado, recomendo que estude o Kotlin e Programação Funcional ou o React Native. A princípio, parecem ser tecnologias mais promissores que o AndroidAnnotations.

Fontes

Página do AndroidAnnotations

Como recriar uma atividade

Vlw.

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Relacionado

Estratégia de Anúncios com In Loco Media no AndroidEstratégia de Anúncios com In Loco Media no AndroidAndroid
Lint Tool Para Alta Performance em APPs AndroidLint Tool Para Alta Performance em APPs AndroidAndroid
GCMNetworkManager Para Execução de Tarefas no Background AndroidGCMNetworkManager Para Execução de Tarefas no Background AndroidAndroid
Estudando Android - Lista de Conteúdos do BlogEstudando Android - Lista de Conteúdos do BlogAndroid

Compartilhar

Comentários Facebook (8)

Comentários Blog (2)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
beltrao.rudah (1) (0)
09/01/2017
como sempre um otimo tutorial
Responder
Bhagwat (1) (0)
18/12/2016
As always great tutorial.
Responder