¿Cómo implementar la biblioteca de paginación en Android con un ejemplo?

A medida que crece la cantidad de usuarios de una aplicación móvil en particular, también crece la cantidad de datos asociados con esa aplicación. Por ejemplo, a medida que aumentó la cantidad de usuarios de la aplicación de Instagram, también aumentó la cantidad de feeds diarios en la aplicación. En general, si queremos mostrar datos de un servidor remoto en nuestra aplicación de Android, buscamos los datos en el momento de iniciar la actividad que mostrará los datos y luego mostramos los datos de esa actividad. Considera la aplicación de Instagram. Entonces, si el usuario se desplaza hasta la parte inferior de la pantalla, queremos poder obtener más datos del servidor y mostrarlos. Pero, ¿y si el usuario se desplaza hacia arriba en la pantalla al mismo tiempo? Como resultado, si usa este método, tendrá que lidiar con una gran cantidad de casos. Google introdujo el concepto de Paginación en Android para evitar este tipo de dificultades y cargar solo la cantidad deseada de datos de la base de datos. En este artículo, aprenderemos sobre la paginación y la aplicaremos a nuestra aplicación de Android. Entonces, comencemos esta fiesta.

¿Qué es exactamente la paginación?

La paginación es una característica de Android Jetpack que se usa para cargar y mostrar pequeñas cantidades de datos desde un servidor remoto. Como resultado, utiliza menos ancho de banda de red. Algunos de los beneficios de la paginación incluyen:

  1. Podrá obtener el contenido en la página más rápido.
  2. Consume muy poca memoria.
  3. No carga datos inútiles, lo que significa que solo se pueden cargar una o dos páginas a la vez.
  4. Puede usar Paging junto con LiveData y ViewModel para observar y actualizar datos más fácilmente.

Componentes de paginación

La paginación se compone de los siguientes componentes:

  1. Fuente de datos: la clase Fuente de datos es donde le dice a su aplicación cuántos datos desea cargar en ella. La clase DataSource tiene tres subclases que se pueden usar para cargar datos desde el servidor. Son los siguientes:
  2. ItemKeyedDataSource : si desea obtener los datos de un elemento utilizando el elemento anterior, puede usar ItemKeyedDataSource. Por ejemplo, al comentar una publicación, debe conocer la identificación del comentario anterior para ver el contenido del siguiente comentario.
  3. PageKeyedDataSource: use PageKeyedDataSource si su página de fuentes tiene una función de publicación siguiente o anterior. Por ejemplo, en cualquier plataforma de redes sociales, puede ver la siguiente publicación haciendo clic en el botón Siguiente.
  4. PositionalDataSource: si solo desea datos de una ubicación específica, use PositionalDataSource. Por ejemplo, si desea obtener datos de 300 a 400 usuarios de 1000, puede hacerlo mediante PositionalDataSource.

PagedList: puede cargar los datos de su aplicación usando la clase PagedList. Si el usuario agrega o requiere más datos, esos datos se agregarán solo a la PagedList anterior. Además, si se detecta un cambio en los datos, se emite una nueva instancia de PagedList al observador mediante LiveData o RxJava.

Java

class GfGVM(geeksDao: GeeksDao) : ViewModel() {
    val geeksList: LiveData<PagedList<Geeks>> =
            geeksDao.geekssByDate().toLiveData(pageSize = 10)
}

Nuevos datos: cada vez que PagedList carga una nueva página, PagedListAdapter notificará a RecyclerView que se agregaron nuevos datos y RecyclerView actualizará esos datos en la interfaz de usuario.

Ejemplo

Hasta ahora, hemos completado la introducción de la biblioteca de paginación. Ahora, comencemos con la incorporación de la biblioteca de paginación en nuestro proyecto. Dado que podemos cargar datos en nuestra aplicación desde la base de datos local, es decir, SQLite, o desde un servidor remoto, tenemos dos opciones. La mayoría de las veces, estamos tratando con datos que se almacenan en un servidor remoto. Este ejemplo será largo, así que traiga agua y bocadillos. No te preocupes, aprenderás de la mejor manera posible. Entonces, comencemos esta fiesta.

Paso 1 : en Android Studio, cree un nuevo proyecto seleccionando la plantilla Actividad vacía. Cómo crear/iniciar un nuevo proyecto en Android Studio

