Ringkasan library paging   Bagian dari Android Jetpack.

Library Paging membantu Anda memuat dan menampilkan data sepotong demi sepotong. Memuat potongan data berdasarkan permintaan dapat mengurangi penggunaan bandwidth jaringan dan resource sistem.

Artikel ini menyajikan beberapa contoh konseptual tentang Library Paging, beserta ringkasan cara kerjanya. Untuk melihat contoh lengkap fungsi library ini, cobalah codelab dan contoh dari bagian referensi tambahan.

Arsitektur library

Bagian ini menjelaskan dan menampilkan beberapa komponen utama library paging.

PagedList

Komponen utama Library Paging adalah class PagedList, yang memuat potongan data aplikasi Anda, yang disebut page. Seiring meningkatnya jumlah data yang diperlukan, data di-page ke dalam objek PagedList yang ada. Jika terdapat perubahan pada data yang dimuat, instance baru PagedList akan dikirim ke penampung data yang dapat diamati dari objek berbasis LiveData atau RxJava2. Saat objek PagedList dihasilkan, UI aplikasi Anda akan menampilkan kontennya, semuanya dengan tetap mempertahankan siklus proses pengontrol UI Anda.

Cuplikan kode berikut menunjukkan cara mengonfigurasi model tampilan aplikasi untuk memuat dan menyajikan data menggunakan penampung LiveData dari objek 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();
        }
    }
    

Data

Setiap instance PagedList memuat cuplikan terbaru data aplikasi Anda dari objek DataSource terkaitnya. Data mengalir dari backend atau database aplikasi Anda ke objek PagedList.

Contoh berikut menggunakan library persistensi Room untuk menata data aplikasi Anda, tetapi jika ingin menyimpan data menggunakan cara lain, Anda juga dapat menyediakan sumber data sendiri.

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

Untuk mempelajari lebih lanjut cara memuat data ke objek PagedList, lihat panduan cara Memuat data yang di-page.

UI

Class PagedList berfungsi dengan PagedListAdapter untuk memuat item ke dalam RecyclerView. Class ini bekerja sama untuk mengambil dan menampilkan konten saat dimuat, dengan mengambil dahulu konten di luar tampilan dan menganimasikan perubahan konten.

Untuk mempelajari lebih lanjut, lihat panduan cara Menampilkan daftar yang di-page.

Mendukung arsitektur data yang berbeda

Library Paging mendukung arsitektur data yang:

  • Ditayangkan hanya dari server backend.
  • Disimpan hanya di database dalam perangkat.
  • Dikombinasikan dengan sumber lain, menggunakan database dalam perangkat sebagai cache.

Gambar 1 menunjukkan aliran data di setiap skenario arsitektur ini. Dalam kasus solusi khusus jaringan atau khusus database, data mengalir langsung ke model UI aplikasi Anda. Jika Anda menggunakan pendekatan gabungan, data mengalir dari server backend, masuk ke database dalam perangkat, lalu ke model UI aplikasi Anda. Terkadang, endpoint setiap aliran data kehabisan data yang harus dimuat, dan pada saat itu lebih banyak data akan diminta dari komponen yang menyediakan data. Misalnya, jika database dalam perangkat kehabisan data, lebih banyak data akan diminta dari server.

Diagram alur data
Gambar 1. Bagaimana data mengalir melalui setiap arsitektur yang didukung Library Paging

Bagian selanjutnya dari artikel ini menyajikan rekomendasi untuk mengonfigurasi setiap kasus penggunaan aliran data.

Khusus jaringan

Untuk menampilkan data dari server backend, gunakan Retrofit API versi sinkron untuk memuat informasi ke dalam objek DataSource kustom Anda.

Khusus database

Siapkan RecyclerView untuk mengamati penyimpanan lokal; sebaiknya gunakan library persistensi Room. Dengan begitu, setiap kali data dimasukkan atau diubah di database aplikasi, perubahan tersebut otomatis tercermin dalam RecyclerView yang menampilkan data ini.

Jaringan dan database

Setelah mulai mengamati database, Anda dapat mendeteksi kapan database kehabisan data menggunakan PagedList.BoundaryCallback. Selanjutnya, Anda dapat mengambil lebih banyak item dari jaringan dan memasukkannya ke dalam database. Jika UI Anda mengamati database, hanya itulah yang perlu Anda lakukan.

Menangani error jaringan

Saat menggunakan jaringan untuk mengambil atau mem-page data yang Anda tampilkan melalui Library Paging, jangan memperlakukan jaringan sebagai "tersedia" atau "tidak tersedia" sepanjang waktu, mengingat banyak koneksi yang terputus-putus atau tidak stabil:

  • Server tertentu dapat gagal merespons permintaan jaringan.
  • Perangkat mungkin tersambung ke jaringan yang lambat atau lemah.

