Modularização Android Parte 3

MVVM, Koin, Rx, Room, Databinding e um pouco mais…

Nos dois primeiros artigos, falamos dos módulos domain e data. Caso tenha perdido algum deles, seguem os links:

Presentation Module:

Finalmente chegamos no último module do nosso projeto, o qual, para os nossos usuários, é o mais importante, pois é aqui que vamos apresentar nossos dados no app.

Conteúdo da presentation:

View: são nossas Acitivties/Fragments, onde vamos apresentar nossos dados.

ViewModel: é onde vamos gerenciar os dados relacionados às nossas Views, ou seja, chamar nosso repositório para consumir dados ou observar dados, por exemplo.

Diagrama de fluxo do módulo presentation:

A nossa view solicita nossa lista de jobs, para o ViewModel, que utiliza o repository para buscar os dados para serem apresentados.

Lá no início, quando criamos nosso projeto, o module app foi criado, e é ele que vamos utilizar como nosso presentantion module.

Vamos começar a desenvolver o presentation module:

A primeira coisa que vamos fazer é adicionar as dependências necessárias ao nosso arquivo dependencies.gradle para o modulo presentation.

Vamos adicionar o Koin ViewModels, para injetar os nossos ViewModels, e também Databindind e ViewModel. Em geral, o restante a fazer são coisas comuns no desenvolvimento, mas, caso tenha alguma dúvida, me coloco à disposição para tentar saná-la.

E agora, vamos chamar nossas libs no gradle do módulo presentation:

Para utilizar o Databindig, precisamos acrescentar:

dataBinding { enable true }

O nosso módulo precisa dos outros módulos para funcionar. Por isso, implementamo-los aqui.

Adicionei uma nova variável chamada testDependencies, que serve somente para organizar as dependências de test. Mas não estamos implementando testes nesse tutorial.

Aqui, vamos falar de cada package e sobre o que cada um representa e seus respectivos conteúdos:

Extensions: onde armazenamos nossas extensions, que podemos utilizar no nosso module, e, nesse caso, temos 3 classes.

  • Context extensions: um conjunto de extensions relacionados ao context do Android. Mas aqui temos apenas uma, que é para mostrar um toast, e, para utilizá-la dentro de uma activity, por exemplo, precisamos apenas chamar toast(MENSAGEM). Caso queira adicionar mais alguma extension relacionada ao context, é necessário somente adicionar o método dentro dessa classe (o mesmo serve para as duas seguintes)
  • View extensions: é um conjunto de extensions relacionados a views. Aqui, temos a visible, que serve para mudar a visibilidade de alguma view. E, para utilizá-la, bastar chamar por exemplo txtName.visible(true).
  • ViewGroup extensions: um conjunto de extensions relacionados a ViewGroup. Aqui, temos um inflate, que é extremamente útil para utilizar em adapters, como, por exemplo, parent.inflate(R.layout.item_android_job), que está sendo usado no nosso AndroidJobsAdapter (mais para frente poderão checar).

  • Viewmodel: aqui temos o nosso BaseViewModel, que todos as nossas classes do tipo viewmodel irão estender. E também temos nossa StateMachine, que vai gerenciar os estados das nossas chamadas do repository dentro do viewmodel.
  • BaseViewModel: simplesmente estende a classe ViewModel e também é onde temos um disposables, que é um gerenciador do ciclo de vida dos Observables, os quais são as nossas chamadas do useCase (estes são do tipo Single<T>, mas poderiam ser Observables<T> ).

No método onCleared(), que é chamado quando o ViewModel morre, limpamos todos os Obsevables armazenados no nosso disposables. É importante fazer isso porque, se não dermos um fim nos observables, eles podem ficar rodando e ocasionar algum leak de memória. E usamos o CompositeDisposable(), que nada mais é do que um container de disposables.

  • ViewState: um gerenciador de estados, para que possamos mostrar o estado certo na view, de acordo com o que for emitido.

Primeiro temos os 3 estados possíveis:

  • Loading: esse estado é emitido no .doOnSubscribe, pois queremos mostrar o loader assim que nossa stream começar. Nesse estado não se precisa de nenhum dado para ser emitido, pois na criação usamos ViewState<Nothing>.
  • Success: esse estado é emitido no .map, que aplica algo específico no item emitido. Nesse caso, emite o Success juntamente com o dado da stream (nossa lista de jobs)
  • Failed: quando acontecer algo de errado na nossa stream, vai ser emitido o estado Failed no onErrorReturn, junto com o throwable(erro) emitido.