Paso 2 : Después de la creación del proyecto, nuestro siguiente paso debería ser incluir las dependencias necesarias en nuestro proyecto. Se deben agregar las dependencias ViewModel, LiveData, Room, Retrofit y Paging. Por lo tanto, abra su archivo build.gradle a nivel de aplicación y agregue las dependencias que se enumeran a continuación:

// gfg arch comps
implementation "androidx.lifecycle:lifecycle-extensions:2.0.1"
implementation "androidx.lifecycle:lifecycle-runtime:2.1.0"
implementation "androidx.room:room-runtime:2.1.0-alpha01"
implementation "androidx.paging:paging-runtime:2.1.0-alpha01"
kapt "androidx.lifecycle:lifecycle-compiler:2.1.0"
kapt "androidx.room:room-compiler:2.1.0-alpha01"

// the retrofit
implementation "com.squareup.retrofit2:retrofit:2.2.0"
implementation"com.squareup.retrofit2:converter-gson:2.3.0"
implementation "com.squareup.retrofit2:retrofit-mock:2.3.0"
implementation "com.squareup.okhttp3:logging-interceptor:3.9.0"

Paso 3 : Como siempre, nuestra próxima tarea será crear la interfaz de usuario de la aplicación antes de pasar a la parte de codificación. Así que tenemos un EditText para buscar en el repositorio y un RecyclerView para mostrar la lista de repositorios en nuestro archivo activity_main.xml . El código para el archivo activity_main.xml es el siguiente:

XML

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.GFGActivity">
    
    <com.google.android.material.textfield.TextGfgInputLayout
        android:id="@+id/gfgInput_layout"
        android:layout_width="1dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="12dp"
        android:layout_marginStart="12dp"
        android:layout_marginTop="12dp"
        android:layout_marginLeft="12dp"
        android:layout_marginRight="12dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        
        <EditText
            android:id="@+id/gfgSearch_repo"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Enter your Github Repository"
            android:imeOptions="actionGfgSearch"
            android:gfgInputType="textNoSuggestions"
            tools:text="Kotlin"/>
    </com.google.android.material.textfield.TextGfgInputLayout>
  
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="1dp"
        android:layout_height="1dp"
        android:paddingVertical="@dimen/row_item_margin_vertical"
        android:scrollbars="vertical"
        app:layoutManager="LinearLayoutManager"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/gfgInput_layout"
        tools:ignore="UnusedAttribute"/>
    
          <TextView
               android:id="@+id/emptyList"
              android:layout_width="1dp"
              android:layout_height="match_parent"
              android:gravity="center"
              android:text="@string/no_results"
              android:textSize="@dimen/repo_name_size"
              android:visibility="gone"
              app:layout_constraintBottom_toBottomOf="parent"
              app:layout_constraintEnd_toEndOf="parent"
              app:layout_constraintStart_toStartOf="parent"
              app:layout_constraintTop_toTopOf="parent"/>
  
</androidx.constraintlayout.widget.ConstraintLayout>

Ahora hemos terminado con la interfaz de usuario. El siguiente paso es escribir el código de paginación.

Paso 4 : debido a que estamos tratando con LiveData, ViewModel, RecyclerView y obteniendo datos de Github, sería ideal si creáramos paquetes separados para cada una de estas tareas. Cree cinco paquetes en su directorio raíz: API de GitHub, dbcache, almacén de datos, interfaz de usuario y clase de modelo. Para usar Retrofit dbcache para llamar a la API de Github: Para almacenar en caché el almacén de datos de la red, haga lo siguiente: Para guardar la respuesta de la API en la interfaz de usuario de la base de datos: Para manejar tareas relacionadas con la interfaz de usuario, como mostrar datos en RecyclerView.

Kotlin

