Android-architectuurcomponenten de paging-bibliotheek met ruimte gebruiken

In deze zelfstudie laat ik je zien hoe je de Paging-bibliotheek van de Android-architectuurcomponenten gebruikt met een Room-backed-database in een Android-app. 

U leert hoe u de Paging-bibliotheek gebruikt om grote gegevenssets efficiënt te laden vanuit een Room-backed-database, waardoor uw gebruikers een soepelere ervaring krijgen tijdens het scrollen in een RecyclerView. 

voorwaarden

Als je deze zelfstudie wilt kunnen volgen, heb je het volgende nodig:

  • Android Studio 3.1.3 of hoger
  • Kotlin plug-in 1.2.51 of hoger
  • een basiskennis van de Android-architectuurcomponenten (met name de Actuele gegevens en Room database)

Als je de architectuurcomponenten nog niet hebt geleerd, raden we je ten zeerste aan om onze geweldige serie te bekijken, alles over Android Architecture Components van Tin Megali. Zorg ervoor dat je erin gaat duiken! 

Een voorbeeldproject voor deze zelfstudie is te vinden op onze GitHub-repo, zodat u deze eenvoudig kunt volgen.

Wat is de paging-bibliotheek?

De paging-bibliotheek is een andere bibliotheek die is toegevoegd aan de architectuurcomponenten. De bibliotheek helpt bij het efficiënt beheren van het laden en weergeven van een grote gegevensset in de RecyclerView. Volgens de officiële documenten:

Met de pagingbibliotheek kunt u gemakkelijker gegevens geleidelijk en sierlijk in uw app laden RecyclerView.

Als een deel van uw Android-app een grote gegevensset van een lokale of externe gegevensbron gaat weergeven maar slechts een deel ervan tegelijkertijd weergeeft, kunt u overwegen de Paging-bibliotheek te gebruiken. Dit zal de prestaties van uw app helpen verbeteren! 

Dus waarom de paging-bibliotheek gebruiken?

Nu u een inleiding tot de paging-bibliotheek hebt gezien, kunt u zich afvragen waarom u deze gebruikt? Hier zijn enkele redenen waarom u zou moeten overwegen om het te gebruiken bij het laden van grote gegevenssets in a RecyclerView

  • Er worden geen gegevens opgevraagd die niet nodig zijn. Deze bibliotheek vraagt ​​alleen gegevens aan die zichtbaar zijn voor de gebruiker, terwijl de gebruiker door de lijst scrolt. 
  • Bespaart de batterij van de gebruiker en verbruikt minder bandbreedte. Omdat er alleen gegevens worden gevraagd die nodig zijn, bespaart dit enkele apparaatbronnen. 

Het zal niet efficiënt zijn bij het werken met een grote hoeveelheid gegevens, omdat de onderliggende gegevensbron alle gegevens ophaalt, hoewel slechts een deelverzameling van die gegevens aan de gebruiker zal worden getoond. In zo'n situatie zouden we moeten overwegen om de gegevens te pagen. 

1. Maak een Android Studio-project

Start je Android Studio 3 op en maak een nieuw project met een lege activiteit genaamd Hoofdactiviteit. Zorg ervoor om te controleren Inclusief Kotlin-ondersteuning

2. Voeg de architectuurcomponenten toe

Nadat u een nieuw project hebt gemaakt, voegt u de volgende afhankelijkheden toe in uw build.gradle. In deze tutorial gebruiken we de nieuwste Paging-bibliotheekversie 1.0.1, terwijl Room 1.1.1 is (vanaf dit schrijven). 

afhankelijkheden implementatie fileTree (dir: 'libs', include: ['* .jar']) implementatie "android.arch.persistence.room:runtime:1.1.1" kapt "android.arch.persistence.room:compiler:1.1 .1 "implementatie" android.arch.paging: runtime: 1.0.1 "implementatie" com.android.support:recyclerview-v7:27.1.1 "