Sebagai gantinya, aplikasi Anda harus memeriksa kemungkinan terjadi kegagalan di setiap permintaan dan melakukan pemulihan selancar mungkin jika jaringan tidak tersedia. Misalnya, Anda dapat menyediakan tombol "coba lagi" yang dapat diklik pengguna jika pemuatan ulang data tidak menyelesaikan masalah. Jika error terjadi selama tahap paging data, sebaiknya ulangi permintaan paging secara otomatis.

Mengupdate aplikasi yang ada

Jika aplikasi Anda sudah menggunakan data dari database atau sumber backend, Anda dapat melakukan upgrade langsung ke fungsionalitas yang disediakan oleh Library Paging. Bagian ini menjelaskan cara mengupgrade aplikasi yang memiliki desain umum yang sudah ada.

Solusi paging kustom

Jika Anda menggunakan fungsionalitas kustom untuk memuat sebagian kecil data dari sumber data aplikasi, Anda dapat mengganti logika ini dengan sebagian data dari class PagedList. Instance PagedList menawarkan koneksi bawaan ke sumber data umum. Instance ini juga menyediakan adaptor untuk objek RecyclerView yang dapat Anda sertakan di UI aplikasi Anda.

Data yang dimuat menggunakan daftar, bukan halaman

Jika Anda menggunakan daftar dalam memori sebagai struktur data pendukung untuk adaptor UI, sebaiknya amati pembaruan data menggunakan class PagedList jika jumlah item dalam daftar sangat banyak. Instance PagedList dapat menggunakan LiveData<PagedList> atau Observable<List> untuk meneruskan pembaruan data ke UI aplikasi Anda, sehingga meminimalkan waktu pemuatan dan penggunaan memori. Lebih baik lagi, mengganti objek List dengan objek PagedList di aplikasi Anda tidak memerlukan perubahan apa pun pada struktur UI aplikasi atau logika pembaruan data.

Mengaitkan kursor data dengan tampilan daftar menggunakan CursorAdapter

Aplikasi Anda mungkin menggunakan CursorAdapter untuk mengaitkan data dari Cursor dengan ListView. Dalam hal ini, biasanya Anda perlu melakukan migrasi dari ListView ke RecyclerView sebagai penampung UI daftar aplikasi Anda, lalu mengganti komponen Cursor dengan Room atau PositionalDataSource, bergantung pada apakah instance Cursor mengakses database SQLite atau tidak.

Dalam beberapa situasi, misalnya saat menangani instance Spinner, Anda hanya menyediakan adaptor itu sendiri. Selanjutnya, library mengambil data yang dimuat ke adaptor tersebut dan menampilkan data itu kepada Anda. Dalam situasi ini, ubah jenis data adaptor Anda ke LiveData<PagedList>, lalu gabung daftar ini dalam objek ArrayAdapter sebelum meminta class library meluaskan item ini di UI.

Memuat konten secara asinkron menggunakan AsyncListUtil

Jika Anda menggunakan objek AsyncListUtil untuk memuat dan menampilkan kelompok informasi secara asinkron, Library Paging memungkinkan Anda memuat data dengan lebih mudah:

  • Data Anda tidak harus posisional. Library Paging memungkinkan Anda memuat data langsung dari backend menggunakan kunci yang disediakan oleh jaringan.
  • Data Anda bisa berukuran sangat besar. Dengan Library Paging, Anda dapat memuat data ke dalam halaman hingga tidak ada lagi data yang tersisa.
  • Anda dapat mengamati data dengan lebih mudah. Library Paging dapat menyajikan data yang disimpan oleh ViewModel aplikasi Anda dalam struktur data yang dapat diamati.

Contoh database

Cuplikan kode berikut menunjukkan beberapa kemungkinan untuk membuat semua bagian bekerja sama.

Mengamati data yang di-page menggunakan LiveData

Cuplikan kode berikut menunjukkan kerja sama antara semua bagian. Saat acara konser ditambahkan, dihapus, atau diubah di database, konten di RecyclerView diperbarui secara otomatis dan efisien:

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

Mengamati data yang di-page menggunakan RxJava2

Jika lebih suka menggunakan RxJava2 daripada LiveData, Anda dapat membuat objek Observable atau 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();
        }
    }
    

Selanjutnya, Anda dapat mulai dan berhenti mengamati data menggunakan kode dalam cuplikan berikut:

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

Kode untuk ConcertDao dan ConcertAdapter sama baik untuk solusi berbasis RxJava2 maupun LiveData.

Berikan masukan

Sampaikan masukan dan ide Anda kepada kami melalui resource berikut:

Pelacak masalah
Laporkan masalah agar kami dapat memperbaiki bug.

Referensi lainnya

Untuk mempelajari Library Paging lebih lanjut, pelajari referensi berikut.

Contoh

Codelab

Video