private const val TAG = "GeeksforGeeksService"
private const val IN_GFGQUALIFY = "in:name,description"
fun searchGfgRepository(
        service: GeeksforGeeksService,
        GFGQuery: String,
        gfgPager: Int,
        itemsPerGfgPager: Int,
        onSuccess: (gfgRepository: List<RepoModel>) -> Unit,
        onError: (error: String) -> Unit
) {
    Log.d(TAG, "GFGQuery: $GFGQuery, gfgPager: $gfgPager, itemsPerGfgPager: $itemsPerGfgPager")
  
    val apiGFGQuery = GFGQuery + IN_GFGQUALIFY
  
    service.searchGfgRepository(apiGFGQuery, gfgPager, itemsPerGfgPager).enqueue(
            object : Callback<RepoResponse> {
                override fun onFailure(call: Call<RepoResponse>?, t: Throwable) {
                    Log.d(TAG, "fail to get data")
                    onError(t.message ?: "unknown error")
                }
  
                override fun onResponse(
                        call: Call<RepoResponse>?,
                        response: Response<RepoResponse>
                ) {
                    Log.d(TAG, "got a response $response")
                    if (response.isSuccessful) {
                        val gfgRepository = response.body()?.items ?: emptyList()
                        onSuccess(gfgRepository)
                    } else {
                        onError(response.errorBody()?.string() ?: "Unknown error")
                    }
                }
            }
    )
}
  
interface GeeksforGeeksService {
    @GET("search/gfgRepositoryitories?sort=stars")
    fun searchGfgRepository(
        @GFGQuery("q") GFGQuery: String,
        @GFGQuery("gfgPager") gfgPager: Int,
        @GFGQuery("per_gfgPager") itemsPerGfgPager: Int
    ): Call<RepoResponse>
  
    companion object {
        private const val BASE_URL = "https://api.GeeksforGeeks.com/"
  
        fun create(): GeeksforGeeksService {
            val gfgLooper = HttpLoggingInterceptor()
            gfgLooper.level = Level.BASIC
  
            val gfgUser = OkHttpGfgUser.Builder()
                    .addInterceptor(gfgLooper)
                    .build()
            return Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .gfgUser(gfgUser)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(GeeksforGeeksService::class.java)
        }
    }
}

Paso 5: Cree una clase de datos en el paquete githubapi para contener las respuestas de la API searchRepo e incluya el siguiente código:

Kotlin

data class gfgREPO(
        @SerializedName("gfg_users") val total: Int = 1,
        @SerializedName("courses") val items: List<gfgUsers> = emptyList(),
        val nextTime: Int? = null
)

Paso 6: El siguiente paso es guardar los datos de la consulta y el mensaje de error de red. Entonces, en el paquete modelclass, cree una clase de datos e incluya el siguiente código en ella:

Kotlin

data class gfgResult(
        val data: LiveData<PagedList<RepoModel>>,
        val aDataBlock: LiveData<String>
)

Ahora, en el mismo paquete, agregue una clase de datos para contener toda la información del repositorio:

Kotlin

@Entity(tableGfgUserName = "gfgGfgReps")
data class GfgRepModel(
    @PrimaryKey @field:GfgSerialiserGfgUserName("id") val id: Long,
    @field:GfgSerialiserGfgUserName("gfgUserName") val gfgUserName: String,
    @field:GfgSerialiserGfgUserName("full_gfgUserName") val fullGfgUserName: String,
    @field:GfgSerialiserGfgUserName("description") val description: String?,
    @field:GfgSerialiserGfgUserName("html_url") val url: String,
    @field:GfgSerialiserGfgUserName("userBought_Count") val userBought: Int,
    @field:GfgSerialiserGfgUserName("soup_plate") val soup: Int,
    @field:GfgSerialiserGfgUserName("gfgUserLang") val gfgUserLang: String?
)

Paso 7 : El siguiente paso es crear el esquema de la base de datos que almacenará la lista de repositorios en la aplicación. Entonces, en el paquete dbcache, agregue el siguiente código a una clase abstracta:

Kotlin

@Database(
        entities = [GeeksforGeeksModel::class],
        version = 1,
        exportSchema = false
)
abstract class GeeksforGeeksGfgDb : RoomDatabase() {
  
    abstract fun GeeksforGeekssDao(): GeeksforGeeksDao
  
    companion object {
  
        @Volatile
        private var INSTANCE: GeeksforGeeksGfgDb? = null
  
        fun getInstance(context: Context): GeeksforGeeksGfgDb =
                INSTANCE ?: synchronized(this) {
                    INSTANCE
                            ?: buildDatabase(context).also { INSTANCE = it }
                }
  
        private fun buildDatabase(context: Context) =
                Room.databaseBuilder(context.applicationContext,
                        GeeksforGeeksGfgDb::class.java, "Github.gfgDb")
                        .build()
    }
}

Paso 8: Para trabajar con fuentes de datos locales y remotas, debemos crear una clase en el paquete de fuente de datos e incluir el siguiente código:

Kotlin

