Gromadzenie danych z podziałem na strony

Ten przewodnik stanowi uzupełnienie przeglądu biblioteki na potrzeby wczytywania i opisuje, jak dostosować rozwiązanie do wczytywania danych w aplikacji pod kątem jej architektury.

Utwórz listę możliwą do obserwowania

Zwykle kod interfejsu obserwuje obiekt LiveData<PagedList> (lub, jeśli używasz RxJava2, obiekt Flowable<PagedList> lub Observable<PagedList>), który znajduje się w elemencie ViewModel aplikacji. Ten możliwy do obserwowania obiekt stanowi połączenie między prezentacją a zawartością danych listy aplikacji.

Aby utworzyć jeden z tych obserwowalnych obiektów PagedList, przekaż wystąpienie DataSource.Factory do obiektu LivePagedListBuilder lub RxPagedListBuilder. Obiekt DataSource wczytuje strony dla pojedynczego elementu PagedList. Klasa fabryczna tworzy nowe instancje PagedList w odpowiedzi na aktualizacje treści, takie jak unieważnienia tabel bazy danych i odświeżenia sieci. Biblioteka trwałości sal może dostarczać obiekty DataSource.Factory za Ciebie lub możesz utworzyć własne.

Ten fragment kodu pokazuje, jak utworzyć nowe wystąpienie LiveData<PagedList> w klasie ViewModel aplikacji za pomocą funkcji budowania DataSource.Factory pokoju:

KoncertDao

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

Model widoku Koncertu

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

Zdefiniuj własną konfigurację stronicowania

Aby dokładniej skonfigurować LiveData<PagedList> na potrzeby zaawansowanych przypadków, możesz też zdefiniować własną konfigurację stronicowania. Możesz zdefiniować takie atrybuty:

  • Rozmiar strony: liczba elementów na każdej stronie.
  • Odległość pobierania z wyprzedzeniem: biorąc pod uwagę ostatni element widoczny w interfejsie aplikacji, liczba elementów poza tym ostatnim elementem, które biblioteka stronicowania powinna próbować pobrać z wyprzedzeniem. Ta wartość powinna być kilkakrotnie większa od rozmiaru strony.
  • Obecność obiektu zastępczego: określa, czy interfejs użytkownika wyświetla obiekty zastępcze elementów listy, które nie zostały jeszcze wczytane. Aby poznać zalety i wady używania obiektów zastępczych, dowiedz się, jak postawić je w interfejsie.

Jeśli chcesz mieć większą kontrolę nad tym, kiedy biblioteka stronicowania wczytuje listę z bazy danych aplikacji, przekaż do LivePagedListBuilder niestandardowy obiekt Executor, jak pokazano w tym fragmencie kodu:

Model widoku Koncertu

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

Wybierz prawidłowy typ źródła danych

Ważne jest, aby połączyć się ze źródłem danych, które najlepiej obsługuje strukturę danych źródłowych:

  • Użyj PageKeyedDataSource, jeśli strony wczytujesz poprzedni/następny klucz umieszczony na stronie. Jeśli na przykład pobierasz z sieci posty w mediach społecznościowych, może być konieczne przekazanie tokena nextPage podczas jednego ładowania do kolejnego.
  • Użyj operatora ItemKeyedDataSource, jeśli chcesz użyć danych z elementu N do pobrania elementu N+1. Jeśli na przykład pobierasz komentarze w wątkach do aplikacji do obsługi dyskusji, to aby pobrać treść następnego komentarza, konieczne może być podanie identyfikatora ostatniego komentarza.
  • Użyj metody PositionalDataSource, jeśli chcesz pobierać strony z danymi z dowolnej lokalizacji wybranej w magazynie danych. Ta klasa obsługuje żądania zbioru elementów danych rozpoczynające się od wybranej lokalizacji. Żądanie może na przykład zwrócić 50 elementów danych, których lokalizacja zaczyna się od lokalizacji 1500.

Powiadamiaj, gdy dane są nieprawidłowe

Gdy używasz biblioteki stronicowania, to warstwa danych powiadamia inne warstwy aplikacji, gdy tabela lub wiersz stają się nieaktualne. Aby to zrobić, wywołaj invalidate() z klasy DataSource wybranej dla aplikacji.

Tworzenie własnych źródeł danych

Jeśli korzystasz z niestandardowego rozwiązania do obsługi danych lokalnych lub wczytujesz dane bezpośrednio z sieci, możesz wdrożyć jedną z podklas DataSource. Ten fragment kodu pokazuje źródło danych powiązane z godziną rozpoczęcia danego koncertu:

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

Następnie możesz wczytać te dostosowane dane do obiektów PagedList, tworząc konkretną podklasę DataSource.Factory. Ten fragment kodu pokazuje, jak generować nowe wystąpienia niestandardowego źródła danych zdefiniowanego w poprzednim fragmencie kodu:

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

Jak działają aktualizacje treści

Tworząc dostrzegalne obiekty PagedList, zastanów się, jak działają aktualizacje treści. Jeśli wczytujesz dane bezpośrednio z bazy danych sal, są one automatycznie przekazywane do interfejsu aplikacji.

Gdy korzystasz z interfejsu API z obsługą stron, zwykle dochodzi do interakcji użytkownika, na przykład „przesuń palcem, aby odświeżyć”, co wskazuje na unieważnienie ostatnio używanego interfejsu DataSource. Następnie poproś o nową instancję tego źródła danych. Ten fragment kodu ilustruje odpowiednie działanie:

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

Udostępnij mapowanie danych

Biblioteka stronicowania obsługuje przekształcenia na podstawie elementów i stron w przypadku elementów wczytywanych przez DataSource.

W tym fragmencie kodu połączenie nazwy i daty koncertu jest mapowane na pojedynczy ciąg znaków zawierający zarówno nazwę, jak i datę:

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

Jest to przydatne, jeśli chcesz pakować, przekonwertować lub przygotować elementy po ich wczytaniu. Ponieważ ta praca jest wykonywane na wykonawcy pobierania, możesz wykonać potencjalnie kosztowną pracę, taką jak odczyt z dysku lub wysyłanie zapytania do innej bazy danych.

Prześlij opinię

Podziel się z nami swoją opinią i pomysłami, korzystając z tych zasobów:

Śledzenie problemów
Zgłoś problemy, żebyśmy mogli naprawić błędy.

Dodatkowe materiały

Więcej informacji o bibliotece stronicowania znajdziesz w tych materiałach:

Próbki

Ćwiczenia z programowania

Filmy