Thu thập dữ liệu được phân trang

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:

ConcertDao

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

ConcertViewModel

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:

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:

ConcertViewModel

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áo nextPage 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