Introdução aos Componentes da Arquitetura Android
() translation by (you can also view the original English article)
O Android foi introduzido ao mundo em 2005, e durante esses 12 anos de existência, a plataforma alcançou surpreendente sucesso, tornando-se o sistema operacional móvel mais instalado. Durante esse tempo, foram lançadas 14 versões diferentes do sistema operacional, com o Android sempre se tornando mais maduro. No entanto, uma área muito importante da plataforma continuou a ser ignorada: um padrão de arquitetura, capaz de lidar com as peculiaridades de plataforma e simples o bastante para ser compreendido e adotado pelo desenvolvedor médio.
Bem, antes tarde do que nunca. No último Google I/O, a equipe Android finalmente decidiu resolver este problema e responder às reações dos desenvolvedores em todo o mundo, anunciando uma recomendação oficial para uma Arquitetura de Aplicativos Android e fornecendo os elementos para implementá-la: os novos Componentes de Arquitetura. E melhor ainda, eles conseguiram fazê-lo sem comprometer a abertura do sistema que nós conhecemos e amamos.



Neste tutorial, exploraremos a arquitetura padronizada proposta pela equipe do Android no Google I/O e olharemos para os elementos principais dos novos componentes de arquitetura: LifeCycle, ViewModel, LifeData e Room. Não daremos muita atenção para o código, em vez disso, concentraremo-nos sobre o conceito e a lógica por trás desses temas. Nós também vamos dar uma olhada em alguns trechos de código, todos eles escritos usando Kotlin, uma linguagem incrível que agora é oficialmente suportada pelo Android.
1. O Que Estava Faltando ao Android?
Se você está apenas começando sua jornada como um desenvolvedor, é possível que você não saiba exatamente do que estou falando. Afinal, a arquitetura de aplicativos pode ser um tema obscuro no início. Mas acredite, você vai aprender a sua importância em breve! Conforme um aplicativo cresce e se torna mais complexo, sua arquitetura se tornará cada vez mais importante. Literalmente pode fazer seu trabalho uma felicidade ou um inferno.
Arquitetura de Aplicativos
De modo grosseiro, uma arquitetura de aplicativo é um plano consistente, que precisa ser feito antes de iniciado o processo de desenvolvimento. Este plano fornece um mapa de como os diferentes componentes do aplicativo devem ser organizados e interligados. Apresenta as diretrizes que devem ser seguidas durante o processo de desenvolvimento e forçar alguns sacrifícios (geralmente relacionados com mais classes e código padronizado) que no final vão ajudar você a construir um aplicativo bem escrito que é mais testável, expansível e de fácil manutenção.
Arquitetura de aplicativo de software é o processo de definição de uma solução estruturada que atende todos os requisitos técnicos e operacionais, enquanto otimiza os atributos de qualidade comuns, tais como desempenho, segurança e gerenciabilidade. Envolve uma série de decisões com base em uma ampla gama de fatores, e cada uma dessas decisões pode ter impacto considerável sobre a qualidade, desempenho, facilidade de manutenção e sucesso geral do aplicativo.
— Guia de Design e Arquitetura de Software da Microsoft
Boa arquitetura leva muitos fatores em consideração, especialmente as características e limites do sistema. Existem muitas soluções arquitetônicas diferentes por aí, todas elas com prós e contras. No entanto, alguns conceitos-chave são comuns entre todas as visões.
Velhos Erros
Até o último Google I/O, o sistema Android não recomendava qualquer arquitetura específica para desenvolvimento de aplicativos. Isso significa que você estava completamente livre para adotar qualquer modelo lá fora: MVP, MVC, MVPP, ou mesmo nenhum padrão de qualquer modo. Além disso, o framework do Android nem sequer fornecia soluções nativas para problemas criados pelo próprio sistema, especificamente do ciclo de vida dos componentes.