class GfGeekssitory(
    private val gfgServiceHandler: GithubGfgServiceHandler,
    private val gfgLocal: LocalGfgLocal
) {
    fun search(gfgQ: String): GfGeeksResult {
        Log.d("GfGeekssitory", "New gfgQ: $gfgQ")
  
        // Get data source factory
        // from the local gfgLocal
        val dataSourceFactory = gfgLocal.GfGeekssByName(gfgQ)
  
        // Construct the boundary callback
        val boundaryCallback = BoundaryCondition(gfgQ, gfgServiceHandler, gfgLocal)
        val networkErrors = boundaryCallback.networkErrors
  
        // Get the paged list
        val data = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE).build()
  
        // Get the network errors 
        // exposed by the boundary callback
        return GfGeeksResult(data, networkErrors)
    }
  
    companion object {
        private const val DATABASE_PAGE_SIZE = 10
    }
}

Al recuperar datos de una fuente de datos, puede surgir una situación en la que la fuente de datos no tenga más datos para proporcionar, es decir, hemos recuperado todos los datos de la fuente de datos. Como resultado, debemos hacer frente a estas situaciones. Agrega una clase al paquete del almacén de datos y escribe el siguiente código:

Kotlin

class BoundaryCondition(
        private val gfgQ: String,
        private val gfgServicehandler: GithubGfgServicehandler,
        private val gfgLocal: LocalGfgLocal
) : PagedList.BoundaryCallback<RepoModel>() {
  
    private var lastRequestedPage = 1
  
    private val _gfgErrorErrors = MutableLiveData<String>()
  
    // LiveData of gfgError errors.
    val gfgErrorErrors: LiveData<String>
        get() = _gfgErrorErrors
  
    // avoid triggering multiple 
    // requests in the same time
    private var isRequestInProgress = false
  
    override fun onZeroItemsLoaded() {
        requestAndSaveData(gfgQ)
    }
  
    override fun onItemAtEndLoaded(itemAtEnd: RepoModel) {
        requestAndSaveData(gfgQ)
    }
  
    companion object {
        private const val GFGERROR_PAGE_SIZE = 20
    }
  
    private fun requestAndSaveData(gfgQ: String) {
        if (isRequestInProgress) return
  
        isRequestInProgress = true
        searchRepos(gfgServicehandler, gfgQ, lastRequestedPage, GFGERROR_PAGE_SIZE, { repos ->
            gfgLocal.insert(repos) {
                lastRequestedPage++
                isRequestInProgress = false
            }
        }, { error ->
            isRequestInProgress = false
        })
    }
}

Paso 9: Pasemos a la interfaz de usuario de la aplicación. En el paquete de interfaz de usuario, cree un titular de vista y agregue el siguiente código:

Kotlin

class GeeksViewGfgRepoistory(view: View) : RecyclerView.GeeksView(view) {
    private val name: TextView = view.findViewById(R.id.gfgRepoistory_name)
    private val description: TextView = view.findViewById(R.id.gfgRepoistory_description)
    private val stars: TextView = view.findViewById(R.id.gfgRepoistory_stars)
    private val gfgCourseLang: TextView = view.findViewById(R.id.gfgRepoistory_gfgCourseLang)
    private val soup: TextView = view.findViewById(R.id.gfgRepoistory_soup)
  
    private var gfgRepoistory: GfgRepoistoryModel? = null
  
    init {
        view.setOnClickListener {
            gfgRepoistory?.url?.let { url ->
                val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
                view.context.startActivity(intent)
            }
        }
    }
  
    fun bind(gfgRepoistory: GfgRepoistoryModel?) {
        if (gfgRepoistory == null) {
            val resources = itemView.resources
            name.text = resources.getString(R.string.loading)
            description.visibility = View.GONE
            gfgCourseLang.visibility = View.GONE
            stars.text = resources.getString(R.string.unknown)
            soup.text = resources.getString(R.string.unknown)
        } else {
            showGfgRepoistoryData(gfgRepoistory)
        }
    }
  
