Raccogli i dati delle pagine

Questa guida si basa sulla panoramica della libreria di pagine pacchetti e illustra come è possibile personalizzare la soluzione di caricamento dei dati della tua app in base alle esigenze di architettura della tua app.

Costruire un elenco osservabile

In genere, il codice UI osserva un oggetto LiveData<PagedList> (oppure, se utilizzi RxJava2, un oggetto Flowable<PagedList> o Observable<PagedList>), che risiede nella sezione ViewModel della tua app. Questo oggetto osservabile forma una connessione tra la presentazione e i contenuti dei dati degli elenchi della tua app.

Per creare uno di questi oggetti PagedList osservabili, passa un'istanza di DataSource.Factory a un oggetto LivePagedListBuilder o RxPagedListBuilder. Un oggetto DataSource carica le pagine per un singolo PagedList. La classe Factory crea nuove istanze di PagedList in risposta agli aggiornamenti dei contenuti, come invalidità delle tabelle di database e aggiornamenti della rete. La libreria di persistenza della stanza può fornirti oggetti DataSource.Factory oppure puoi crearne di personalizzati.

Lo snippet di codice riportato di seguito mostra come creare una nuova istanza di LiveData<PagedList> nella classe ViewModel della tua app utilizzando le funzionalità di creazione della stanza DataSource.Factory:

Dao Concerto

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

Definisci la tua configurazione di paging

Per configurare ulteriormente una LiveData<PagedList> per casi avanzati, puoi anche definire una configurazione di paging personalizzata. In particolare, puoi definire i seguenti attributi:

  • Dimensioni pagina: il numero di elementi in ogni pagina.
  • Distanza di precaricamento: dato l'ultimo elemento visibile nell'interfaccia utente di un'app, il numero di elementi oltre a quest'ultimo che la libreria di paging deve tentare di recuperare in anticipo. Questo valore deve essere di diverse volte superiore alle dimensioni della pagina.
  • Presenza segnaposto: determina se nell'interfaccia utente vengono visualizzati dei segnaposto per gli elementi dell'elenco il cui caricamento non è ancora stato completato. Per una discussione sui vantaggi e sugli svantaggi dell'utilizzo dei segnaposto, scopri come fornire segnaposto nella UI.

Se vuoi avere un maggiore controllo su quando la libreria di paging carica un elenco dal database della tua app, trasmetti un oggetto Executor personalizzato a LivePagedListBuilder, come mostrato nel seguente snippet di codice:

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

Scegliere il tipo di origine dati corretto

È importante connettersi all'origine dati che gestisce al meglio la struttura dei dati di origine:

  • Utilizza PageKeyedDataSource se le pagine caricate incorporano le chiavi Successiva/Precedente. Ad esempio, se recuperi post dai social media dalla rete, potresti dover passare un token nextPage da un caricamento a uno successivo.
  • Utilizza ItemKeyedDataSource se devi utilizzare i dati dell'elemento N per recuperare l'elemento N+1. Ad esempio, se recuperi i commenti in thread per un'app di discussione, potresti dover passare l'ID dell'ultimo commento per recuperare i contenuti di quello successivo.
  • Utilizza PositionalDataSource se devi recuperare pagine di dati da qualsiasi posizione nel datastore. Questa classe supporta la richiesta di un set di elementi di dati a partire da qualsiasi località selezionata. Ad esempio, la richiesta potrebbe restituire i 50 elementi di dati a partire dalla posizione 1500.

Invia una notifica quando i dati non sono validi

Quando utilizzi la libreria di paging, spetta al livello dati notificare gli altri livelli dell'app quando una tabella o una riga è obsoleta. Per farlo, chiama invalidate() dal corso DataSource che hai scelto per la tua app.

Crea le tue origini dati

Se utilizzi una soluzione dati locali personalizzata o se carichi i dati direttamente da una rete, puoi implementare una delle sottoclassi DataSource. Il seguente snippet di codice mostra un'origine dati derivata dall'ora di inizio di un determinato concerto:

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

Potrai quindi caricare questi dati personalizzati in oggetti PagedList creando una sottoclasse concreta di DataSource.Factory. Il seguente snippet di codice mostra come generare nuove istanze dell'origine di dati personalizzata definita nello snippet di codice precedente:

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

Valuta come funzionano gli aggiornamenti dei contenuti

Quando crei oggetti PagedList osservabili, valuta come funzionano gli aggiornamenti dei contenuti. Se carichi i dati direttamente da un database delle stanze, gli aggiornamenti vengono inviati automaticamente all'interfaccia utente della tua app.

Quando utilizzi un'API di rete impaginata, in genere si verifica un'interazione utente, ad esempio "scorri per aggiornare", che serve da indicatore per invalidare l'elemento DataSource che hai utilizzato più di recente. Puoi quindi richiedere una nuova istanza di quell'origine dati. Lo snippet di codice riportato di seguito illustra questo comportamento:

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

Fornisci la mappatura dei dati

La Libreria di paging supporta le trasformazioni basate su elementi e pagine degli elementi caricati da una classe DataSource.

Nel seguente snippet di codice, una combinazione di nome e data del concerto viene mappata a una singola stringa contenente sia il nome che la data:

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

Questo può essere utile se vuoi aggregare, convertire o preparare gli elementi dopo che sono stati caricati. Poiché questa operazione viene eseguita sull'esecutore di recupero, puoi svolgere operazioni potenzialmente costose, come leggere dal disco o eseguire query su un database separato.

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