Então, se você quisesse adotar um padrão Model View Presenter em seu aplicativo, você precisava encontrar sua própria solução do zero, escrevendo um monte de código estereotipado, ou adotar uma biblioteca sem suporte oficial. E essa ausência de padrões criou um monte de aplicativos mal-escritos, com bases de código que eram difíceis de manter e testar.
Como eu disse, esta situação tem sido criticada por anos. Na verdade, eu escrevi recentemente sobre este problema e como solucioná-lo na minha série como adotar Model View Presenter no Android. Mas o importante é que após 12 longos anos, a equipe Android finalmente decidiu ouvir nossas queixas e nos ajudar com este problema.
2. Arquitetura Android
O novo Guia de Arquitetura Android define alguns princípios-chave que uma boa aplicação Android deve obedecer e também propõe um caminho seguro para o desenvolvedor criar um bom app. No entanto, o guia afirma explicitamente que a rota apresentada não é obrigatória, e, finalmente, a decisão é pessoal; é o desenvolvedor que deve decidir qual o tipo de arquitetura para adotar.
De acordo com o guia, uma boa aplicação Android deverá proporcionar uma sólida separação de conceitos e determinar a interface do usuário de um modelo. Qualquer código que não manipule uma interação da interface do usuário ou sistema operacional não deve estar em uma atividade ou fragmento, porque mantê-los o mais limpo possível permitirá evitar muitos problemas relacionados com o ciclo de vida. Afinal de contas, o sistema pode destruir atividades ou fragmentos a qualquer momento. Além disso, os dados devem ser manipulados por modelos que são isolados da interface do usuário, e, consequentemente, de questões de ciclo de vida.
A Nova Arquitetura Recomendada
A arquitetura que a Android recomenda não pode ser rotulada facilmente entre os padrões que nós conhecemos. Parece um padrão Model View Controller, mas está tão intimamente ligado à arquitetura do sistema que é difícil de rotular cada elemento usando as convenções conhecidas. Isto não é relevante, no entanto, como o mais importante é que ele vale-se dos novos componentes de arquitetura para criar uma separação de conceitos, com facilidade de manutenção e excelente capacidade de teste. E melhor ainda, é fácil de implementar.
Para entender o que a equipe Android proõe, temos de conhecer todos os elementos dos Componentes da Arquitetura, uma vez que são eles que farão o trabalho pesado para nós. Existem quatro componentes, cada um com uma função específica: Room, ViewModel, LiveData e Lifecycle. Todas essas partes têm suas próprias responsabilidades, e eles trabalham juntos para criar uma arquitetura sólida. Vamos dar uma olhada em um diagrama simplificado da arquitetura proposta para entendê-la melhor.



Como você pode ver, temos três elementos principais, cada um com sua responsabilidade.
- A Activity e o Fragment representam a camada View, que não lida com a lógica de negócios e operações complexas. Apenas configura o modo de exibição, lida com a interação do usuário e o mais importante, observa e apresenta elementos de LiveData retirados do ViewModel.
- O ViewModel automaticamente observa o estado do Lifecycle do modo de exibição, mantendo a consistência durante as alterações de configuração e outros eventos de ciclo de vida do Android. Isso também é exigido pelo modo de exibição para buscar dados do Repository, que é fornecido como LiveData observável. É importante compreender que o ViewModel nunca faz referência a View diretamente e que as atualizações dos dados são sempre feitas pela entidade LiveData.
- O Repository não é um componente especial do Android. É uma classe simples, sem qualquer implementação específica, que é responsável pela busca de dados de todas as fontes disponíveis, de um banco de dados para serviços web. Ele lida com todos esses dados, geralmente transformando-os para observáveis LiveData e tornando-os disponíveis para o ViewModel.
- O banco de dados Room é uma biblioteca de mapeamento de SQLite que facilita o processo de lidar com um banco de dados. Ele grava automaticamente uma tonelada de código estereotipado, verifica erros em tempo de compilação e o melhor de tudo, pode retornar diretamente consultas com LiveData observável.
Tenho certeza que você percebeu que nós conversamos muito sobre observáveis. O padrão Observer é uma das bases do elemento LiveData e componentes Lifecycle. Este padrão permite um objeto notificar a uma lista de observadores sobre qualquer alteração em seu estado ou dados. Então quando uma atividade observa uma entidade LiveData, ela receberá atualizações quando dados sofre qualquer tipo de modificação.
Uma outra recomendação Android é consolidar sua arquitetura usando um sistema de injeção de dependência, como Dagger 2 do Google ou usando o padrão Service Locator (que é muito mais simples do que o DI, mas sem muitas das suas vantagens). Não cobrimos DI ou Service Locator neste tutorial, mas Envato Tuts + tem alguns excelentes tutoriais sobre esses temas. No entanto, alertamos que existem algumas particularidades de trabalhar com Dagger 2 e Android Components que serão explicadas na segunda parte desta série.
3. Componentes de Arquitetura
Nós deve mergulhar profundamente nos aspectos dos novos componentes para sermos capazes de realmente entender e adotar este modelo de arquitetura. No entanto, não teremos todos os detalhes neste tutorial. Devido à complexidade de cada elemento, neste tutorial, vamos apenas falar sobre a ideia geral por trás de cada um e veremos alguns trechos de código simplificado. Vamos tentar cobrir terreno suficiente para apresentar os componentes e começar. Mas não temam, porque futuros artigos desta série vão cavar fundo e cobrir todas as particularidades dos Componentes de Arquitetura.
Componentes do Ciclo de Vida
A maioria dos componentes de apps Android têm ciclos de vida ligados a eles, que são gerenciados diretamente pelo próprio sistema. Até recentemente, coube ao desenvolvedor monitorar os componentes e agir em conformidade, inicializando e terminando as tarefas no momento oportuno. No entanto, era muito fácil ficar confuso e cometer erros relacionados a este tipo de operação. Mas o pacote android.arch.lifecycle mudou tudo isso.
Agora, Atividades e Fragmentos têm um objeto Lifecycle anexado a eles que pode ser observado pelas classes LifecycleObserver, como um ViewModel ou qualquer objeto que implementa essa interface. Isso significa que o observador irá receber atualizações sobre as mudanças de estado do objeto que ele está observando, como quando uma atividade está em pausa ou quando está iniciando. Ele também pode verificar o estado atual do objeto observado. Então é mais fácil agora para lidar com as operações que devem considerar os ciclos de vida do framework.



