Visão geral da Biblioteca Paging 2 Parte do Android Jetpack.
A Paging Library ajuda a carregar e exibir pequenos blocos de dados por vez. O carregamento de dados parciais sob demanda reduz o uso da largura de banda da rede e dos recursos do sistema.
Este guia oferece vários exemplos conceituais da biblioteca, além de uma visão geral de como ela funciona. Para ver exemplos completos de como essa biblioteca funciona, consulte o codelab e os exemplos da seção Outros recursos.
Configurar
Para importar componentes da biblioteca Paging para seu app Android, adicione as seguintes
dependências ao arquivo build.gradle
do app:
Groovy
dependencies { def paging_version = "2.1.2" implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx // optional - RxJava support implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx }
Kotlin
dependencies { val paging_version = "2.1.2" implementation("androidx.paging:paging-runtime:$paging_version") // For Kotlin use paging-runtime-ktx // alternatively - without Android dependencies for testing testImplementation("androidx.paging:paging-common:$paging_version") // For Kotlin use paging-common-ktx // optional - RxJava support implementation("androidx.paging:paging-rxjava2:$paging_version") // For Kotlin use paging-rxjava2-ktx }
Arquitetura da biblioteca
Esta seção descreve e mostra os principais componentes da Paging Library.
PagedList
O componente principal da biblioteca Paging é a classe
PagedList
, que carrega
blocos de dados ou páginas do seu app. Conforme mais dados são necessários, eles são
paginados no objeto PagedList
já existente. Se algum dado carregado for alterado, uma nova
instância de PagedList
será emitida para o armazenador de dados observáveis de um objeto baseado em
LiveData
ou RxJava2. À medida que os objetos
PagedList
são gerados,
a IU do app apresenta o conteúdo deles, respeitando os
ciclos de vida dos controladores de IU.
O snippet de código a seguir mostra como é possível configurar o modelo de visualização de seu app para
carregar e apresentar dados usando um armazenador LiveData
de objetos PagedList
.
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; // Creates a PagedList object with 50 items per page. public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), 50).build(); } }
Dados
Cada instância de PagedList
carrega
um resumo atualizado dos dados do app a partir do objeto
DataSource
correspondente. Fluxos de dados
do back-end ou do banco de dados do seu app para o objeto PagedList
.
O exemplo a seguir usa a biblioteca de persistência Room para organizar os dados do seu app. No entanto, caso queira armazená-los de outra forma, também é possível fornecer sua própria fonte de dados.
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a // PositionalDataSource object. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); }
Para saber mais sobre como carregar dados em objetos PagedList
, consulte o
guia sobre como Carregar dados paginados.
Interface do usuário
A classe PagedList
funciona com um
PagedListAdapter
para carregar itens em uma
RecyclerView
. Essas
classes trabalham juntas para buscar e exibir o conteúdo à medida que ele é carregado, fazendo a pré-busca
de conteúdo fora da visualização e animando as mudanças de conteúdo.
Para saber mais, consulte o guia sobre como Exibir listas paginadas.
Compatibilidade com diferentes arquiteturas de dados
A Paging Library é compatível com as arquiteturas de dados a seguir:
- Veiculada somente de um servidor de back-end.
- Armazenada somente em um banco de dados do dispositivo.
- Uma combinação das outras origens, usando o banco de dados do dispositivo como cache.
A figura 1 mostra como é o fluxo de dados cada um desses cenários de arquitetura. No caso de uma solução somente de rede ou de banco de dados, os dados fluem diretamente para o modelo de IU do seu app. Caso você esteja usando uma abordagem combinada, os dados fluem do servidor de back-end para um banco de dados do dispositivo e depois para o modelo de IU do app. De vez em quando, o endpoint de cada fluxo fica sem dados para carregar e solicita mais dados do componente que os forneceu. Por exemplo, quando um banco de dados do dispositivo fica sem dados, ele solicita mais do servidor.
O restante desta seção oferece recomendações para a configuração de cada caso de uso de fluxo de dados.
Somente rede
Para exibir dados de um servidor de back-end, use a versão síncrona da
API Retrofit para carregar
informações no seu próprio objeto DataSource
personalizado.
Somente banco de dados
Configure seu RecyclerView
para observar o armazenamento local, de preferência usando a biblioteca de
persistência Room. Dessa forma, sempre que os dados forem
inseridos ou modificados no banco de dados do app, essas mudanças serão
refletidas automaticamente no RecyclerView
que exibe esses dados.
Rede e banco de dados
Depois de começar a observar o banco de dados, será possível detectar quando
ele estiver sem dados usando
PagedList.BoundaryCallback
.
Você pode buscar mais itens da sua rede e inseri-los no
banco de dados. Caso a IU esteja observando o banco de dados, é só isso que você precisa fazer.
Solucionar erros de rede
Ao usar uma rede para buscar ou paginar os dados exibidos usando a biblioteca Paging, é importante não tratar a rede como "disponível" "ou" "indisponível" o tempo todo, porque muitas conexões são intermitentes ou lentas:
- Um servidor específico pode não responder a uma solicitação de rede.
- O dispositivo pode estar conectado a uma rede lenta ou fraca.
Em vez disso, seu app precisa verificar se há falhas em cada solicitação e recuperá-las da melhor forma possível nos casos em que a rede não está disponível. Por exemplo, você pode oferecer um botão "Tentar novamente" a ser selecionado pelos usuários quando a etapa de atualização de dados não funcionar. Caso ocorra um erro durante a etapa de paginação de dados, é melhor repetir as solicitações de paginação de forma automática.
Atualizar seu app existente
Caso seu app já consuma dados de um banco de dados ou de uma fonte de back-end, é possível fazer upgrade diretamente para a função oferecida pela biblioteca Paging. Esta seção mostra como fazer upgrade de um app com design comum.
Soluções personalizadas de paginação
Se você usar a funcionalidade personalizada para carregar pequenos subconjuntos de dados da
fonte de dados do seu app, substitua essa lógica pela classe
PagedList
. As instâncias de
PagedList
fornecem conexões integradas a fontes de dados comuns. Essas instâncias
também fornecem adaptadores para
objetos RecyclerView
que
você pode incluir na IU do seu app.
Dados carregados usando listas, em vez de páginas
Se você usar uma lista na memória como estrutura de dados de apoio para o adaptador da UI,
considere a possibilidade de usar uma classe PagedList
se o número
de itens na lista ficar grande. As instâncias de PagedList
podem usar
LiveData<PagedList>
ou
Observable<List>
para transmitir atualizações de dados para a IU do app, minimizando os tempos de carregamento
e o uso de memória. Melhor ainda, substituir um objeto List
por um PagedList
no seu app não exige nenhuma mudança na
estrutura de IU ou na lógica de atualização de dados do seu app.
Associar um cursor de dados a uma visualização em lista usando o CursorAdapter
Seu app pode usar um CursorAdapter
para associar dados de um Cursor
a uma
ListView
. Nesse caso, você geralmente precisa
migrar de uma ListView
para uma
RecyclerView
como o
contêiner de IU da lista do seu app e depois substituir o componente Cursor
por Room ou
PositionalDataSource
, caso as instâncias de Cursor
acessem um
banco de dados SQLite.
Em algumas situações, como ao trabalhar com instâncias de
Spinner
, você fornece apenas o
próprio adaptador. Em seguida, uma biblioteca recebe os dados que são carregados nesse adaptador e
os exibe para você. Nessas situações, mude o tipo de
dados do adaptador para LiveData<PagedList>
e envolva
essa lista em um objeto ArrayAdapter
antes de tentar fazer com que uma classe de biblioteca infle esses itens em uma IU.
Carregar conteúdo de forma assíncrona usando o AsyncListUtil
Se você estiver usando objetos
AsyncListUtil
para
carregar e exibir grupos de informações de forma assíncrona, a biblioteca Paging permitirá
que você carregue dados com mais facilidade:
- Seus dados não precisam ser de posicionamento. A biblioteca Paging permite carregar dados diretamente do seu back-end usando as chaves fornecidas pela rede.
- Seus dados podem ser extremamente grandes. Usando a biblioteca Paging, é possível carregar dados em páginas até que não haja mais dados restantes.
- Você pode observar seus dados com mais facilidade. A biblioteca Paging pode apresentar os dados que o ViewModel do seu app mantém em uma estrutura de dados observável.
Exemplos de banco de dados
Os snippets de código a seguir mostram várias maneiras possíveis de fazer com que todas as partes funcionem juntas.
Observar dados paginados usando o LiveData
O snippet de código a seguir mostra todas as partes funcionando juntas. À medida que eventos
de shows são adicionados, removidos ou modificados no banco de dados, o conteúdo em
RecyclerView
é
atualizado de forma automática e eficiente:
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> } class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> = concertDao.concertsByDate().toLiveData(pageSize = 50) } class ConcertActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact val viewModel: ConcertViewModel by viewModels() val recyclerView = findViewById(R.id.concert_list) val adapter = ConcertAdapter() viewModel.concertList.observe(this, PagedList(adapter::submitList)) recyclerView.setAdapter(adapter) } } class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { fun onBindViewHolder(holder: ConcertViewHolder, position: Int) { val concert: Concert? = getItem(position) // Note that "concert" is a placeholder if it's null. holder.bindTo(concert) } companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. override fun areItemsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert.id == newConcert.id override fun areContentsTheSame(oldConcert: Concert, newConcert: Concert) = oldConcert == newConcert } } }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); } public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final LiveData<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new LivePagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50).build(); } } public class ConcertActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ConcertViewModel viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); RecyclerView recyclerView = findViewById(R.id.concert_list); ConcertAdapter adapter = new ConcertAdapter(); viewModel.concertList.observe(this, adapter::submitList); recyclerView.setAdapter(adapter); } } public class ConcertAdapter extends PagedListAdapter<Concert, ConcertViewHolder> { protected ConcertAdapter() { super(DIFF_CALLBACK); } @Override public void onBindViewHolder(@NonNull ConcertViewHolder holder, int position) { Concert concert = getItem(position); if (concert != null) { holder.bindTo(concert); } else { // Null defines a placeholder item - PagedListAdapter automatically // invalidates this row when the actual object is loaded from the // database. holder.clear(); } } private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK = new DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. @Override public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.getId() == newConcert.getId(); } @Override public boolean areContentsTheSame(Concert oldConcert, Concert newConcert) { return oldConcert.equals(newConcert); } }; }
Observar dados paginados usando RxJava2
Se preferir usar
RxJava2 em vez de
LiveData
, você pode
criar um objeto Observable
ou Flowable
:
Kotlin
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: Observable<PagedList<Concert>> = concertDao.concertsByDate().toObservable(pageSize = 50) }
Java
public class ConcertViewModel extends ViewModel { private ConcertDao concertDao; public final Observable<PagedList<Concert>> concertList; public ConcertViewModel(ConcertDao concertDao) { this.concertDao = concertDao; concertList = new RxPagedListBuilder<>( concertDao.concertsByDate(), /* page size */ 50) .buildObservable(); } }
Em seguida, você pode iniciar e parar a observação dos dados usando o código no snippet a seguir:
Kotlin
class ConcertActivity : AppCompatActivity() { private val adapter = ConcertAdapter() // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val viewModel: ConcertViewModel by viewModels() private val disposable = CompositeDisposable() public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val recyclerView = findViewById(R.id.concert_list) recyclerView.setAdapter(adapter) } override fun onStart() { super.onStart() disposable.add(viewModel.concertList .subscribe(adapter::submitList))) } override fun onStop() { super.onStop() disposable.clear() } }
Java
public class ConcertActivity extends AppCompatActivity { private ConcertAdapter adapter = new ConcertAdapter(); private ConcertViewModel viewModel; private CompositeDisposable disposable = new CompositeDisposable(); @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RecyclerView recyclerView = findViewById(R.id.concert_list); viewModel = new ViewModelProvider(this).get(ConcertViewModel.class); recyclerView.setAdapter(adapter); } @Override protected void onStart() { super.onStart(); disposable.add(viewModel.concertList .subscribe(adapter.submitList(flowableList) )); } @Override protected void onStop() { super.onStop(); disposable.clear(); } }
O código para ConcertDao
e ConcertAdapter
é o mesmo para uma
solução baseada em
RxJava2 e para uma
solução baseada em LiveData
.
Enviar feedback
Envie comentários e ideias usando os recursos abaixo:
- Issue tracker
- Informe os problemas para que possamos corrigir os bugs.
Outros recursos
Para saber mais sobre a biblioteca Paging, consulte os recursos a seguir.
Amostras
- Amostra de paginação de Componentes da arquitetura do Android (link em inglês)
- Amostra de paginação com rede (link em inglês)
Codelabs
Vídeos
- Android Jetpack: gerenciar listas infinitas com RecyclerView e Paging (Google I/O 2018)
- Android Jetpack: Paging
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Migrar para a Paging 3
- Exibir listas paginadas
- Coletar dados paginados