StateMachineSingle: vamos aplicá-la nas nossas chamadas do tipo Single, do repository, para nos emitir os estados que mencionamos acima.

Como já foi explicada cada parte da stream, vamos seguir em frente. Mais adiante vamos entender melhor o funcionamento, mas, por enquanto, segue um vídeo interessante que explica detalhadamente o que fizemos:

Feature:

Utilizamos essa nomenclatura para cada feature, e, no nosso caso, temos duas features/pacotes: list e main.

  • Main: esta tela foi criada apenas para termos uma navegação até a nossa lista, mas vamos ver como ela funciona:

MainViewModel: aqui manipulamos os dados que vamos utilizar na nossa view.

Como podemos ver, temos 2 liveDatas, das quais estamos mudando os valores nos métodos onShowAndroidJobsRequire e onOutAppRequire, que estão sendo chamados no xml, e, para isso ser possível, usamos databinding.

Esse é o layout da nossa view. E agora vamos explicar como vinculamos ele ao nosso ViewModel com DataBinding.

Para podermos utilizar o nosso MainViewModel no nosso layout, primeiro precisamos colocar todo nosso layout dentro do bloco layout; em seguida, criamos um bloco data, onde vamos colocar nossa variável, que pode interagir dentro do xml (que, nesse caso, é o nosso viewmodel), e setamos o name, e depois o type, que na verdade é a referencia da classe que vamos utilizar.

Em seguida já podemos utilizar o viewmodel.onShowAndroidJobsRequire e viewmodel.onOutAppLiveData, nos onClicks do Button e textView.

E, toda vez que clicarmos nessas views, o método vinculado no onClick será invocado no viewmodel.


MainActivity: aqui está nossa view, que utiliza o MainViewModel e o layout acima:

Inicialmente, temos o ViewModel injetado na view e para isso estamos utilizando o Koin, pois com o simples by viewModel() temos a referência do viewmodel.

Como estamos usando DataBinding, precisamos vinculá-lo à nossa View. Para isso, criamos uma variável lateinit var databinding, pois garantimos que ele sempre irá ser inicializado. No método onCreate, primeiro setamos o layout da activity no DataBinding para vincular as referências do layout; depois setamos o ViewModel no DataBinding; e, por fim, setamos o lifecycle owner ao databindng para que possamos escutar as mudanças dos LiveDatas do viewmodel.

Agora fazemos o setup dos observers dos LiveDatas do nosso ViewModel. E aqui vai uma coisa interessante que o Koin faz para nós, pois, normalmente, temos que fazer o seguinte para inicializar o ViewModel:

ViewModelProviders.of(this).get(MyViewModel::class.java)

Mas, utilizando o Koin, ele faz isso para nós, e não precisamos mais repetir tal procedimento em toda a nova View que vamos criar para inicializar nossos Viewmodels.

E, por fim mas não menos importante, vamos observar os eventos dos nossos LiveDatas, que são acionados nos clicks de cada botão da view. Os dois esperam receber um true para fazer alguma coisa: showAndroidJobsLiveData vai mudar para AndroidJobListActivity e o outLiveData simplesmente fecha o app. O fluxo onde essas ações acontecem é o seguinte:

button Click — > onShowAndroidJobsRequire() — > showAndroidJobsLiveData.postValue(true) — > startActivity

  • List: bom, tudo que fizemos até aqui nos últimos artigos e no conteúdo acima é para que possamos finalmente mostrar nossa lista de android jobs. Depois de vermos bastante conteúdo, agora vamos ver como tudo fez sentido.

AndroidJobListViewModel: Lembra do useCase que criamos la no primeiro artigo. Então, aqui está ele, no construtor do viewmodel, e também temos o uiScheduler, que será onde vamos observar o resultado do nosso useCase.

Primeiro temos o LiveData da nossa StateMachine, e inicialmente o nosso estado será Loading.

O primeiro método getJobs é o local onde chamamos o useCase para nos fornecer a lista de jobs que estamos buscando. O simples useCase.execute, executa tudo o que já foi criado nos dois artigos anteriores. Como podemos ver, foi adicionado um compose com o StateMachineSingle(), que vai aplicar alguma função de transformação, que no nosso caso será emitir os StateMachines na nossa chamada do useCase(cmd + f ViewState, em caso de duvida). Depois adicionamos o nosso uiScheduler, no observerOn, ou seja, vamos observar na ui thread. Por fim, esperamos o resultado final no subscribe e, assim que o recebermos, setamos seu valor no nosso LiveData. A segunda chave está vazia, pois na StateMachineSingle já retornamos o estado de erro. E para quem não sabe quais são os estados das duas funções abertas no subscribe, a primeira é onSuccess, e a segunda é onError.

