Android电影App Paging实现扩展:如何获取多类型电影数据,是否需要复用PagingDataSource?
Great question! You have two solid options here—either reuse your existing MoviePagingDataSource with a parameter to distinguish movie types, or create separate PagingSource classes for each category. Let’s walk through both approaches and their pros and cons.
Option 1: Reuse a Single MoviePagingDataSource with a Type Parameter
This is the more concise approach if your only difference between categories is the API endpoint being called. You can add a parameter to specify which type of movies to fetch, then branch on that parameter in the load method.
First, define an enum to represent your movie categories (this keeps things type-safe):
enum class MovieCategory { POPULAR, UPCOMING }
Then modify your existing MoviePagingDataSource to accept this enum and use it to call the correct API:
private const val STARTING_PAGE_INDEX = 1 class MoviePagingDataSource( private val apiService: AbsoluteAPI, private val category: MovieCategory // Add the category parameter ) : PagingSource<Int, Movie>() { override fun getRefreshKey(state: PagingState<Int, Movie>): Int? { // Implement refresh key to restore pagination state after config changes return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } ?: STARTING_PAGE_INDEX } override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> { val pagePosition = params.key ?: STARTING_PAGE_INDEX val response = when(category) { MovieCategory.POPULAR -> apiService.fetchPopularMovies(apiKey, pagePosition) MovieCategory.UPCOMING -> apiService.fetchUpcomingMovies(apiKey, pagePosition) } Log.e("Response","Result: {${response.body()}}") val movies = response.body()?.moviesResults ?: emptyList() return try { LoadResult.Page( data = movies, prevKey = if (pagePosition == STARTING_PAGE_INDEX) null else pagePosition - 1, nextKey = if (movies.isEmpty()) null else pagePosition + 1 ) } catch (exception: HttpException) { Log.e("exception","exception $exception") LoadResult.Error(exception) } catch (exception: IOException) { Log.e("exception","exception $exception") LoadResult.Error(exception) } } }
To use this, just create different instances for each category:
// For popular movies val popularPagingSource = MoviePagingDataSource(apiService, MovieCategory.POPULAR) // For upcoming movies val upcomingPagingSource = MoviePagingDataSource(apiService, MovieCategory.UPCOMING)
Pros: Less code duplication, easy to maintain if categories only differ by API endpoint.
Cons: If you later need unique logic for a category (like different pagination rules, error handling, or data transformation), this class can get cluttered.
Option 2: Create Separate PagingSource Classes
If you want to follow the Single Responsibility Principle (each class does one thing), or anticipate needing unique logic per category, create individual PagingSource classes. You can even abstract common logic into a base class to avoid duplication.
First, create a base abstract class with shared logic:
abstract class BaseMoviePagingDataSource(protected val apiService: AbsoluteAPI) : PagingSource<Int, Movie>() { protected val STARTING_PAGE_INDEX = 1 override fun getRefreshKey(state: PagingState<Int, Movie>): Int? { return state.anchorPosition?.let { anchorPosition -> state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) } ?: STARTING_PAGE_INDEX } // Abstract method to be implemented by subclasses for specific API calls protected abstract suspend fun fetchMovies(page: Int): Response<MovieResponse> override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Movie> { val pagePosition = params.key ?: STARTING_PAGE_INDEX return try { val response = fetchMovies(pagePosition) Log.e("Response","Result: {${response.body()}}") val movies = response.body()?.moviesResults ?: emptyList() LoadResult.Page( data = movies, prevKey = if (pagePosition == STARTING_PAGE_INDEX) null else pagePosition - 1, nextKey = if (movies.isEmpty()) null else pagePosition + 1 ) } catch (exception: HttpException) { Log.e("exception","exception $exception") LoadResult.Error(exception) } catch (exception: IOException) { Log.e("exception","exception $exception") LoadResult.Error(exception) } } }
Then create concrete classes for each category:
class PopularMoviePagingDataSource(apiService: AbsoluteAPI) : BaseMoviePagingDataSource(apiService) { override suspend fun fetchMovies(page: Int): Response<MovieResponse> { return apiService.fetchPopularMovies(apiKey, page) } } class UpcomingMoviePagingDataSource(apiService: AbsoluteAPI) : BaseMoviePagingDataSource(apiService) { override suspend fun fetchMovies(page: Int): Response<MovieResponse> { return apiService.fetchUpcomingMovies(apiKey, page) } }
Pros: Clean separation of concerns, easy to add unique logic to individual categories later.
Cons: Slightly more boilerplate upfront, but manageable with the base class.
Which Should You Choose?
- Go with Option 1 if your categories are simple and only differ by the API endpoint. It’s quick and keeps code DRY.
- Go with Option 2 if you expect each category to have unique behavior (e.g., different pagination rules, custom error handling) or want to keep your codebase organized as it scales.
Also, note that I implemented the getRefreshKey method in both examples—this is important for restoring pagination state after configuration changes (like screen rotation), so don’t leave it as TODO in your final code!
内容的提问来源于stack exchange,提问作者HoLoGram

