Coletar dados paginados

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

Criar uma lista observável

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

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

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

ConcertDao

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>
    }
    

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

ConcertViewModel

Kotlin

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

    val concertList = myConcertDataSource.toLiveData(pageSize = 50)
    

Java

    // 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 sua própria configuração de paginação

Para configurar mais detalhadamente uma 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 Paging Library 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.

Caso queira ter mais controle sobre quando a Paging Library carrega uma lista do banco de dados do seu app, passe um objeto Executor personalizado para o LivePagedListBuilder, conforme mostrado no snippet de código a seguir:

ConcertViewModel

Kotlin

    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
    )
    

Java

    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 incorporarem chaves "próximo/anterior". Por exemplo, se você estiver buscando postagens em mídias sociais da rede, poderá ser necessário passar um token nextPage de um carregamento para outro 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 passar o código do último comentário para conseguir 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 a partir do local 1500.

Notificar quando os dados forem inválidos

Ao usar a Paging Library, a camada de dados é responsável por notificar as outras camadas do seu app quando uma tabela ou linha se tornar obsoleta. Para fazer isso, chame invalidate() da classe DataSource escolhida para seu app.

Criar suas próprias fontes de dados

Caso você use uma solução de dados locais personalizada ou carregue dados diretamente de uma rede, é possível implementar uma das subclasses da 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:

Kotlin

    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)
        }
    }
    

Java

    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, você pode carregar 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:

Kotlin

    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
        }
    }
    

Java

    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 PagedList observáveis, considere como as atualizações de conteúdo funcionam. Se os dados forem carregados diretamente de um banco de dados do Room, as atualizações serão enviadas automaticamente para a IU do seu app.

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

Kotlin

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

Java

    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 Paging Library é compatível com transformações de itens carregados por uma DataSource com base em itens e em páginas.

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:

Kotlin

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

Java

    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 pode 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

Compartilhe seus comentários e ideias conosco por meio dos recursos a seguir:

Rastreador de problemas
Informe os problemas para que possamos corrigir os bugs.

Outros recursos

Para saber mais sobre a Paging Library, consulte os recursos a seguir.

Exemplos

Codelabs

Vídeos