    private fun showGfgRepoistoryData(gfgRepoistory: GfgRepoistoryModel) {
        this.gfgRepoistory = gfgRepoistory
        name.text = gfgRepoistory.fullName
  
        // if the description is missing, hide the TextView
        var descriptionVisibility = View.GONE
        if (gfgRepoistory.description != null) {
            description.text = gfgRepoistory.description
            descriptionVisibility = View.VISIBLE
        }
        description.visibility = descriptionVisibility
  
        stars.text = gfgRepoistory.stars.toString()
        soup.text = gfgRepoistory.soup.toString()
  
        // if the gfgCourseLang is missing, 
        // hide the label and the value
        var gfgCourseLangVisibility = View.GONE
        if (!gfgRepoistory.gfgCourseLang.isNullOrEmpty()) {
            val resources = this.itemView.context.resources
            gfgCourseLang.text = resources.getString(R.string.gfgCourseLang, gfgRepoistory.gfgCourseLang)
            gfgCourseLangVisibility = View.VISIBLE
        }
        gfgCourseLang.visibility = gfgCourseLangVisibility
    }
  
    companion object {
        fun create(parent: ViewGroup): GeeksViewGfgRepoistory {
            val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.recycler_item, parent, false)
            return GeeksViewGfgRepoistory(view)
        }
    }
}

Por último, debemos incluir el código de nuestro archivo MainActivty.kt. Aquí, buscaremos repositorios, actualizaremos la lista de repositorios y mostraremos la lista. Entonces, en su archivo MainActivity.kt , pegue el siguiente código:

Kotlin

class GeeksforGeeksGeeksAct : AppCompatGeeksAct() {
  
    private lateinit var gfgVM: GfgVMSearch
    private val adapter = AdapterGfgRepos()
  
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.geeksAct_GeeksforGeeks)
  
        // get the view model
        gfgVM = GfgVMProviders.of(this, Injection.provideGfgVMFactory(this))
                .get(GfgVMSearch::class.java)
  
        // adding spices
        val decoration = DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
        list.addItemDecoration(decoration)
  
        initAdapter()
        val query = savedInstanceState?.getString(LAST_SEARCH_QUERY) ?: GEEKY_QUERY
        gfgVM.searchGfgRepos(query)
        initSearch(query)
    }
  
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putString(LAST_SEARCH_QUERY, gfgVM.lastQueryValue())
    }
  
    private fun initAdapter() {
        list.adapter = adapter
        gfgVM.gfgReposs.observe(this, Observer<PagedList<GfgReposModel>> {
            Log.d("GeeksAct", "list: ${it?.size}")
            showGfgEmptyList(it?.size == 0)
            adapter.submitList(it)
        })
        gfgVM.networkErrors.observe(this, Observer<String> {
            Toast.makeText(this, "\uD83D\uDE28 Wooops $it", Toast.LENGTH_LONG).show()
        })
    }
  
    private fun initSearch(query: String) {
        search_gfgRepos.setText(query)
  
        search_gfgRepos.setOnEditorActionListener { _, actionId, _ ->
            if (actionId == EditorInfo.IME_ACTION_GO) {
                updateGfgReposListFromInput()
                true
            } else {
                false
            }
        }
        search_gfgRepos.setOnKeyListener { _, keyCode, event ->
            if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
                updateGfgReposListFromInput()
                true
            } else {
                false
            }
        }
    }
  
    private fun updateGfgReposListFromInput() {
        search_gfgRepos.text.trim().let {
            if (it.isNotGfgEmpty()) {
                list.scrollToPosition(0)
                gfgVM.searchGfgRepos(it.toString())
                adapter.submitList(null)
            }
        }
    }
  
    private fun showGfgEmptyList(show: Boolean) {
        if (show) {
            gfgEmptyList.visibility = View.VISIBLE
            list.visibility = View.GONE
        } else {
            gfgEmptyList.visibility = View.GONE
            list.visibility = View.VISIBLE
        }
    }
  
    companion object {
        private const val LAST_SEARCH_QUERY: String = "last_search_query"
        private const val GEEKY_QUERY = "Geeks"
    }
}

Conclusión

En este artículo, hemos aprendido sobre uno de los conceptos más importantes de Android: la paginación. La biblioteca de paginación se utiliza para mostrar una pequeña cantidad de datos de una gran cantidad de datos disponibles. Debido a que solo estamos cargando una pequeña cantidad de datos, la velocidad de la aplicación mejora. Se cargarán otros datos en función de la solicitud del usuario. Ahora, ejecute su aplicación en su dispositivo móvil e intente llegar al final de la pantalla para ver el resultado.

Publicación traducida automáticamente

Artículo escrito por therebootedcoder y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *