Coletar dados paginados

Este guia foi desenvolvido com base na Visão geral da biblioteca Paging e discute como personalizar a solução de carregamento de dados do app para atender às necessidades de arquitetura dele.

Criar uma lista observável

Normalmente, o código da IU observa um objeto LiveData<PagedList> (ou, se você estiver usando RxJava2, um objeto Flowable<PagedList> ou Observable<PagedList>), que reside no ViewModel do app. Esse objeto observável forma uma conexão entre a apresentação e o conteúdo dos dados de lista do seu app.

Para criar um desses objetos PagedList observáveis, transmita uma instância de DataSource.Factory para um objeto LivePagedListBuilder ou RxPagedListBuilder. Um objeto DataSource carrega páginas para uma única PagedList. A classe de fábrica cria novas instâncias de PagedList em resposta a atualizações de conteúdo, como invalidações de tabelas de banco de dados e atualizações de rede. A biblioteca de persistência Room pode fornecer objetos DataSource.Factory para você, ou você pode criar objetos próprios.

O snippet de código a seguir mostra como criar uma nova instância de LiveData<PagedList> na classe ViewModel do seu app com o recursos de construção DataSource.Factory da Room.

ConcertDao

@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>
}
@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();
}

ConcertViewModel

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
       concertDao
.concertsByDate()

val concertList = myConcertDataSource.toLiveData(pageSize = 50)
// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
       concertDao
.concertsByDate();

LiveData<PagedList<Concert>> concertList =
       
LivePagedListBuilder(myConcertDataSource, /* page size */ 50).build();

Definir a própria configuração de paginação

Para configurar LiveData<PagedList> para casos avançados, também é possível definir sua própria configuração de paginação. Você pode definir especialmente os seguintes atributos:

  • Tamanho da página: o número de itens em cada página.
  • Distância da pré-busca: considerando o último item visível na IU de um app, o número de itens após o último item que a biblioteca Paging pode tentar buscar com antecedência. Esse valor precisa ser várias vezes maior que o tamanho da página.
  • Presença de marcador: determina se a IU exibirá marcadores para os itens de listas que ainda não foram carregados. Para ver uma discussão sobre os benefícios e as desvantagens de usar marcadores, consulte Fornecer marcadores na IU.

Se quiser ter mais controle sobre quando a biblioteca Paging carrega uma lista do banco de dados do seu app, transmita um objeto Executor personalizado ao LivePagedListBuilder, como mostrado no snippet de código a seguir.

ConcertViewModel

val myPagingConfig = Config(
        pageSize
= 50,
        prefetchDistance
= 150,
        enablePlaceholders
= true
)

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =
        concertDao
.concertsByDate()

val concertList = myConcertDataSource.toLiveData(
        pagingConfig
= myPagingConfig,
        fetchExecutor
= myExecutor
)
PagedList.Config myPagingConfig = new PagedList.Config.Builder()
       
.setPageSize(50)
       
.setPrefetchDistance(150)
       
.setEnablePlaceholders(true)
       
.build();

// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =
        concertDao
.concertsByDate();

LiveData<PagedList<Concert>> concertList =
       
new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig)
           
.setFetchExecutor(myExecutor)
           
.build();

Escolher o tipo de fonte de dados correto

É importante se conectar à fonte de dados que processa melhor a estrutura dos seus dados de origem:

  • Use PageKeyedDataSource se as páginas carregadas incorporam chaves anteriores ou subsequentes. Por exemplo, caso você esteja buscando postagens de mídia social na rede, talvez seja necessário transmitir um token nextPage de um carregamento para um carregamento subsequente.
  • Use ItemKeyedDataSource se precisar usar dados do item N para buscar o item N+1. Por exemplo, se você estiver buscando comentários em sequência em um app de discussões, poderá ser necessário transmitir o ID do último comentário para acessar o conteúdo do comentário seguinte.
  • Use PositionalDataSource se precisar buscar páginas de dados de qualquer local escolhido no seu armazenamento de dados. Essa classe é compatível com a solicitação de um conjunto de itens de dados a partir de qualquer local selecionado. Por exemplo, a solicitação pode retornar os 50 itens de dados do local 1500.

Notificar quando os dados forem inválidos

Ao usar a biblioteca Paging, a camada de dados é responsável por notificar as outras camadas do seu app quando uma tabela ou linha se torna obsoleta. Para isso, chame invalidate() da classe de DataSource que você escolheu para seu app.

Criar as próprias fontes de dados

Se você usa uma solução de dados local personalizada ou carrega dados diretamente de uma rede, é possível implementar uma das subclasses de DataSource. O snippet de código a seguir mostra uma fonte de dados que foi vinculada ao horário de início de determinado show.

class ConcertTimeDataSource() :
       
ItemKeyedDataSource<Date, Concert>() {
   
override fun getKey(item: Concert) = item.startTime

   
override fun loadInitial(
            params
: LoadInitialParams<Date>,
            callback
: LoadInitialCallback<Concert>) {
       
val items = fetchItems(params.requestedInitialKey,
                params
.requestedLoadSize)
        callback
.onResult(items)
   
}

   
override fun loadAfter(
            params
: LoadParams<Date>,
            callback
: LoadCallback<Concert>) {
       
val items = fetchItemsAfter(
            date
= params.key,
            limit
= params.requestedLoadSize)
        callback
.onResult(items)
   
}
}
public class ConcertTimeDataSource
       