Por agora, para criar uma atividade ou fragmento que adequa-se a este novo padrão, você tem que estender um LifecycleActivity ou LifecycleFragment. No entanto, é possível que isto não seja sempre necessário, desde que a equipe do Android tem como objetivo integrar completamente estas novas ferramentas com o seu framework.
1 |
class MainActivity : LifecycleActivity() { |
2 |
|
3 |
override fun onCreate(savedInstanceState: Bundle?) { |
4 |
super.onCreate(savedInstanceState) |
5 |
setContentView(R.layout.activity_main) |
6 |
} |
7 |
} |
O LifecycleObserver recebe eventos do Lifecycle e pode reagir através de anotações. Nenhum método de substituição é necessário.
1 |
class MainActivityObserver : LifecycleObserver, AnkoLogger { |
2 |
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) |
3 |
fun onResume() { |
4 |
info("onResume") |
5 |
} |
6 |
|
7 |
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) |
8 |
fun onPause() { |
9 |
info("onPause") |
10 |
} |
11 |
} |
O Componente LiveData
O componente LiveData é um suporte de dados que contém um valor que pode ser observado. Dado que o observador tenha proporcionado um Lifecycle durante a instanciação do LiveData, o LiveData irá se comportar de acordo com o estado do Lifecycle. Se o estado de Lifecycle do observador é STARTED ou RESUMED, o observador está ativo; caso contrário, está inativo.
O LiveData percebe quando os dados foram alterados e também se o observador está ativo e deve receber uma atualização. Outra característica interessante da LiveData é que é capaz de remover o observador se estiver em um estado de Lifecycle.State.DESTROYED, evitando vazamentos de memória quando observado por Activities e Fragments.



Um LiveData deve implementar os métodos onActive e onInactive.
1 |
class LocationLiveData(context: Context) |
2 |
: LiveData<Location>(), AnkoLogger, LocationListener { |
3 |
private val locationManager: LocationManager = |
4 |
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager |
5 |
|
6 |
override fun onActive() { |
7 |
info("onActive") |
8 |
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, this) |
9 |
} |
10 |
|
11 |
override fun onInactive() { |
12 |
info("onInactive") |
13 |
locationManager.removeUpdates(this) |
14 |
} |
15 |
// .... |
16 |
} |
Para observar um componente LiveData, você deve chamar observer (LifecycleOwner, Observer<T>).
1 |
class MainActivity : LifecycleActivity(), AnkoLogger { |
2 |
fun observeLocation() { |
3 |
val location = LocationLiveData(this) |
4 |
location.observe(this, |
5 |
Observer { location -> |
6 |
info("location: $location") |
7 |
}) |
8 |
} |
9 |
} |
O Componente ViewModel
Uma das mais importantes classes dos novos Componentes de Arquitetura é o ViewModel, que é projetado para armazenar dados que estão relacionados com a interface do usuário, mantendo a sua integridade durante alterações na configuração como rotações de tela. O ViewModel é capaz de conversar com o Repository, obtendo o LiveData dele e tornando-o disponível para ser observado pela view. O ViewModel também não precisará fazer novas chamadas para o Repository após as alterações de configuração, o que otimiza muito o código.



