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:

Trendy

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
Figura 1. Il flusso di dati attraverso ciascuna delle architetture supportate dalla libreria di paging

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