Generalidades

Android – ¿Cómo probar Paging 3 (PagingSource)? | Autor: mohamed gamal | julio de 2021

Mohamed Gamal

Pagination 3 es una de las mejores bibliotecas de paginación: si le proporciona todo el contenido que queremos lograr, no solo se puede lograr la paginación con el menor esfuerzo, sino que también se pueden obtener datos de diferentes fuentes de datos (api / base de datos local) .

Pero por algunas razones, este también es un problema muy difícil:

  • Si tiene una vista de lista sin paginación, no es fácil migrar su antigua base de código a Paging 3;
  • ¿Qué partes del código se verán afectadas? emmmm Esta es una muy buena pregunta, por supuesto que la capa de datos no lo hará, pero Repository Los nuevos cursos cambiarán PagingSource
  • Cómo probar PagingSource ? ! !

Aquí consigo reviews De algunos Api -> getReviews()

api.getReviews(
limit = params.loadSize,
offset = offset
)

Esto es un ejemplo Api Respuesta ->

val reviewsResponse = ReviewsResponse(
reviews = listOf(
ReviewItemResponse(id = "id")
),
totalCount = 10,
pagination = PaginationResponse(limit = 1, offset = 0)
)

Esta clase se usa para manejar la fuente de paginación de paginación usando la biblioteca paging 3:

class ReviewsPagingSource(private val api: Api) : PagingSource<Int, Review>()   override suspend fun load(params: LoadParams<Int>) = try 
// offset always starts with 0
val offset = params.key ?: 0
val response =
api.getReviews(
limit = params.loadSize,
offset = offset
)
LoadResult.Page(
data = response.reviews.map Review(it) ,
prevKey =
if (response.pagination.offset == 0) null
else response.pagination.offset,
nextKey =
if (response.pagination.offset + response.reviews.size >= response.totalCount) null
else response.pagination.offset + response.reviews.size
)
catch (exception: Exception)
LoadResult.Error(exception)

Como notó aquí:

  • No es necesario tener el tuyo propioResult Una clase que se usa para procesar respuestas y ayudar a desarrollar métodos generales de manejo de errores, porque la página 3 ha pasado LoadResult sealed class Esto se encargará de esto.
  • Así que aquí hay 3 cosas interesantes que son difíciles de probar, manejo de errores, offset Y los datos devueltos … una cosa más a tener en cuenta, load La función es una suspend Uno;)

Llama esto fuente, prefiero llamarlo throw a repo en lugar de llamarlo directamente desde el modelo de vista, así que creé este repositorio:

class ReviewsRepository(private val api: Api) fun getReviews() = Pager(
config = PagingConfig(
pageSize = REVIEWS_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory =
ReviewsPagingSource(api)

).flow
companion object
private const val REVIEWS_PAGE_SIZE = 15

Este es un ejemplo de cómo probar ReviewsPagingSource:

@ExperimentalCoroutinesApi
class ReviewsPagingSourceTest
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
@get:Rule
var coroutineTestRule = CoroutineTestRule()
@Mock lateinit var api: Apilateinit var reviewsPagingSource: ReviewsPagingSource@Before
fun setup()
MockitoAnnotations.initMocks(this)
reviewsPagingSource = ReviewsPagingSource(api)
@Test
fun `reviews paging source load - failure - http error`() = runBlockingTest
val error = RuntimeException("404", Throwable())
given(api.getReviews(any(), any())).willThrow(error)
val expectedResult = PagingSource.LoadResult.Error<Int, Review>(error)assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
)
)
@Test
fun `reviews paging source load - failure - received null`() = runBlockingTest
given(api.getReviews(any(), any())).willReturn(null)val expectedResult = PagingSource.LoadResult.Error<Int, Review>(NullPointerException())assertEquals(
expectedResult.toString(), reviewsPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
).toString()
)
@Test
fun `reviews paging source refresh - success`() = runBlockingTest
given(api.getReviews(any(), any())).willReturn(reviewsResponse)val expectedResult = PagingSource.LoadResult.Page(
data = reviewsResponse.reviews.map Review(it) ,
prevKey = null,
nextKey = 1
)
assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Refresh(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
)
)
@Test
fun `reviews paging source append - success`() = runBlockingTest
given(api.getReviews(any(), any())).willReturn(nextReviewsResponse)val expectedResult = PagingSource.LoadResult.Page(
data = reviewsResponse.reviews.map Review(it) ,
prevKey = 1,
nextKey = 2
)
assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Append(
key = 1,
loadSize = 1,
placeholdersEnabled = false
)
)
)
@Test
fun `reviews paging source prepend - success`() = runBlockingTest
given(api.getReviews(any(), any())).willReturn(reviewsResponse)val expectedResult = PagingSource.LoadResult.Page(
data = reviewsResponse.reviews.map Review(it) ,
prevKey = null,
nextKey = 1
)
assertEquals(
expectedResult, reviewsPagingSource.load(
PagingSource.LoadParams.Prepend(
key = 0,
loadSize = 1,
placeholdersEnabled = false
)
)
)
companion object val reviewsResponse = ReviewsResponse(
reviews = listOf(
ReviewItemResponse(id = "id")
),
totalCount = 10,
pagination = PaginationResponse(limit = 1, offset = 0)
)
val nextReviewsResponse = ReviewsResponse(
reviews = listOf(
ReviewItemResponse(id = "id")
),
totalCount = 10,
pagination = PaginationResponse(limit = 1, offset = 1)
)

Me gusta analizar el código, si tiene alguna pregunta, no dude en preguntarme

LEER  Qualcomm y Razer preparan kits de desarrollo para juegos móviles de última generación

Publicaciones relacionadas

Deja una respuesta

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

Botón volver arriba