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:
- Podrá obtener el contenido en la página más rápido.
- Consume muy poca memoria.
- No carga datos inútiles, lo que significa que solo se pueden cargar una o dos páginas a la vez.
- 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:
- 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:
- 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.
- 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.
- 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