The Android Developer Challenge is back! Submit your idea before December 2.

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.

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 blocos de dados ou páginas do seu app. À medida que mais dados se tornarem necessários, eles serão paginados no objeto PagedList existente. Se qualquer dado carregado mudar, 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 seu app apresenta o conteúdo deles, ao mesmo tempo em que respeita os ciclos de vida dos controladores de IU.

O snippet de código a seguir mostra como configurar o modelo de visualização do seu app para carregar e apresentar dados usando um LiveData com 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 seu app a partir do objeto DataSource correspondente. Os dados são transferidos 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 do 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 (link em inglês) para carregar informações para seu seu objeto DataSource personalizado.

Somente banco de dados

Configure a RecyclerView para observar o armazenamento local, de preferência usando a biblioteca de persistência do Room. Dessa forma, sempre que dados forem inseridos ou modificados no banco de dados do seu app, essas alterações serão refletidas automaticamente na RecyclerView que exibe esses dados.

Rede e banco de dados

Depois de começar a observar o banco de dados, é possível ouvir 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ê usa a funcionalidade personalizada para carregar pequenos subconjuntos de dados da fonte de dados do seu app, é possível substituir essa lógica pela da classe PagedList. As instâncias de PagedList oferecem conexões integradas a fontes de dados comuns. Essas instâncias também fornecem adaptadores para objetos RecyclerView que podem ser incluídos na IU do app.

Dados carregados usando listas, em vez de páginas

Se você usa uma lista na memória como estrutura de dados de backup para o adaptador da sua IU, observe as atualizações de dados usando uma classe PagedList caso o número de itens da lista possa aumentar. Instâncias de PagedList podem usar LiveData<PagedList> ou Observable<List> para passar atualizações de dados para a IU do seu app, minimizando os tempos de carregamento e o uso da memória. Melhor ainda, a substituição de um objeto List por um PagedList no seu app não exige nenhuma mudança na estrutura da IU do seu app nem na lógica de atualização de dados.

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, normalmente é necessário migrar de uma ListView para uma RecyclerView como o contêiner de IU da lista do app e substituir o componente Cursor pelo Room ou PositionalDataSource, dependendo de se as instâncias do Cursor possuem acesso ao banco de dados SQLite.

Em algumas situações, como ao trabalhar com instâncias do Spinner, só é preciso fornecer o 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 una essa lista a 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ê usa objetos AsyncListUtil para carregar e exibir grupos de informações de forma assíncrona, a Paging Library possibilita o carregamento de 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 mudados no banco de dados, o conteúdo na 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)
            val viewModel = ViewModelProviders.of(this)
                    .get<ConcertViewModel>()
            val recyclerView = findViewById(R.id.concert_list)
            val adapter = ConcertAdapter()
            viewModel.livePagedList.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 =
                    ViewModelProviders.of(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 o RxJava2

Se você prefere usar o RxJava2 (link em inglês) em vez do LiveData, é possível 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()
        private lateinit var viewModel: ConcertViewModel

        private val disposable = CompositeDisposable()

        public override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val recyclerView = findViewById(R.id.concert_list)
            viewModel = ViewModelProviders.of(this)
                    .get<ConcertViewModel>()
            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 = ViewModelProviders.of(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 tanto para uma solução baseada no RxJava2 quanto para uma baseada no LiveData.

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 seguintes recursos.

Exemplos

Codelabs

Vídeos