Deze artefacten zijn beschikbaar in de Maven-repository van Google. 

allprojects repositories google () jcenter ()

Door de afhankelijkheden toe te voegen, hebben we Gradle geleerd hoe de bibliotheek te vinden. Vergeet niet om uw project te synchroniseren nadat u ze hebt toegevoegd. 

3. Maak de entiteit

Maak een nieuwe Kotlin-gegevensklasse Persoon. Voor de eenvoud, onze Persoon entiteit heeft slechts twee velden:

  • een uniek ID (ID kaart)
  • de naam van de persoon (naam)

Voeg daarnaast een toString ( methode die eenvoudig de naam

import android.arch.persistence.room.Entity import android.arch.persistence.room.PrimaryKey @Entity (tableName = "persons") dataklasse Person (@PrimaryKey val id: String, val name: String) override fun toString ( ) = naam

4. Maak de DAO

Zoals u weet, hebben we toegang tot data-objecten (DAO's) nodig om toegang te krijgen tot de gegevens van onze app met de Room-bibliotheek. In ons eigen geval hebben we een 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 @Dao interface PersonDao @Query ("SELECT * FROM persons") fun getAll (): LiveData> @Query ("SELECT * FROM persons") fun getAllPaged (): DataSource.Factory @Gebruik leuk invoegenAlle (personen: lijst) @Wissen grappig verwijderen (persoon: persoon)

In onze PersonDao klas, we hebben er twee @Query methoden. Een van hen is alles krijgen(), welke een a retourneert Actuele gegevens die een lijst bevat van Persoon voorwerpen. De andere is getAllPaged (), welke een a retourneert DataSource.Factory

Volgens de officiële documenten, de Databron les is de:

Basisklasse voor het laden van pagina's met snapshotgegevens in een PagedList.

EEN PagedList is een speciaal soort Lijst voor het weergeven van pagina-gegevens in Android: 

EEN PagedList is een lijst die zijn gegevens laadt in chunks (pagina's) van a Databron. Items kunnen worden geopend met te krijgen (int), en verder laden kan worden geactiveerd met loadAround (int).

We hebben het Fabriek statische methode in de Databron class, die dienst doet als een fabriek (objecten maken zonder de exacte klasse van het object te hoeven opgeven dat wordt gemaakt) voor de Databron. Deze statische methode omvat twee gegevenstypen:

  • De sleutel waarmee items worden geïdentificeerd Databron. Houd er rekening mee dat voor een Room-query de pagina's zijn genummerd, dus we gebruiken Geheel getal als het type pagina-ID. Het is mogelijk om "ingetoetste" pagina's te gebruiken met behulp van de paging-bibliotheek, maar Room biedt dat momenteel niet. 
  • Het type items of entiteiten (POJO's) in de lijst geladen door de Databrons.

5. Maak de database

Dit is wat onze database met Room-klassen is AppDatabase lijkt op: 

importeer 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) abstract class AppDatabase: RoomDatabase () abstract fun personDao (): PersonDao-begeleidende object // voor Singleton instantiation @Volatile private var instance: AppDatabase? = null fun getInstance (context: Context): AppDatabase return instance?: synchrone (this) instance?: buildDatabase (context) .also instance = it private fun buildDatabase (context: Context): AppDatabase return Room .databaseBuilder (context, AppDatabase :: class.java, DATABASE_NAME) .addCallback (object: RoomDatabase.Callback () override fun onCreate (db: SupportSQLiteDatabase) super.onCreate (db) val request = OneTimeWorkRequestBuilder() .build () WorkManager.getInstance () ?. wacht (aanvraag)) .build ()

Hier hebben we één exemplaar van onze database gemaakt en vooraf ingevuld met gegevens met de nieuwe WorkManager-API. Merk op dat de vooraf ingevulde gegevens slechts een lijst van 1000 namen zijn (neem een ​​kijkje in de verstrekte voorbeeldbroncode voor meer informatie). 

6. Het ViewModel maken

Voor onze gebruikersinterface om gegevens op een levenscyclusbewuste manier op te slaan, te observeren en te bedienen, hebben we een ViewModel. Onze PersonsViewModel, waardoor de AndroidViewModel klas, gaat functioneren als onze 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 PersonsViewModel constructor (applicatie: Applicatie): AndroidViewModel (applicatie) private var personsLiveData: LiveData> init val factory: DataSource.Factory = AppDatabase.getInstance (getApplication ()). PersonDao (). GetAllPaged () val pagedListBuilder: LivePagedListBuilder = LivePagedListBuilder(fabriek, 50) personsLiveData = pagedListBuilder.build () fun getPersonsLiveData () = personsLiveData

In deze klasse hebben we een enkel veld met de naam personsLiveData. Dit veld is gewoon een Actuele gegevens dat bevat een PagedList van Persoon voorwerpen. Omdat dit een is Actuele gegevens, onze gebruikersinterface (de Activiteit of Fragment) gaat deze gegevens observeren door de getter-methode aan te roepen getPersonsLiveData ()

We zijn geïnitialiseerd personsLiveData binnen in de in het blok. In dit blok krijgen we de DataSource.Factory door het te bellen AppDatabase singleton voor de PersonDao voorwerp. Wanneer we dit object krijgen, bellen we getAllPaged ()

We maken vervolgens een LivePagedListBuilder. Dit is wat de officiële documentatie zegt over een LivePagedListBuilder

Bouwer voor Actuele gegevens, gegeven een DataSource.Factory en een PagedList.Config.

Wij leveren zijn constructeur een DataSource.Factory als het eerste argument en de paginagrootte als het tweede argument (in ons eigen geval zal het paginaformaat 50 zijn). Meestal moet u een grootte kiezen die hoger is dan het maximumaantal dat u in één keer aan de gebruiker kunt weergeven. Uiteindelijk bellen we bouwen() om te bouwen en naar ons terug te keren Actuele gegevens

7. De PagedList-adapter maken

Om onze te laten zien PagedList gegevens in a RecyclerView, we hebben een ... nodig PagedListAdapter. Hier is een duidelijke definitie van deze klasse uit de officiële documenten:

RecyclerView.Adapter basisklasse voor het presenteren van wisselbare gegevens van PagedLists in a RecyclerView.

Dus we creëren een PersonAdapter dat breidt zich uit PagedListAdapter.

import android.arch.paging.PagedListAdapter import android.content.Context import android.support.v7.widget.RecyclerView import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import 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 (position) if (person == null) holderPerson.clear () else holderPerson.bind (person) override fun onCreateViewHolder (boven: ViewGroup, viewType: Int): PersonViewHolder return PersonViewHolder (LayoutInflater.from (context) .inflate (R.layout.item_person, parent, false)) 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

PagedListAdapter wordt net als elke andere subklasse van gebruikt RecyclerView.Adapter. Met andere woorden, u moet de methoden implementeren onCreateViewHolder () en onBindViewHolder ()

Om de PagedListAdapter abstracte klasse, moet je in zijn constructor het type PageLists (dit zou een gewone oude Java-klasse moeten zijn: een POJO) en ook een klasse die de ViewHolder dat wordt gebruikt door de adapter. In ons geval hebben we het gegeven Persoon en PersonViewHolder als respectievelijk het eerste en tweede argument. 

Let daar op PagedListAdapter vereist dat je het doorgeeft a DiffUtil.ItemCallback naar de PageListAdapter bouwer. DiffUtil is een RecyclerView utility-klasse die het verschil tussen twee lijsten kan berekenen en een lijst met updatebewerkingen uitvoert die de eerste lijst in de tweede omzet. ItemCallback is een innerlijke abstracte statische klasse (binnen DiffUtil) gebruikt voor het berekenen van het verschil tussen twee niet-lege items in een lijst. 

Concreet leveren wij PersonDiffCallback naar onze PagedListAdapter bouwer. 

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 

Omdat we aan het implementeren zijn DiffUtil.ItemCallback, we moeten twee methoden implementeren: areItemsTheSame () en areContentsTheSame ()

  • areItemsTheSame wordt geroepen om te controleren of twee objecten hetzelfde item vertegenwoordigen. Als uw items bijvoorbeeld unieke id's hebben, moet deze methode de gelijkheid van id's controleren. Deze methode retourneert waar als de twee items hetzelfde object of vertegenwoordigen vals als ze anders zijn.
  • areContentsTheSame wordt geroepen om te controleren of twee items dezelfde gegevens hebben. Deze methode retourneert waar als de inhoud van de items hetzelfde zijn of vals als ze anders zijn.

Onze PersonViewHolder innerlijke klasse is slechts een typisch voorbeeld RecyclerView.ViewHolder. Het is verantwoordelijk voor het binden van gegevens zoals nodig uit ons model in de widgets voor een rij in onze lijst. 

class PersonAdapter (val context: Context): PagedListAdapter(PersonDiffCallback ()) // ... class PersonViewHolder (weergave: Weergave): RecyclerView.ViewHolder (weergave) var tvName: TextView = view.name fun bind (persoon: persoon) tvName.text = person.name fun clear () tvName.text = null

8. Resultaat weergeven

In onze onCreate () van onze Hoofdactiviteit, we hebben gewoon het volgende gedaan:

  • initialiseren onze ViewModel veld met behulp van de utility-klasse ViewModelProviders
  • maak een instantie van PersonAdapter
  • configureren van onze RecyclerView
  • bind de PersonAdapter naar de RecyclerView
  • observeer de Actuele gegevens en stuur het PagedList objecten naar de PersonAdapter door aan te roepen 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: PersonsViewModel override 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 (adapter) private fun subscribeUi (adapter: PersonAdapter) viewModel.getPersonLiveData (). Observe (this, Observer names -> if (names! = Null) adapter.submitList (namen))

Eindelijk, wanneer u de app uitvoert, is hier het resultaat:

Tijdens het scrollen kan Room leemtes voorkomen door 50 items tegelijk te laden en beschikbaar te maken voor onze PersonAdapter, welke een subklasse is van PagingListAdapter. Houd er echter rekening mee dat niet alle gegevensbronnen snel worden geladen. De laadsnelheid is ook afhankelijk van de verwerkingskracht van het Android-apparaat. 

9. Integratie met RxJava

Als u RxJava in uw project gebruikt of wilt gebruiken, bevat de paging-bibliotheek een ander nuttig artefact: RxPagedListBuilder. U gebruikt dit artefact in plaats van LivePagedListBuilder voor RxJava-ondersteuning. 

U maakt eenvoudig een instantie van RxPagedListBuilder, het leveren van dezelfde argumenten als voor LivePagedListBuilder-de DataSource.Factory en het paginaformaat. Jij belt dan buildObservable () of buildFlowable () terugkeren waarneembaar of flowable Voor jouw PagedList respectievelijk. 

Om expliciet de Scheduler voor het laden van gegevens, noem je de methode setter setFetchScheduler (). Om ook de Scheduler voor het afleveren van het resultaat (bijv. AndroidSchedulers.mainThread ()), gewoon bellen setNotifyScheduler (). Standaard, setNotifyScheduler () standaard naar de UI-thread, terwijl setFetchScheduler () standaard de I / O thread pool. 

Conclusie

In deze zelfstudie leer je hoe je de Paging-component van de Android Architecture Components (die onderdeel zijn van Android Jetpack) met Room kunt gebruiken. Dit helpt ons bij het efficiënt laden van grote gegevenssets uit de lokale database om een ​​soepelere gebruikerservaring mogelijk te maken tijdens het scrollen door een lijst in de RecyclerView

Ik raad ten zeerste aan om de officiële documentatie te bekijken voor meer informatie over de paging-bibliotheek in Android.