Panoramica della libreria di Paging 2 Parte di Android Jetpack.
La libreria di paging consente di caricare e visualizzare piccoli blocchi di dati alla volta. Il caricamento di dati parziali on demand riduce l'utilizzo della larghezza di banda di rete e delle risorse di sistema.
Questa guida fornisce diversi esempi concettuali della libreria, oltre a una panoramica del suo funzionamento. Per visualizzare esempi completi del funzionamento di questa libreria, prova il codelab e gli esempi nella sezione risorse aggiuntive.
Configurazione
Per importare i componenti di Paging nella tua app Android, aggiungi le seguenti
dipendenze al file build.gradle
dell'app:
Alla moda
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 }
Architettura delle biblioteche
Questa sezione descrive e mostra i componenti principali della libreria di paging.
Elenco pagine
Il componente chiave della libreria di Paging è la classe PagedList
, che carica blocchi di dati o pagine della tua app. Poiché sono necessari più dati, vengono
pagati nell'oggetto PagedList
esistente. Se i dati caricati cambiano, una nuova istanza di PagedList
viene emessa per il titolare dei dati osservabile da un oggetto basato su LiveData
o RxJava2. Durante la generazione degli oggetti PagedList
, l'interfaccia utente dell'app ne presenta i contenuti, rispettando il cicli di vita dei controller UI.
Il seguente snippet di codice mostra come configurare il modello di vista dell'app per caricare e presentare i dati utilizzando un contenitore LiveData
di oggetti 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(); } }
Dati
Ogni istanza di PagedList
carica
uno snapshot aggiornato dei dati della tua app dall'oggetto DataSource
corrispondente. I dati vengono inviati dal backend o dal database dell'app all'oggetto PagedList
.
L'esempio seguente utilizza la libreria di persistenza della stanza per organizzare i dati dell'app, ma se vuoi archiviare i dati utilizzando un altro mezzo, puoi anche fornire il tuo dati di fabbrica dell'origine dati.
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(); }
Per saperne di più su come caricare i dati negli oggetti PagedList
, consulta la guida su come caricare i dati impaginati.
Interfaccia utente
Il corso PagedList
funziona con un
PagedListAdapter
per caricare elementi in una
RecyclerView
. Questi corsi interagiscono per recuperare e visualizzare i contenuti man mano che vengono caricati, precaricando i contenuti non visibili e animando le modifiche ai contenuti.
Per ulteriori informazioni, consulta la guida su come visualizzare elenchi impaginati.
Supportare diverse architetture di dati
La Libreria Paging supporta le seguenti architetture di dati:
- Pubblicato solo da un server di backend.
- Archiviati solo in un database on-device.
- Una combinazione delle altre origini, che utilizza il database sul dispositivo come cache.
La Figura 1 mostra il flusso di dati in ciascuno di questi scenari di architettura. Nel caso di una soluzione solo di rete o solo database, i dati passano direttamente al modello di UI della tua app. Se utilizzi un approccio combinato, i dati passano dal server di backend, in un database sul dispositivo e poi nel modello di UI dell'app. Di tanto in tanto, l'endpoint di ciascun flusso di dati esaurisce i dati da caricare e a quel punto richiede più dati al componente che ha fornito i dati. Ad esempio, quando un database sul dispositivo esaurisce i dati, richiede più dati al server.
![Diagrammi dei flussi di dati](https://developer.android.google.cn/static/topic/libraries/architecture/images/paging-library-data-flow.png?authuser=6&hl=it)
Il resto di questa sezione fornisce suggerimenti per la configurazione di ciascun caso d'uso del flusso di dati.
Solo rete
Per visualizzare i dati di un server di backend, usa la versione sincrona dell'API Retrofit per caricare le informazioni nel tuo oggetto DataSource
personalizzato.
Solo database
Configura il tuo RecyclerView
in modo da osservare lo spazio di archiviazione locale, preferibilmente utilizzando la libreria di persistenza della stanza. In questo modo, ogni volta che i dati vengono inseriti o modificati nel database dell'app, queste modifiche vengono applicate automaticamente nel RecyclerView
in cui vengono visualizzati i dati.
Rete e database
Dopo aver iniziato a osservare il database, puoi esaminare quando il database ha esaurito i dati utilizzando PagedList.BoundaryCallback
.
Puoi quindi recuperare altri elementi dalla tua rete e inserirli nel
database. Se la tua UI osserva il database, non devi fare altro.
Gestire gli errori di rete
Quando utilizzi una rete per recuperare o paginare i dati visualizzati tramite la libreria di pagine cercate, è importante non considerare la rete sempre come "disponibile" o "non disponibile", poiché molte connessioni sono intermittenti o instabili:
- Un server specifico potrebbe non rispondere a una richiesta di rete.
- Il dispositivo potrebbe essere connesso a una rete lenta o debole.
L'app dovrebbe invece controllare ogni richiesta per verificare la presenza di errori ed eseguire il ripristino nel modo più controllato possibile nei casi in cui la rete non sia disponibile. Ad esempio, puoi fornire agli utenti un pulsante "Riprova" da selezionare se il passaggio di aggiornamento dei dati non funziona. Se si verifica un errore durante il passaggio di paging dei dati, ti consigliamo di riprovare automaticamente a inviare le richieste di paging.
Aggiornare l'app esistente
Se la tua app consuma già dati da un database o da un'origine di backend, è possibile eseguire l'upgrade direttamente alla funzionalità fornita dalla libreria di Paging. Questa sezione mostra come eseguire l'upgrade di un'app con un design esistente in comune.
Soluzioni di paging personalizzate
Se utilizzi una funzionalità personalizzata per caricare piccoli sottoinsiemi di dati dall'origine dati della tua app, puoi sostituire questa logica con quella della classe PagedList
. Le istanze di
PagedList
offrono connessioni integrate a origini dati comuni. Queste istanze forniscono anche adattatori per gli oggetti RecyclerView
che potresti includere nell'interfaccia utente dell'app.
Dati caricati utilizzando elenchi anziché pagine
Se utilizzi un elenco in memoria come struttura dei dati di supporto per l'adattatore della UI, valuta la possibilità di osservare gli aggiornamenti dei dati utilizzando una classe PagedList
se il numero di elementi nell'elenco diventa grande. Le istanze di PagedList
possono utilizzare
LiveData<PagedList>
o
Observable<List>
per passare aggiornamenti dei dati all'interfaccia utente della tua app, riducendo al minimo i tempi di caricamento
e l'utilizzo della memoria. Meglio ancora, la sostituzione di un oggetto List
con un oggetto PagedList
nella tua app non richiede alcuna modifica alla struttura dell'interfaccia utente o alla logica di aggiornamento dei dati dell'app.
Associare un cursore di dati a una visualizzazione elenco utilizzando CursorAdapter
La tua app potrebbe utilizzare un elemento CursorAdapter
per associare i dati di una Cursor
con una
ListView
. In questo caso, di solito devi eseguire la migrazione da ListView
a RecyclerView
come contenitore dell'interfaccia utente dell'elenco dell'app, quindi sostituire il componente Cursor
con Room o PositionalDataSource
, a seconda che le istanze di Cursor
accedano a un database SQLite.
In alcune situazioni, ad esempio quando lavori con le istanze di Spinner
, fornisci solo l'adattatore stesso. Una libreria prende i dati caricati nell'adattatore e li visualizza automaticamente. In questi casi, modifica il tipo di dati dell'adattatore in LiveData<PagedList>
, quindi aggrega questo elenco in un oggetto ArrayAdapter
prima di tentare di far aumentare questi elementi in una UI da parte di una classe della libreria.
Carica i contenuti in modo asincrono utilizzando AsyncListUtil
Se utilizzi oggetti AsyncListUtil
per caricare e visualizzare gruppi di informazioni in modo asincrono, la libreria di paging consente di caricare i dati più facilmente:
- I tuoi dati non devono necessariamente essere orientati al posizionamento. La libreria di paging consente di caricare i dati direttamente dal backend utilizzando le chiavi fornite dalla rete.
- Le dimensioni dei tuoi dati possono essere innumerevoli. Utilizzando la libreria di paging, puoi caricare dati nelle pagine fino a quando non ci sono più dati.
- Puoi osservare i tuoi dati più facilmente. La libreria di paging può presentare i dati archiviati nel ViewModel dell'app in una struttura di dati osservabile.
Esempi di database
I seguenti snippet di codice mostrano diversi modi possibili per far funzionare tutti i componenti.
Osservazione dei dati impaginati utilizzando LiveData
Lo snippet di codice riportato di seguito mostra che tutte le parti funzionano insieme. Man mano che gli eventi dei concerti vengono aggiunti, rimossi o modificati nel database, i contenuti della RecyclerView
vengono aggiornati in modo automatico ed efficiente:
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); } }; }
Osservazione dei dati impaginati utilizzando RxJava2
Se preferisci utilizzare RxJava2 anziché LiveData
, puoi creare un oggetto Observable
o 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(); } }
Puoi quindi avviare e interrompere l'osservazione dei dati utilizzando il codice nello snippet seguente:
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(); } }
Il codice per ConcertDao
e ConcertAdapter
è lo stesso per una soluzione basata su RxJava2 e per una soluzione basata su LiveData
.
Fornisci feedback
Condividi il tuo feedback e le tue idee con noi attraverso queste risorse:
- Issue Tracker
- Segnala i problemi per consentirci di correggerli.
Risorse aggiuntive
Per scoprire di più sulla libreria di paging, consulta le risorse seguenti.
Samples
Codelab
Video
- Android Jetpack: gestire elenchi infiniti con RecyclerView e Paging (Google I/O '18)
- Android Jetpack: paging
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Eseguire la migrazione a Paging 3
- Visualizza elenchi impaginati
- Raccogliere dati su pagine