Omówienie biblioteki Stron 2 Część stanowiąca część Androida Jetpack.
Biblioteka stronicowania ułatwia ładowanie i wyświetlanie niewielkich fragmentów danych jednocześnie. Ładowanie częściowych danych na żądanie zmniejsza wykorzystanie przepustowości sieci i zasobów systemowych.
W tym przewodniku znajdziesz kilka koncepcyjnych przykładów biblioteki wraz z omówieniem jej działania. Aby zapoznać się z pełnymi przykładami działania tej biblioteki, zapoznaj się z ćwiczeniami w Codelabs i z przykładami dostępnymi w sekcji z zasobami dodatkowymi.
Skonfiguruj
Aby zaimportować komponenty stronicowania do aplikacji na Androida, dodaj do jej pliku build.gradle
te zależności:
Odlotowe
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 }
Architektura biblioteki
W tej sekcji opisujemy i pokazujemy główne komponenty biblioteki stronicowania.
Lista stron
Kluczowym komponentem biblioteki stronicowania jest klasa PagedList
, która wczytuje fragmenty danych aplikacji, czyli strony. W miarę zapotrzebowania na dane są one przenoszone do istniejącego obiektu PagedList
. Jeśli wczytane dane ulegną zmianie, obiekt oparty na LiveData
lub RxJava2 spowoduje wysłanie do obserwowanego właściciela danych nowego wystąpienia PagedList
. W miarę generowania obiektów PagedList
interfejs aplikacji prezentuje ich zawartość z poszanowaniem cykli życia kontrolerów interfejsu.
Ten fragment kodu pokazuje, jak skonfigurować model widoku aplikacji w celu wczytywania i prezentowania danych za pomocą operatora LiveData
obiektów 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(); } }
Dane
Każda instancja obiektu PagedList
wczytuje aktualny zrzut danych aplikacji z odpowiadającego mu obiektu DataSource
. Przepływy danych z backendu lub bazy danych aplikacji do obiektu PagedList
.
W tym przykładzie do porządkowania danych aplikacji wykorzystano bibliotekę przechowywania danych. Jeśli jednak chcesz przechowywać dane w inny sposób, możesz też udostępnić własną fabrykę źródeł danych.
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(); }
Więcej informacji o wczytywaniu danych do obiektów PagedList
znajdziesz w przewodniku Wczytywania danych stronicowanych.
Interfejs użytkownika
Klasa PagedList
współpracuje z PagedListAdapter
w celu wczytywania elementów do RecyclerView
. Klasy te współpracują ze sobą przy pobieraniu i wyświetlaniu wczytywanych treści, pobieraniu z wyprzedzeniem treści poza widocznym obszarem i animowaniu zmian treści.
Więcej informacji znajdziesz w przewodniku Wyświetlanie list z podziałem na strony.
Obsługują różne architektury danych
Biblioteka stronicowania obsługuje te architektury danych:
- Udostępniane tylko z serwera backendu.
- Są przechowywane tylko w bazie danych na urządzeniu.
- Połączenie innych źródeł z wykorzystaniem bazy danych na urządzeniu jako pamięci podręcznej.
Rysunek 1 pokazuje przepływ danych w każdym z tych scenariuszy architektury. W przypadku rozwiązania działającego tylko w sieci lub baz danych dane trafiają bezpośrednio do modelu interfejsu użytkownika aplikacji. Jeśli stosujesz połączone podejście, dane przepływają z serwera backendu do bazy danych na urządzeniu, a następnie do modelu interfejsu aplikacji. Co jakiś czas w punkcie końcowym każdego przepływu danych brakuje danych do wczytania. Następnie od komponentu, który je dostarczył, wysyła żądanie dodatkowych danych. Jeśli np. zabraknie danych w bazie danych na urządzeniu, do serwera wysyłaj żądanie ich większej ilości.
W pozostałej części tej sekcji znajdziesz zalecenia dotyczące konfigurowania poszczególnych przypadków użycia przepływu danych.
Tylko sieć
Aby wyświetlić dane z serwera backendu, użyj synchronicznej wersji Retrofit API, aby wczytać informacje do własnego niestandardowego obiektu DataSource
.
Tylko baza danych
Skonfiguruj RecyclerView
, aby obserwować pamięć lokalną. Najlepiej jest używać biblioteki trwałości sal. Dzięki temu za każdym razem, gdy dane są wstawiane lub modyfikowane w bazie danych aplikacji, zmiany te są automatycznie odzwierciedlane w usłudze RecyclerView
, która je wyświetla.
Sieć i baza danych
Po rozpoczęciu obserwacji bazy danych możesz użyć polecenia PagedList.BoundaryCallback
, aby wykrywać, kiedy w bazie danych doszło do wyczerpania danych.
Możesz wtedy pobrać więcej elementów z sieci i umieścić je w bazie danych. Jeśli Twój interfejs użytkownika obserwuje bazę danych, nie musisz nic więcej robić.
Obsługa błędów sieci
Jeśli korzystasz z sieci do pobierania lub strony na stronie danych, które wyświetlasz za pomocą biblioteki stron docelowych, pamiętaj, aby nie traktować tej sieci przez cały czas jako „dostępnej” lub „niedostępnej”, ponieważ wiele połączeń jest niestabilnych lub niestabilnych:
- Określony serwer może nie odpowiedzieć na żądanie sieciowe.
- Urządzenie może być połączone z siecią, która działa wolno lub jest słaba.
Zamiast tego aplikacja powinna sprawdzać każde żądanie pod kątem błędów i jak najefektywniej przywracać dane w przypadku, gdy sieć jest niedostępna. Możesz na przykład udostępnić przycisk „Ponów”, który użytkownicy będą mogli wybrać, jeśli odświeżenie danych nie zadziała. Jeśli podczas kroku stronicowania danych wystąpi błąd, najlepiej ponawiać żądania automatycznie.
Aktualizowanie istniejącej aplikacji
Jeśli aplikacja pobiera już dane z bazy danych lub źródła backendu, możesz przejść bezpośrednio na wersję udostępnianą przez bibliotekę stronicowania. W tej sekcji dowiesz się, jak uaktualnić aplikację, która ma wspólny wygląd.
Niestandardowe rozwiązania do stronicowania
Jeśli korzystasz z funkcji niestandardowych do wczytywania niewielkich podzbiorów danych ze źródła danych aplikacji, możesz zastąpić tę logikę tą z klasy PagedList
. Instancje PagedList
oferują wbudowane połączenia z typowymi źródłami danych. Instancje te udostępniają też adaptery obiektów RecyclerView
, które możesz uwzględnić w interfejsie aplikacji.
Dane wczytywane przy użyciu list zamiast stron
Jeśli jako bazowej struktury danych dla adaptacji interfejsu używasz listy w pamięci, rozważ obserwację aktualizacji danych za pomocą klasy PagedList
, jeśli liczba elementów na liście może być bardzo duża. Instancje PagedList
mogą używać LiveData<PagedList>
lub Observable<List>
do przekazywania aktualizacji danych do interfejsu aplikacji, co minimalizuje czasy wczytywania i wykorzystanie pamięci. Co więcej, zastąpienie obiektu List
obiektem PagedList
w aplikacji nie wymaga zmian w strukturze interfejsu aplikacji ani logiki aktualizacji danych.
Powiązywanie kursora danych z widokiem listy za pomocą CursorAdapter
Aplikacja może używać elementu CursorAdapter
do powiązania danych z Cursor
z ListView
. W takim przypadku zwykle trzeba przeprowadzić migrację z ListView
do RecyclerView
jako kontenera interfejsu listy aplikacji, a następnie zastąpić komponent Cursor
elementem Sala lub PositionalDataSource
w zależności od tego, czy instancje Cursor
mają dostęp do bazy danych SQLite.
W niektórych sytuacjach, np. podczas pracy z instancjami Spinner
, udostępniasz tylko sam adapter. Biblioteka pobiera z niego wczytane dane i wyświetla je za Ciebie. W takiej sytuacji zmień typ danych adaptera na LiveData<PagedList>
, a potem umieść tę listę w obiekcie ArrayAdapter
, zanim klasa biblioteki będzie uzupełniać te elementy w interfejsie użytkownika.
Asynchroniczne ładowanie treści za pomocą metody AsyncListUtil
Jeśli używasz obiektów AsyncListUtil
do asynchronicznego wczytywania i wyświetlania grup informacji, biblioteka stronicowania ułatwia ładowanie danych:
- Dane nie muszą być pozycjonowane. Biblioteka stronicowania umożliwia ładowanie danych bezpośrednio z backendu za pomocą kluczy dostarczanych przez sieć.
- Ilość danych może być niewyobrażalnie duża. Za pomocą biblioteki stronicowania możesz wczytywać dane na stronach tak długo, aż znikną wszystkie dane.
- Możesz łatwiej obserwować dane. Biblioteka stronicowania może prezentować dane przechowywane przez model ViewModel aplikacji w obserwowalnej strukturze danych.
Przykłady baz danych
Poniższe fragmenty kodu pokazują kilka możliwych sposobów współdziałania wszystkich elementów.
Obserwowanie danych podziału na strony przy użyciu LiveData
Poniższy fragment kodu przedstawia wszystkie połączone ze sobą elementy. Gdy wydarzenia z koncertów są dodawane, usuwane lub zmieniane w bazie danych, zawartość RecyclerView
jest automatycznie i skutecznie aktualizowana:
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); } }; }
Obserwowanie danych z podziałem na strony przy użyciu RxJava2
Jeśli wolisz użyć RxJava2 zamiast LiveData
, możesz utworzyć obiekt Observable
lub 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(); } }
Możesz uruchomić i zatrzymać obserwację danych za pomocą kodu w tym fragmencie:
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(); } }
Kody ConcertDao
i ConcertAdapter
są takie same w przypadku rozwiązań opartych na RxJava2 i LiveData
.
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
- Android Jetpack: zarządzaj nieskończonymi listami za pomocą RecyclerView i Paging (Google I/O 2018)
- Android Jetpack: pager
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Migrate to Paging 3 (Migracja do strony 3)
- Wyświetlanie list z podziałem na strony
- Gromadzenie danych z podziałem na strony