In questo tutorial, ti mostrerò come utilizzare la libreria di paging dai componenti di architettura Android con un database supportato da room in un'app Android.
Scoprirai come utilizzare la libreria Paging per caricare in modo efficiente set di dati di grandi dimensioni da un database di Room-backed, dando ai tuoi utenti un'esperienza più fluida mentre scorri in un RecyclerView.
Per poter seguire questo tutorial, avrai bisogno di:
LiveData
e database di camera)Se non hai imparato a conoscere i componenti dell'architettura, ti consigliamo vivamente di dare un'occhiata alle nostre fantastiche serie su Android Architecture Components di Tin Megali. Assicurati di andare a fare immersioni!
Un esempio di progetto per questo tutorial può essere trovato sul nostro repository GitHub in modo da poter seguire facilmente.
La libreria di paging è un'altra libreria aggiunta ai componenti di architettura. La libreria aiuta a gestire in modo efficiente il caricamento e la visualizzazione di un grande set di dati nel RecyclerView
. Secondo i documenti ufficiali:
La libreria di paging semplifica il caricamento dei dati in modo graduale e lineare all'interno dell'appRecyclerView
.
Se una qualsiasi parte della tua app Android visualizza un set di dati di grandi dimensioni da un'origine dati locale o remota ma ne visualizza solo una parte alla volta, dovresti prendere in considerazione l'utilizzo della libreria Paging. Ciò contribuirà a migliorare le prestazioni della tua app!
Ora che hai visto un'introduzione alla libreria di Paging, potresti chiedere, perché usarlo? Ecco alcuni motivi per cui dovresti considerare di usarlo per caricare grandi insiemi di dati in a RecyclerView
.
Non sarà efficiente quando si lavora con una grande quantità di dati, poiché l'origine dati sottostante recupera tutti i dati, anche se solo un sottoinsieme di tali dati verrà visualizzato all'utente. In una situazione del genere, dovremmo prendere in considerazione il paging dei dati.
Avvia il tuo Android Studio 3 e crea un nuovo progetto con un'attività vuota chiamata Attività principale
. Assicurati di controllare Include il supporto Kotlin.
Dopo aver creato un nuovo progetto, aggiungi le seguenti dipendenze nel tuo build.gradle. In questo tutorial, stiamo usando l'ultima versione della libreria Paging 1.0.1, mentre Room è la 1.1.1 (al momento della stesura di questo documento).
dependencies implementation fileTree (dir: 'libs', include: ['* .jar']) implementazione "android.arch.persistence.room:runtime:1.1.1" kapt "android.arch.persistence.room:compiler:1.1 .1 "implementazione" android.arch.paging: runtime: 1.0.1 "implementazione" com.android.support:recyclerview-v7:27.1.1 "
Questi artefatti sono disponibili nel repository Maven di Google.
allprojects repositories google () jcenter ()
Aggiungendo le dipendenze, abbiamo insegnato a Gradle come trovare la libreria. Assicurati di ricordare di sincronizzare il progetto dopo averlo aggiunto.
Crea una nuova classe di dati di Kotlin Persona
. Per semplicità, il nostro Persona
entità ha solo due campi:
id
)nome
)Inoltre, includi a accordare(
metodo che restituisce semplicemente il nome
.
import android.arch.persistence.room.Entity import android.arch.persistence.room.PrimaryKey @Entity (tableName = "persons") classe di dati Person (@PrimaryKey val id: String, nome val: String) override fun toString ( ) = nome
Come sapete, per noi di accedere ai dati della nostra app con la libreria Room, abbiamo bisogno di oggetti di accesso ai dati (DAO). Nel nostro caso, abbiamo creato un PersonDao
.
import android.arch.lifecycle.LiveData import android.arch.paging.DataSource import android.arch.persistence.room.Dao import android.arch.persistence.room.Delete import android.arch.persistence.room.Insert import android.arch .persistence.room.Query @DaoDao PersonDao @Query ("SELECT * FROM persons") fun getAll (): LiveData> @Query ("SELECT * FROM persons") fun getAllPaged (): DataSource.Factory
@Inserisci divertente insertAll (persone: Elenco ) @Delete fun delete (person: Person)
Nel nostro PersonDao
classe, ne abbiamo due @query
metodi. Uno di loro è prendi tutto()
, che restituisce a LiveData
che contiene un elenco di Persona
oggetti. L'altro è getAllPaged ()
, che restituisce a DataSource.Factory
.
Secondo i documenti ufficiali, il Fonte di dati
la classe è:
Classe base per il caricamento di pagine di dati di istantanee in a PagedList
.
UN PagedList
è un tipo speciale di Elenco
per mostrare i dati pagati in Android:
UNPagedList
è una lista che carica i suoi dati in blocchi (pagine) da aFonte di dati
. Gli oggetti sono accessibili conottenere (int)
, e un ulteriore caricamento può essere attivato conloadAround (int)
.
Abbiamo chiamato il Fabbrica
metodo statico nel Fonte di dati
class, che funge da factory (creazione di oggetti senza dover specificare la classe esatta dell'oggetto che verrà creato) per il Fonte di dati
. Questo metodo statico accetta due tipi di dati:
Fonte di dati
. Nota che per una query Room, le pagine sono numerate, quindi utilizziamo Numero intero
come il tipo di identificativo della pagina. È possibile avere pagine "digitate" usando la libreria Paging, ma Room non lo offre al momento. Fonte di dati
S.Ecco cosa è la nostra classe di database Room AppDatabase
sembra:
import android.arch.persistence.db.SupportSQLiteDatabase import android.arch.persistence.room.Database import android.arch.persistence.room.Room import android.arch.persistence.room.RoomDatabase import android.content.Context import androidx.work .OneTimeWorkRequestBuilder import androidx.work.WorkManager import com.chikeandroid.pagingtutsplus.utils.DATABASE_NAME import com.chikeandroid.pagingtutsplus.workers.SeedDatabaseWorker @Database (entities = [Person :: class], version = 1, exportSchema = false) classe astratta AppDatabase: RoomDatabase () abstract fun personDao (): oggetto companion PersonDao // Per istanza Singleton @Volatile istanza var privata: AppDatabase? = null divertente getInstance (context: Context): AppDatabase return instance?: synchronized (this) instance?: buildDatabase (context) .also instance = it private fun BuildDatabase (context: Context): AppDatabase return Room .databaseBuilder (context, AppDatabase :: class.java, DATABASE_NAME) .addCallback (oggetto: RoomDatabase.Callback () override fun onCreate (db: SupportSQLiteDatabase) super.onCreate (db) val request = OneTimeWorkRequestBuilder() .build () WorkManager.getInstance () ?. enqueue (richiesta)) .build ()
Qui abbiamo creato una singola istanza del nostro database e pre-popolata con i dati utilizzando la nuova API WorkManager. Si noti che i dati pre-compilati sono solo un elenco di 1.000 nomi (immergersi nel codice sorgente di esempio fornito per ulteriori informazioni).
Affinché la nostra interfaccia utente memorizzi, osservi e serva i dati in modo consapevole dal punto di vista del ciclo di vita, abbiamo bisogno di un ViewModel
. Nostro PersonsViewModel
, che estende il AndroidViewModel
classe, funzionerà come nostro ViewModel
.
import android.app.Application import android.arch.lifecycle.AndroidViewModel import android.arch.lifecycle.LiveData import android.arch.paging.DataSource import android.arch.paging.LivePagedListBuilder import android.arch.paging.PagedList import com.chikeandroid .pagingtutsplus.data.AppDatabase import com.chikeandroid.pagingtutsplus.data.Person class PeopleViewModel constructor (application: Application): AndroidViewModel (applicazione) private var personsLiveData: LiveData> init val factory: DataSource.Factory = AppDatabase.getInstance (getApplication ()). PersonDao (). GetAllPaged () val pagedListBuilder: LivePagedListBuilder = LivePagedListBuilder (factory, 50) personsLiveData = pagedListBuilder.build () fun getPersonsLiveData () = personsLiveData
In questa classe, abbiamo un singolo campo chiamato personsLiveData
. Questo campo è semplicemente un LiveData
che detiene un PagedList
di Persona
oggetti. Perché questo è un LiveData
, la nostra interfaccia utente (il Attività
o Frammento
) osserverà questi dati chiamando il metodo getter getPersonsLiveData ()
.
Abbiamo inizializzato personsLiveData
dentro il dentro
bloccare. All'interno di questo blocco, otteniamo il DataSource.Factory
chiamando il AppDatabase
singleton per il PersonDao
oggetto. Quando otteniamo questo oggetto, chiamiamo getAllPaged ()
.
Quindi creiamo a LivePagedListBuilder
. Ecco cosa dice la documentazione ufficiale su a LivePagedListBuilder
:
Costruttore perLiveData
, dato aDataSource.Factory
e aPagedList.Config
.
Forniamo il suo costruttore a DataSource.Factory
come primo argomento e dimensione della pagina come secondo argomento (nel nostro caso, la dimensione della pagina sarà 50). In genere, è necessario scegliere una dimensione superiore al numero massimo che è possibile visualizzare contemporaneamente all'utente. Alla fine, chiamiamo costruire()
costruire e tornare a noi a LiveData
.
Per mostrare il nostro PagedList
dati in a RecyclerView
, abbiamo bisogno di un PagedListAdapter
. Ecco una chiara definizione di questa classe dai documenti ufficiali:
RecyclerView.Adapter
classe base per la presentazione di dati paginati daPagedList
s in aRecyclerView
.
Quindi creiamo a PersonAdapter
quello si estende PagedListAdapter
.
importare android.arch.paging.PagedListAdapter importare android.content.Context importare android.support.v7.widget.RecyclerView importare android.view.LayoutInflater importare android.view.View importare android.view.ViewGroup importare android.widget.TextView import com .chikeandroid.pagingtutsplus.R import com.chikeandroid.pagingtutsplus.data.Person import kotlinx.android.synthetic.main.item_person.view. * class PersonAdapter (val context: Context): PagedListAdapter(PersonDiffCallback ()) override fun onBindViewHolder (holderPerson: PersonViewHolder, position: Int) var person = getItem (posizione) if (person == null) holderPerson.clear () else holderPerson.bind (person) override fun onCreateViewHolder (parent: ViewGroup, viewType: Int): PersonViewHolder return PersonViewHolder (LayoutInflater.from (context) .inflate (R.layout.item_person, parent, false)) class PersonViewHolder (view: View): RecyclerView.ViewHolder (visualizza) var tvName: TextView = view.name fun bind (person: Person) tvName.text = person.name fun clear () tvName.text = null
PagedListAdapter
è usato come qualsiasi altra sottoclasse di RecyclerView.Adapter
. In altre parole, devi implementare i metodi onCreateViewHolder ()
e onBindViewHolder ()
.
Per estendere il PagedListAdapter
classe astratta, dovrai fornire, nel suo costruttore, il tipo di PageLists
(questa dovrebbe essere una semplice vecchia classe Java: un POJO) e anche una classe che estende il ViewHolder
che verrà utilizzato dall'adattatore. Nel nostro caso, l'abbiamo dato Persona
e PersonViewHolder
come primo e secondo argomento rispettivamente.
Nota che PagedListAdapter
richiede di passarlo a DiffUtil.ItemCallback
al PageListAdapter
costruttore. DiffUtil
è un RecyclerView
classe di utilità che può calcolare la differenza tra due elenchi e generare un elenco di operazioni di aggiornamento che converte la prima lista nella seconda. ItemCallback
è una classe statica astratta interna (all'interno DiffUtil
) utilizzato per calcolare il diff tra due elementi non nulli in un elenco.
In particolare, forniamo PersonDiffCallback
a noi PagedListAdapter
costruttore.
import android.support.v7.util.DiffUtil import com.chikeandroid.pagingtutsplus.data.Person class PersonDiffCallback: DiffUtil.ItemCallback() override fun areItemsTheSame (oldItem: Person, newItem: Person): Boolean return oldItem.id == newItem.id override fun areContentsTheSame (oldItem: Person ?, newItem: Person?): Boolean return oldItem == newItem
Perché stiamo implementando DiffUtil.ItemCallback
, dobbiamo implementare due metodi: areItemsTheSame ()
e areContentsTheSame ()
.
areItemsTheSame
viene chiamato per verificare se due oggetti rappresentano lo stesso oggetto. Ad esempio, se i tuoi articoli hanno ID univoci, questo metodo dovrebbe verificare la loro uguaglianza di id. Questo metodo restituisce vero
se i due elementi rappresentano lo stesso oggetto o falso
se sono diversi.areContentsTheSame
viene chiamato per verificare se due articoli hanno gli stessi dati. Questo metodo restituisce vero
se il contenuto degli articoli è lo stesso o falso
se sono diversi.Nostro PersonViewHolder
la classe interiore è solo un tipico RecyclerView.ViewHolder
. È responsabile del binding dei dati secondo necessità dal nostro modello ai widget per una riga nel nostro elenco.
class PersonAdapter (val context: Context): PagedListAdapter(PersonDiffCallback ()) // ... class PersonViewHolder (view: View): RecyclerView.ViewHolder (view) var tvName: TextView = view.name fun bind (person: Person) tvName.text = person.name fun clear () tvName.text = null
Nel nostro onCreate ()
della nostra Attività principale
, abbiamo semplicemente fatto quanto segue:
ViewModel
campo usando la classe di utilità ViewModelProviders
PersonAdapter
RecyclerView
PersonAdapter
al RecyclerView
LiveData
e inviare il PagedList
oggetti oltre al PersonAdapter
invocando submitList ()
import android.arch.lifecycle.Observer import android.arch.lifecycle.ViewModelProviders import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.RecyclerView import com.chikeandroid.pagingtutsplus.adapter .PersonAdapter import com.chikeandroid.pagingtutsplus.viewmodels.PersonsViewModel class MainActivity: AppCompatActivity () private lateinit var viewModel: PeopleViewModel sovrascrive fun onCreate (savedInstanceState: Bundle?) Super.onCreate (savedInstanceState) setContentView (R.layout.activity_main) viewModel = ViewModelProviders.of (this) .get (PersonsViewModel :: class.java) val adapter = PersonAdapter (this) findViewById(R.id.name_list) .adapter = adapter subscribeUi (adattatore) private fun subscribeUi (adattatore: PersonAdapter) viewModel.getPersonLiveData (). Osserva (this, Observer names -> if (names! = Null) adapter.submitList (nomi))
Infine, quando esegui l'app, ecco il risultato:
Durante lo scorrimento, Room è in grado di prevenire gli spazi vuoti caricando 50 articoli alla volta e rendendoli disponibili per il nostro PersonAdapter
, che è una sottoclasse di PagingListAdapter
. Ma nota che non tutte le fonti di dati verranno caricate rapidamente. La velocità di caricamento dipende anche dalla potenza di elaborazione del dispositivo Android.
Se stai usando o vuoi usare RxJava nel tuo progetto, la libreria di paging include un altro utile artefatto: RxPagedListBuilder
. Tu usi questo artefatto invece di LivePagedListBuilder
per il supporto RxJava.
Devi semplicemente creare un'istanza di RxPagedListBuilder
, fornendo gli stessi argomenti che vorresti per LivePagedListBuilder
-il DataSource.Factory
e la dimensione della pagina. Quindi chiama buildObservable ()
o buildFlowable ()
restituire un Osservabile
o fluido
per il tuo PagedList
rispettivamente.
Per fornire esplicitamente il Scheduler
per il lavoro di caricamento dei dati, si chiama il metodo setter setFetchScheduler ()
. Fornire anche il Scheduler
per fornire il risultato (ad es. AndroidSchedulers.mainThread ()
), basta chiamare setNotifyScheduler ()
. Di default, setNotifyScheduler ()
il valore predefinito è il thread dell'interfaccia utente, mentre setFetchScheduler ()
il valore predefinito è il pool di thread I / O.
In questo tutorial, hai imparato come utilizzare facilmente il componente Paging dai componenti di Android Architecture (che fanno parte di Android Jetpack) con Room. Questo ci aiuta a caricare in modo efficiente set di dati di grandi dimensioni dal database locale per consentire un'esperienza utente più fluida durante lo scorrimento di un elenco in RecyclerView
.
Consiglio vivamente di consultare la documentazione ufficiale per ulteriori informazioni sulla libreria di Paging su Android.