Android Jetpack — Usando a biblioteca de paginação.

Um mundo novo de possibilidades se abriu com o conjunto de ferramentas do Android Jetpack. Dois anos atrás, com a introdução dos Componentes de Arquitetura, muitos desenvolvedores se viram surpresos. Finalmente estamos recebendo uma instrução de como construir apps Android? Será o fim do MVP? MVVM for life? Esses tipos de questionamentos…

Ano passado,eis que surge Jetpack com uma porção de novidades. Esse post trata de um dos residentes mais antigos do pacote, a Paging Library.

Para estudar os componentes do Jetpack será usado como exemplo uma app baseada na API REST do StackExchange, mais precisamente do famoso e adorado StackOverflow. Para ilustrar o poderío dessa nova lib será construído como exemplo uma lista com todos os usuários, ordenados por reputação. Como a quantidade de usuários é enorme (obviously) a API do SE dá a possibilidade de trazer pedaços dessa grande lista (as páginas). O objetivo então é:

Criar uma lista com scroll infinito utilizando a nova biblioteca de paginação do Android

Nesse tutorial vamos apenas consultar a API e exibir os dados, nada de salvar localmente e puxar os dados do banco de dados local.

Bonus points se:

  • Forem utilizados outros componentes de arquitetura
  • Fazer o carregamento da lista o mais “smooth” possível

Então basicamente o resultado final que será visto é esse:

A primeira coisa a fazer é adicionar as dependências do biblioteca de paginação ao nosso arquivo build.gradle (app level):

Ou se você já se converteu ao Android X:

Note que também foi utilizada a dependência do RxJava2, sua utilidade será explicada posteriormente. Outras dependências que estão nesse projeto são :

Foram usados ViewModel, LiveData e LifeCycle para estuturar a app segundo a arquitetura MVVM . O (clássico) Retrofit para fazer chamadas de rede, e geralmente o CallAdapter de RxJava2 para receber os resultados das chamadas a API.

No MVP, geralmente, utilizava-se o RxJava entre o “Model” (normalmente um Interactor) e o Presenter, então o Presenter reagia quando o model terminava de carregar algo da camada de dados. Aqui no MVVM usou-se o Rx entre o Model (um Repositório) e o ViewModel, de forma que a partir do ViewModel (do VM para a View) utilizou-se o LiveData por que a View (Activity ou Fragment) é geralmente um LifeCycleOwner e os dados podem assim se tornar sensíveis aos ciclos de vida.

Este é o modelo que representa uma resposta a chamada a API do SO, juntamente com esse POJO foi usado um outro para representar o usuário. Existem outros dados, mas por enquanto serão apenas utilizados esses por simplicidade:

Depois dos modelos podemos fazer a configuração do Retrofit, normalmente pode-se separar uma classe para a criação do serviço de acesso a API do SO (o mais comun é usar DI para fornecer essas dependências em sua app):

Essa classe simplesmente vai instanciar o nosso serviço definido na interface StackOverflowAPI. Observem que utilizou-se o RxJava2 Call Adapter para transformar as calls nativas do Retrofit em objetos do Rx (Single, Observable, Flowable e etc). A interface StackOverflowAPI tem essa cara:

Pronto, a camada de rede está configurada, o próximo passo será configurar a biblioteca de paginação. A configuração é um tanto complexa, mas depois que é feita uma vez não parece tão ruim assim. Serão utilizadas 3 classes auxiliares:

  • Um DataSource.Factory , porque nós estamos usando RxJava para criar uma lista paginada com RxJava nós precisaremos fornecer um objeto do tipo Factory para que o repositório saiba criar um DataSource a partir desse Factory.
  • Um DataSource, a implementação do comportamento de carregamento de novos dados é implementado nessa classe.
  • Um DiffUtil.ItemCallback, o adapter precisa de uma instancia dessa classe para definir os updates que serão feitos na lista (RecyclerView).

Definindo então o DataSource.Factory:

A classe é bem simples, a “inspiração” veio desse sample code do google. Basicamente cria-se um DataSource factory (herdando de DataSource.Factory) e implementando o método create. Aqui instancia-se um novo DataSource e ao mesmo tempo um PublishSubject para emitir o DataSource, isso vai se provar util mais para frente para propagar atualizações para a UI.

