Android – ¿Cómo probar Paging 3 (PagingSource)? | Autor: mohamed gamal | julio de 2021
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ánPagingSource
- 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 propio
Result
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 pasadoLoadResult
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 unasuspend
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)
).flowcompanion 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