Antes de irmos para a view “final”, vamos mostrar rapidamente o nosso adapter.

AndroidJobsAdapter: simplesmente nosso adapter, onde setamos cada job da nossa lista na view.

Não vamos aprofundar como funciona um adapter, pois provavelmente você já fez alguma lista nessa vida (caso não tenha feito, segue a doc).

Um coisa relevante aqui é que usamos a extension inflate para inflar o layout. E, por fim, setamos cada item do layout com nossas infos da entidade androidJob da lista.

AndroidJobsListActivity: temos, enfim, nossa tela que vai mostrar a nossa lista de jobs.

Primeiro, injetamos o viewmodel e nosso adapter (próximo tópico vou mostrar onde estão sendo providas essas injeções).

Temos um método lauchIntent, para que outras views possam inicializar essa utilizando esse método, como fizemos na view anterior.

Setamos o databinding, como fizemos na tela anterior.

Temos uma actionbar e setamos ela no método setupView, inclusive a ação de back, dentro do setNavigationOnClickListener, que vai fechar a activity.

Estamos utilizando binding.toolbar, pois no binding temos acesso às views do nosso layout.

Temos nosso setupViewModel, onde chamamos nosso viewmodel para nos fornecer nossos dados, e, em seguida, temos nosso liveDataObserver, para cada state.

  • Success: setamos a lista no adapter e deixamos o recyclerView visível, e o resto invisível.
  • Loading: mostramos um progressBar, e escondemos o resto.
  • Failed: mostramos um botão de try again, para caso algo dê errado, e escondemos as outras views.

Também fazemos os setup do recycler view setando seu adapter com o nosso androidJobAdapter, que foi injetado.

E, por fim, temos um método para trocar as visibilidades das views que temos no layout utilizando a extension visible.

Di(dependency injection):

Aqui ficam nossos modules Koin, que gerenciam nossas dependências.

  • PresentationModule: aqui provemos tudo de que nossa view e os viewmodels precisam para funcionar.

Primeiro provemos o nosso adapter, que foi injetado na AndroidJobsListActivity (factory: toda vez que requerido cria uma nova instancia).

private val androidJobAdapter: AndroidJobsAdapter by inject()

Em seguida, algo novo nos nossos modules Koin: os ViewModels. Nada mais é do que uma DSL extension para os nossos ViewModels, a fim de que eles possam ser injetados posteriormente nas nossas Views, como fizemos anteriormente:

private val viewModel: AndroidJobListViewModel by viewModel()private val viewModel: MainViewModel by viewModel()

Como nossa mainViewModel, não é necessária nenhuma outra dependência; somente a provemos sem nada no construtor. Mas, para o AndroidJobListViewModel, precisamos passar o useCase(provido na DomainModule.kt) e AndroidSchedulers.mainThread(main thread do android).

Mas como esses módulos vão funcionar???

Precisamos fazer o startKoin na classe Application do projeto:

Chamamos o startkoin e, primeiro, provemos o contexto e em seguida todos os koin Modules que criamos no projeto.

Obs: Nao esqueca de setar a classe Application no manifest

<application android:name=”.MyModuleApplication”

SHOW MEEEEE:


Presentation Repo: https://github.com/ifucolo/android-modularization/tree/presentation

Bom, galera, chegamos ao fim da primeira parte da série, pois pretendo fazer mais artigos em cima desse projeto, com testes e algumas coisas a mais.

Ficou com alguma dúvida? Manda um e-mail que terei o prazer de responder.

iagofucolo@gmail.com

follow me: https://twitter.com/Iagolfucolo

Revisores:

Rafael Machado

Bruno Stone

Android Dev BR

Artigos em português sobre Android, curados pela comunidade Android Dev BR. Junte-se a nós: slack.androiddevbr.org.

Iago Mendes Fucolo

Written by

Android Engineer @NewMotion, Writer, Ex almost footballer, and Brazilian.

Android Dev BR

Artigos em português sobre Android, curados pela comunidade Android Dev BR. Junte-se a nós: slack.androiddevbr.org.