A próxima classe que será implementada é o DataSource em si:

Basicamente é preciso implementar 3 métodos:

  • loadInitial: Aqui você diz com será carregada os dados iniciais (a primeira página) da sua lista. Atenção para a chamada getTopUsers(1, params.requestedLoadSize). Aqui simplesmente falamos para a API, “ei, manda ai a primeira página com requestedLoadSize usuários”. Como já devem ter advinhado requestedLoadSize é o tamanho da página que nós passamos para a API na hora de construir a lista (mais pra frente). Quando a chamada a API termina chamamos a callback callback.onResult(it.items, 1, 2) com os resultados da chamada, o número da página atual e o número da próxima página.
  • loadBefore: Carrega a página anterior. Aqui se a sua lista cresce para os dois lados você deve criar uma pequena lógica para retornar o valor da próxima página, parecida com a lógica do próximo método só que em reverso.
  • loadAfter: Obviamente, carrega a próxima página. Ao fim da chamada invocamos a callback callback.onResult(it.items, params.key + 1), incrementando o número da página.

Foi utilizado também um PublishSubject onde “foram postadas” as atualizações das chamadas. A classe de NetworkState que é “postada” nesse PublishSubject se parece com isso aqui:

Por último o DiffUtil.ItemCallback que será utilizado nos updates no adapter mais a frente:

Deve-se implementar dois métodos: areItemsTheSame onde deve-se criar uma lógica para indetificar se dois items são iguais (self-explanatory) e o areContentsTheSame (also self-explanatory)

Finalmente, a geração das listas paginadas se dá no repositório. Ele recebe uma instância do DataSource.Factory e terá dois métodos, um para retornar os usuários (paginados) e outro para retornar o status da chamada a web.

Para criar a lista paginada (PagedList) é criada uma configuração (utilizando um objeto PagedList.Condig.Builder) onde é setado basicamente o tamanho da página. Existem outras configuração que podem ser feitas, como utilizar placeholders no caso dos dados ainda não terem “chegado” ou carregar uma parte dos dados mais cedo (prefetch), mais infos aqui.

Utilizando as configurações anteriores pode-se gerar a lista paginada em um Observable ou LiveData. Acima foi utilizado RxJava. Note que foi utilizado um RxPagedListBuilder para construir o Observable. Utilizou-se também algumas configurações de scheduling e finalmente retornamos o objeto concreto construído que poderá ser utilizado pelo ViewModel para passar os dados para a View:

O ViewModel, recebe o repositório no construtor e tem um método para cada tipo de dado que ele retorna. Podem notar que o método de retornar usuários utiliza o repositório para transformar o Observable em LiveData utilizando o LiveDataReactiveStreams. Assim a View vai receber um LiveData que já se adapta aos ciclos de vida.

A utilização do ViewModel na View se dá da seguinte maneira:

Ao receber a lista do ViewModel é chamado o método submitList para adicionar a lista ao adapter. Esse método existe graças a classe PagedListAdapter que devemos utilizar para construir o Adapter.

Assim, a última API que deverá ser usada para construir a lista paginada é o PagedListAdapter ao qual deve-se estender passando aquele DiffUtil.Callback que foi criado anterioremente.

O LiveData com o NetworkState será usado na View só para atualizar o SwipeRefresh que envolve o RecyclerView:

Como última observação pode-se notar que o Dagger foi utilizado all over the place, mas sempre com injeção no construtor, então se a configuração do Dagger for muito complicada, pode-se simplesmente remover a anotação e prover os objetos reais.

Bem, é isso. A biblioteca de paginação tem várias partes para serem implementadas, mas é interessante de ver como ela junto com os outros componentes de arquitetura interagem. E o efeito final é bem satisfatório. Para quem já implementou a paginação “na mão” a abstração fornecida pela biblioteca vale a pena.

O código completo pode ser encontrado aqui no meu GitHub. Estou referenciando um commit porque esse repositório está em constante mudança!

If you like it and you know it clap you hands! Clap Clap ;)