extends ItemKeyedDataSource<Date, Concert> {
   
@NonNull
   
@Override
   
public Date getKey(@NonNull Concert item) {
       
return item.getStartTime();
   
}

   
@Override
   
public void loadInitial(@NonNull LoadInitialParams<Date> params,
           
@NonNull LoadInitialCallback<Concert> callback) {
       
List<Concert> items =
            fetchItems
(params.key, params.requestedLoadSize);
        callback
.onResult(items);
   
}

   
@Override
   
public void loadAfter(@NonNull LoadParams<Date> params,
           
@NonNull LoadCallback<Concert> callback) {
       
List<Concert> items =
            fetchItemsAfter
(params.key, params.requestedLoadSize);
        callback
.onResult(items);
   
}

Em seguida, carregue esses dados personalizados em objetos PagedList criando uma subclasse concreta de DataSource.Factory. O snippet de código a seguir mostra como gerar novas instâncias da fonte de dados personalizada definida no snippet de código anterior.

class ConcertTimeDataSourceFactory :
       
DataSource.Factory<Date, Concert>() {
   
val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()
   
var latestSource: ConcertDataSource?
   
override fun create(): DataSource<Date, Concert> {
        latestSource
= ConcertTimeDataSource()
        sourceLiveData
.postValue(latestSource)
       
return latestSource
   
}
}
public class ConcertTimeDataSourceFactory
       
extends DataSource.Factory<Date, Concert> {
   
private MutableLiveData<ConcertTimeDataSource> sourceLiveData =
           
new MutableLiveData<>();

   
private ConcertDataSource latestSource;

   
@Override
   
public DataSource<Date, Concert> create() {
        latestSource
= new ConcertTimeDataSource();
        sourceLiveData
.postValue(latestSource);
       
return latestSource;
   
}
}

Considerar como as atualizações de conteúdo funcionam

Ao construir objetos observáveis PagedList, considere como as atualizações de conteúdo funcionam. Se os dados forem carregados diretamente de um banco de dados da Room, as atualizações serão enviadas automaticamente para a IU do seu app.

Ao usar uma API de rede paginada, você normalmente tem uma interação do usuário, como "deslizar para atualizar", que serve como um sinal para invalidar a DataSource usada mais recentemente. Você precisa solicitar uma nova instância dessa fonte de dados. O snippet de código a seguir demonstra esse comportamento:

class ConcertActivity : AppCompatActivity() {
   
override fun onCreate(savedInstanceState: Bundle?) {
       
// ...
       
concertTimeViewModel.refreshState.observe(this, Observer {
           
// Shows one possible way of triggering a refresh operation.
            swipeRefreshLayout
.isRefreshing =
                    it
== MyNetworkState.LOADING
       
})
        swipeRefreshLayout
.setOnRefreshListener {
            concertTimeViewModel
.invalidateDataSource()
       
}

   
}
}

class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() {
   
val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime)
   
val concertList: LiveData<PagedList<Concert>> =
            dataSourceFactory
.toLiveData(
                pageSize
= 50,
                fetchExecutor
= myExecutor
           
)

   
fun invalidateDataSource() =
            dataSourceFactory
.sourceLiveData.value?.invalidate()

}
public class ConcertActivity extends AppCompatActivity {
   
@Override
   
public void onCreate(@Nullable Bundle savedInstanceState) {
       
// ...
       
viewModel.getRefreshState()
               
.observe(this, new Observer<NetworkState>() {
           
// Shows one possible way of triggering a refresh operation.
           
@Override
           
public void onChanged(@Nullable MyNetworkState networkState) {
                swipeRefreshLayout
.isRefreshing =
                        networkState
== MyNetworkState.LOADING;
           
}
       
};

        swipeRefreshLayout
.setOnRefreshListener(new SwipeRefreshListener() {
           
@Override
           
public void onRefresh() {
                viewModel
.invalidateDataSource();
           
}
       
});

   
}
}

public class ConcertTimeViewModel extends ViewModel {
   
private LiveData<PagedList<Concert>> concertList;
   
private DataSource<Date, Concert> mostRecentDataSource;

   
public ConcertTimeViewModel(Date firstConcertStartTime) {
       
ConcertTimeDataSourceFactory dataSourceFactory =
               
new ConcertTimeDataSourceFactory(firstConcertStartTime);
        mostRecentDataSource
= dataSourceFactory.create();
        concertList
= new LivePagedListBuilder<>(dataSourceFactory, 50)
               
.setFetchExecutor(myExecutor)
               
.build();
   
}

   
public void invalidateDataSource() {
        mostRecentDataSource
.invalidate();
   
}

}

Fornecer mapeamento de dados

A biblioteca Paging é compatível com transformações baseadas em itens e em páginas de itens carregados por uma DataSource.

No snippet de código a seguir, uma combinação do nome e da data do show é mapeada para uma única string contendo o nome e a data.

class ConcertViewModel : ViewModel() {
   
val concertDescriptions : LiveData<PagedList<String>>
       
init {
           
val concerts = database.allConcertsFactory()
                   
.map { "${it.name} - ${it.date}" }
                   
.toLiveData(pageSize = 50)
       
}
}
public class ConcertViewModel extends ViewModel {
   
private LiveData<PagedList<String>> concertDescriptions;

   
public ConcertViewModel(MyDatabase database) {
       
DataSource.Factory<Integer, Concert> factory =
                database
.allConcertsFactory().map(concert ->
                    concert
.getName() + "-" + concert.getDate());
        concertDescriptions
= new LivePagedListBuilder<>(
            factory
, /* page size */ 50).build();
   
}
}

Isso poderá ser útil se você quiser unir, converter ou preparar itens depois que eles forem carregados. Como esse trabalho é feito no executor de busca, é possível realizar tarefas potencialmente caras, como ler o disco ou consultar um banco de dados separado.

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

Codelabs

Vídeos

Nenhuma recomendação no momento.

Tente na sua Conta do Google.