Para criar um modelo de exibição, estenda a classe ViewModel.
1 |
class MainActivityViewModel : ViewModel() { |
2 |
|
3 |
private var notes: MutableLiveData<List<String>>? = null |
4 |
fun getNotes(): LiveData<List<String>> { |
5 |
if (notes == null) { |
6 |
notes = MutableLiveData<List<String>>() |
7 |
loadNotes() |
8 |
} |
9 |
return notes!! |
10 |
} |
11 |
private fun loadNotes() { |
12 |
// do async operation to fetch notes |
13 |
} |
14 |
} |
Para acessar de uma visão, você pode chamar ViewProviders.of(Activity| Fragment).get(ViewModel::Class). Este método de fábrica irá retornar uma nova instância de ViewModel ou obterá a retida, conforme apropriado.
1 |
class MainActivity : LifecycleActivity(), AnkoLogger { |
2 |
override fun onCreate(savedInstanceState: Bundle?) { |
3 |
super.onCreate(savedInstanceState) |
4 |
setContentView(R.layout.activity_main) |
5 |
|
6 |
val viewModel = ViewModelProviders.of(this) |
7 |
.get(MainActivityViewModel::class.java) |
8 |
viewModel.getNotes().observe( |
9 |
this, Observer { |
10 |
notes -> info("notes: $notes") |
11 |
} |
12 |
) |
13 |
} |
14 |
} |
O Componente Room
O Android suportava SQLite desde o início; no entanto, para que funcionasse, era necessário escrever muito código clichê. Além disso, o SQLite não salvava POJOs (plain-old Java objects) e não verificava consultas em tempo de compilação. Chega o Room para resolver estas questões! Ele é uma biblioteca de mapeamento de SQLite, capaz de persistir Java POJOs, convertendo diretamente consultas para objetos, verificação de erros em tempo de compilação e produzindo LiveData observáveis dos resultados da consulta. Quarto é uma biblioteca de Mapeamento Relacional de Objetos com alguns extras legais do Android.
Até agora, você poderia fazer a maior parte do que o Room é capaz usando outras bibliotecas ORM Android. No entanto, nenhuma delas é oficialmente suportada e, mais importante, elas não podem produzir resultados LifeData. A biblioteca Room se encaixa perfeitamente como a camada persistente proposta na Arquitetura Android.
Para criar um banco de dados Room, você vai precisar de uma @Entity para persistir, que pode ser qualquer POJO Java, uma interface @Dao para fazer consultas e operações de entrada/saída e uma classe abstrata @Database que deverá estender RoomDatabase.
1 |
@Entity |
2 |
class Note { |
3 |
@PrimaryKey |
4 |
var id: Long? = null |
5 |
var text: String? = null |
6 |
var date: Long? = null |
7 |
} |
1 |
@Dao |
2 |
interface NoteDAO { |
3 |
@Insert( onConflict = OnConflictStrategy.REPLACE ) |
4 |
fun insertNote(note: Note): Long |
5 |
|
6 |
@Update( onConflict = OnConflictStrategy.REPLACE ) |
7 |
fun updateNote(note: Note): Int |
8 |
|
9 |
@Delete |
10 |
fun deleteNote(note: Note): Int |
11 |
|
12 |
@Query("SELECT * FROM note") |
13 |
fun findAllNotes(): LiveData<Note> |
14 |
|
15 |
// on Kotlin the query arguments are renamed |
16 |
// to arg[N], being N the argument number. |
17 |
// on Java the arguments assume its original name |
18 |
@Query("SELECT * FROM note WHERE id = :arg0") |
19 |
fun findNoteById(id: Long): LiveData<Note> |
20 |
} |
1 |
@Database( entities = arrayOf(Note::class), version = 1) |
2 |
abstract class Databse : RoomDatabase() { |
3 |
abstract fun noteDAO(): NoteDAO |
4 |
} |
Adicionando Componentes de Arquitetura em Seu Projeto
Por enquanto, para usar os novos Componentes de Arquitetura, você precisará primeiro adicionar o repositório do Google ao seu arquivo build.gradle. Para mais detalhes, consulte o guia oficial.
1 |
allprojects { |
2 |
repositories { |
3 |
jcenter() |
4 |
// Add Google repository |
5 |
maven { url 'https://maven.google.com' } |
6 |
} |
7 |
} |
Conclusão
Como você pode ver, a arquitetura padronizada proposta pela Android envolve uma série de conceitos. Não espere ter uma compreensão completa deste tópico ainda. Afinal de contas, estamos apenas introduzindo o tema. Mas você certamente tem conhecimento suficiente agora para entender a lógica por trás da arquitetura e as funções dos diferentes Componentes de Arquitetura.
Nós conversamos sobre a maioria dos temas relacionados com a arquitetura Android proposta e seus componentes; no entanto, detalhes sobre a implementação de Components e alguns extras, como a classe de Repository e o sistema Dagger 2 não puderam ser cobertos por esta primeira parte. Exploraremos os temas nos próximos posts.
Te vejo em breve!