Hướng dẫn này dựa trên bài viết Tổng quan về Thư viện Paging, trình bày về cách bạn có thể tuỳ chỉnh giải pháp tải dữ liệu để đáp ứng nhu cầu cấu trúc của ứng dụng.
Xây dựng danh sách có thể quan sát
Thông thường, mã giao diện người dùng của bạn quan sát một đối tượng LiveData<PagedList>
(hoặc một đối tượng Flowable<PagedList>
hay Observable<PagedList>
nếu bạn đang sử dụng RxJava2), nằm trong ViewModel
của ứng dụng. Đối tượng có thể quan sát này tạo thành kết nối giữa bản trình bày và nội dung của dữ liệu danh sách trong ứng dụng của bạn.
Để tạo một trong các đối tượng PagedList
có thể quan sát này, hãy truyền một thực thể của DataSource.Factory
đến một đối tượng LivePagedListBuilder
hoặc RxPagedListBuilder
. Đối tượng DataSource
tải các trang cho một PagedList
đơn lẻ. Lớp factory sẽ tạo các thực thể mới của PagedList
để phản hồi nội dung cập nhật, chẳng hạn như các trương hợp làm mất hiệu lực bảng cơ sở dữ liệu và làm mới mạng. Thư viện lưu trữ bền vững Room có thể cung cấp cho bạn các đối tượng DataSource.Factory
, hoặc bạn có thể tạo các đối tượng của riêng mình.
Đoạn mã sau đây cho biết cách tạo một thực thể mới của LiveData<PagedList>
trong lớp ViewModel
của ứng dụng bằng cách sử dụng khả năng xây dựng DataSource.Factory
của Room:
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(); }
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();
Xác định cấu hình phân trang của riêng bạn
Để định cấu hình thêm một LiveData<PagedList>
cho các trường hợp nâng cao, bạn cũng có thể xác định cấu hình phân trang của riêng mình. Đặc biệt, bạn có thể xác định các thuộc tính sau:
- Kích thước trang: Số mục trong mỗi trang.
- Khoảng cách tìm nạp trước: Là số mục mà thư viện Paging sẽ tìm nạp trước sau mục cuối cùng được hiển thị trong giao diện người dùng của ứng dụng. Giá trị này phải lớn hơn kích thước trang vài lần.
- Sự hiện diện của phần giữ chỗ: Xác định xem giao diện người dùng có hiển thị phần giữ chỗ cho các mục trong danh sách chưa tải xong hay không. Để thảo luận về các ưu và khuyết điểm của việc sử dụng trình giữ chỗ, hãy tìm hiểu cách Cung cấp trình giữ chỗ trong giao diện người dùng của bạn.
Nếu bạn muốn kiểm soát chặt hơn thời điểm Thư viện Paging tải danh sách từ cơ sở dữ liệu của ứng dụng, hãy chuyển một đối tượng Executor
tuỳ chỉnh sang LivePagedListBuilder
như trong đoạn mã sau:
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();
Chọn đúng loại nguồn dữ liệu
Điều quan trọng là phải kết nối đến nguồn dữ liệu có thể xử lý tốt nhất cấu trúc dữ liệu nguồn của bạn:
- Hãy sử dụng
PageKeyedDataSource
nếu các trang bạn tải được nhúng khoá trang kế/trang trước. Ví dụ: nếu đang tìm nạp các bài đăng trên mạng xã hội từ mạng, bạn có thể sẽ cần chuyển mã thông báonextPage
từ một lượt tải vào một lượt tải sau đó. - Sử dụng
ItemKeyedDataSource
nếu bạn cần sử dụng dữ liệu từ mục N để tìm nạp mục N+1. Ví dụ: nếu đang tìm nạp bình luận theo luồng cho một ứng dụng thảo luận, bạn có thể cần chuyển mã của bình luận cuối cùng để nạp được nội dung của bình luận tiếp theo. - Sử dụng
PositionalDataSource
nếu bạn cần tìm nạp các trang dữ liệu tại bất kỳ vị trí nào mà bạn chọn trong kho dữ liệu của mình. Lớp này hỗ trợ việc yêu cầu một tập hợp các mục dữ liệu bắt đầu tại bất kỳ vị trí nào mà bạn chọn. Ví dụ: yêu cầu có thể trả về 50 mục dữ liệu bắt đầu từ vị trí 1500.
Thông báo khi dữ liệu không hợp lệ
Khi sử dụng Thư viện Paging, lớp dữ liệu (data layer) sẽ thông báo cho các lớp (layer) khác trong ứng dụng khi một bảng hoặc hàng bị lỗi thời. Để làm điều đó, hãy gọi invalidate()
trong lớp DataSource
mà bạn đã chọn cho ứng dụng của mình.
Xây dựng nguồn dữ liệu của riêng bạn
Nếu bạn sử dụng giải pháp dữ liệu cục bộ tuỳ chỉnh hoặc tải dữ liệu trực tiếp qua mạng, bạn có thể triển khai một trong các lớp con DataSource
. Đoạn mã sau đây cho thấy nguồn dữ liệu được xác định dựa trên thời gian bắt đầu của một buổi hoà nhạc cụ thể:
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); }
Sau đó, bạn có thể tải dữ liệu tuỳ chỉnh này vào các đối tượng PagedList
bằng cách tạo một lớp con cụ thể của DataSource.Factory
. Đoạn mã sau đây cho biết cách tạo các phiên bản mới của nguồn dữ liệu tuỳ chỉnh đã xác định trong đoạn mã trước đó:
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; } }
Cân nhắc cách hoạt động của việc cập nhật nội dung
Khi bạn tạo các đối tượng PagedList
có thể quan sát được, hãy cân nhắc cách hoạt động của việc cập nhật nội dung. Nếu bạn đang tải dữ liệu trực tiếp từ một cơ sở dữ liệu Room thì hệ thống sẽ tự động gửi thông tin cập nhật đến giao diện người dùng của ứng dụng.
Thông thường khi sử dụng API mạng được phân trang, sẽ có một hoạt động tương tác người dùng, chẳng hạn như "vuốt để làm mới", đóng vai trò là tín hiệu để vô hiệu hoá DataSource
mà bạn đã dùng gần đây nhất. Sau đó, bạn yêu cầu một phiên bản mới của nguồn dữ liệu đó. Hành vi này được minh hoạ trong đoạn mã sau:
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(); } }
Cung cấp ánh xạ dữ liệu
Thư viện phân trang hỗ trợ các biến đổi dựa trên mục và dựa trên trang được tải bởi DataSource
.
Trong đoạn mã sau đây, tổ hợp tên và ngày diễn ra của buổi hoà nhạc được ánh xạ tới một chuỗi duy nhất chứa cả tên và ngày:
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(); } }
Điều này sẽ hữu ích nếu bạn muốn bao bọc, chuyển đổi hoặc chuẩn bị các mục sau khi tải. Vì công việc này được thực hiện trên trình thực thi tìm nạp, bạn có thể làm được các công việc có khả năng tốn nhiều tài nguyên, chẳng hạn như đọc từ ổ đĩa hoặc truy vấn một cơ sở dữ liệu riêng.
Gửi phản hồi
Hãy chia sẻ phản hồi và ý kiến của bạn với chúng tôi thông qua các tài nguyên sau:
- Công cụ theo dõi lỗi
- Báo cáo sự cố để chúng tôi có thể sửa lỗi.
Tài nguyên khác
Để tìm hiểu thêm về Thư viện Paging, hãy tham khảo các tài nguyên sau.
Mẫu
Lớp học lập trình
Video
- Android Jetpack: quản lý danh sách vô hạn bằng RecyclerView và Paging (Google I/O '18)
- Android Jetpack: Paging
Đề xuất cho bạn
- Lưu ý: văn bản có đường liên kết sẽ hiện khi JavaScript tắt
- Di chuyển sang Paging 3
- Tổng quan về thư viện Paging 2
- Hiển thị danh sách được phân trang