Ringkasan library Paging 2 Bagian dari Android Jetpack.

Library Paging membantu Anda memuat dan menampilkan data sedikit demi sedikit. Memuat bagian 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 sampel dari bagian referensi tambahan.

Penyiapan

Untuk mengimpor komponen Paging ke dalam aplikasi Android, tambahkan dependensi berikut ke file build.gradle aplikasi Anda:

Groovy

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
}

Kotlin

dependencies {
  val 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
}

Arsitektur library

Bagian ini menjelaskan dan menampilkan beberapa komponen utama library paging.

PagedList

Komponen utama Library Paging adalah class PagedList, yang memuat bagian data aplikasi Anda, atau page. Seiring meningkatnya jumlah data yang diperlukan, data di-page ke dalam objek PagedList yang ada. Jika terdapat perubahan pada data yang dimuat, instance PagedList baru akan dikirim ke penampung data yang dapat diamati dari objek berbasis LiveData atau RxJava2. Saat objek PagedList dihasilkan, UI aplikasi Anda akan menyajikan 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 snapshot 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 mengatur 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 cara memuat data ke objek PagedList lebih lanjut, 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 data 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 data yang mengalir 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 di perangkat, lalu ke model UI aplikasi Anda. Terkadang, endpoint setiap alur data kehabisan data yang harus dimuat, dan pada saat itu lebih banyak data akan diminta dari komponen yang menyediakan data. Misalnya, jika database pada perangkat kehabisan data, lebih banyak data akan diminta dari server.

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

Bagian selanjutnya dari artikel ini menyajikan rekomendasi untuk mengonfigurasi setiap kasus penggunaan alur 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 tecermin dalam RecyclerView yang menampilkan data ini.

Jaringan dan database

Setelah mulai mengamati database, Anda dapat memproses 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 perlakukan 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 terhubung 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 fungsi 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 gabungkan daftar ini dalam objek ArrayAdapter sebelum meminta class library meng-inflate 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 bergantung pada posisi. Library Paging memungkinkan Anda memuat data langsung dari backend menggunakan kunci yang disediakan oleh jaringan.
  • Data Anda dapat 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 ditampung 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)
        // 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);
        }
    };
}

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

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

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

Memberikan masukan

Sampaikan masukan dan ide Anda kepada kami melalui resource berikut:

Issue tracker
Laporkan masalah agar kami dapat memperbaiki bug.

Referensi lainnya

Untuk mempelajari Library Paging lebih lanjut, pelajari referensi berikut.

Contoh

Codelab

Video