O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Visão geral da Paging Library   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 Paging Library para seu app Android, adicione as seguintes dependências ao arquivo build.gradle do app:

    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
    }
    

Arquitetura da biblioteca

Esta seção descreve e mostra os principais componentes da Paging Library.

PagedList

O componente principal da Paging Library é a classe PagedList, que carrega partes dos dados do seu app, ou páginas. Conforme mais dados são necessários, ele é paginado no objeto PagedList já existente. Se algum dado carregado for alterado, uma nova instância de PagedList será emitida para o detentor 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 LiveData titular 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.

IU

A classe PagedList funciona com um PagedListAdapter para carregar itens em um RecyclerView. Essas classes trabalham juntas para buscar e exibir o conteúdo à medida que é 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 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 de dados 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 dados do servidor.

Diagramas de fluxos de dados
Figura 1. Como os dados fluem por cada uma das arquiteturas compatíveis com a Paging Library.

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, é possível detectar quando o banco de dados estiver sem dados, usando PagedList.BoundaryCallback. Você pode buscar mais itens da sua rede e inseri-los no banco de dados. Se a IU estiver observando o banco de dados, é só isso que você precisa fazer.

Processar erros de rede

Ao usar uma rede para buscar ou paginar os dados exibidos por meio da Paging Library, é 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. Se ocorrer 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

Se seu app já consome dados de um banco de dados ou de uma fonte de back-end, é possível fazer upgrade diretamente para a função oferecida pela Paging Library. 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 interface do usuário 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 um ListView. Nesse caso, você geralmente precisa migrar de um ListView para um RecyclerView como o contêiner de IU da lista do seu app e depois substituir o componente Cursor por Room ou PositionalDataSource, se as instâncias de Cursor acessam 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 aumentar a classe da biblioteca 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 Paging Library permitirá que você carregue dados com mais facilidade.

  • Seus dados não precisam ser de posicionamento. A Paging Library permite carregar dados diretamente do seu back-end usando as chaves fornecidas pela rede.
  • Seus dados podem ser extremamente grandes. Usando a Paging Library, é possível carregar dados em páginas até que não haja mais dados restantes.
  • Você pode observar seus dados com mais facilidade. A Paging Library 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

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

Issue tracker
Informe os problemas para que possamos corrigir os bugs.

Outros recursos

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

Amostras

Codelabs

Vídeos