RemoteMediator
abstract class RemoteMediator<Key : Any, Value : Any>
kotlin.Any | |
↳ | androidx.paging.RemoteMediator |
Defines a set of callbacks used to incrementally load data from a remote source into a local source wrapped by a PagingSource, e.g., loading data from network into a local db cache.
A RemoteMediator is registered by passing it to Pager's constructor.
RemoteMediator allows hooking into the following events:
- Stream initialization
- REFRESH signal driven from UI
- PagingSource returns a LoadResult which signals a boundary condition, i.e., the most
recent LoadResult.Page in the PREPEND or APPEND direction has LoadResult.Page.prevKey
or LoadResult.Page.nextKey set to
null
respectively.
import androidx.room.withTransaction /** * Sample RemoteMediator for a DB + Network based PagingData stream, which triggers network * requests to fetch additional items when a user scrolls to the end of the list of items stored * in DB. * * This sample loads a list of [User] items from an item-keyed Retrofit paginated source. This * source is "item-keyed" because we're loading the next page using information from the items * themselves (the ID param) as a key to fetch more data. */ @OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // The network load method takes an optional `after=<user.id>` parameter. For every // page after the first, we pass the last user ID to let it continue from where it // left off. For REFRESH, pass `null` to load the first page. val loadKey = when (loadType) { LoadType.REFRESH -> null // In this example, we never need to prepend, since REFRESH will always load the // first page in the list. Immediately return, reporting end of pagination. LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND -> { val lastItem = state.lastItemOrNull() // We must explicitly check if the last item is `null` when appending, // since passing `null` to networkService is only valid for initial load. // If lastItem is `null` it means no items were loaded after the initial // REFRESH and there are no more items to load. if (lastItem == null) { return MediatorResult.Success(endOfPaginationReached = true) } lastItem.id } } // Suspending network load via Retrofit. This doesn't need to be wrapped in a // withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter // dispatches on a worker thread. val response = networkService.searchUsers(query = query, after = loadKey) database.withTransaction { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query) } // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.users) } MediatorResult.Success(endOfPaginationReached = response.nextKey == null) } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } }
import androidx.paging.samples.shared.RemoteKey import androidx.room.withTransaction /** * Sample RemoteMediator for a DB + Network based PagingData stream, which triggers network * requests to fetch additional items when a user scrolls to the end of the list of items stored * in DB. * * This sample loads a list of [User] via Retrofit from a page-keyed network service using * [String] tokens to load pages (each response has a next/previous token), and inserts them * into database. */ @OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() val remoteKeyDao = database.remoteKeyDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // The network load method takes an optional [String] parameter. For every page // after the first, we pass the [String] token returned from the previous page to // let it continue from where it left off. For REFRESH, pass `null` to load the // first page. val loadKey = when (loadType) { LoadType.REFRESH -> null // In this example, we never need to prepend, since REFRESH will always load the // first page in the list. Immediately return, reporting end of pagination. LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) // Query remoteKeyDao for the next RemoteKey. LoadType.APPEND -> { val remoteKey = database.withTransaction { remoteKeyDao.remoteKeyByQuery(query) } // We must explicitly check if the page key is `null` when appending, // since `null` is only valid for initial load. If we receive `null` // for APPEND, that means we have reached the end of pagination and // there are no more items to load. if (remoteKey.nextKey == null) { return MediatorResult.Success(endOfPaginationReached = true) } remoteKey.nextKey } } // Suspending network load via Retrofit. This doesn't need to be wrapped in a // withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter // dispatches on a worker thread. val response = networkService.searchUsers(query, loadKey) // Store loaded data, and next key in transaction, so that they're always consistent database.withTransaction { if (loadType == LoadType.REFRESH) { remoteKeyDao.deleteByQuery(query) userDao.deleteByQuery(query) } // Update RemoteKey for this query. remoteKeyDao.insertOrReplace(RemoteKey(query, response.nextKey)) // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.users) } MediatorResult.Success(endOfPaginationReached = response.nextKey == null) } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } }
Summary
Nested classes | |
---|---|
enum |
Return type of initialize, which signals the action to take after initialize completes. |
sealed |
Public constructors | |
---|---|
<init>() Defines a set of callbacks used to incrementally load data from a remote source into a local source wrapped by a PagingSource, e. |
Public methods | |
---|---|
open suspend RemoteMediator.InitializeAction |
Callback fired during initialization of a PagingData stream, before initial load. |
abstract suspend RemoteMediator.MediatorResult |
load(loadType: LoadType, state: PagingState<Key, Value>) Callback triggered when Paging needs to request more data from a remote source due to any of the following events: |
Public constructors
<init>
RemoteMediator()
Defines a set of callbacks used to incrementally load data from a remote source into a local source wrapped by a PagingSource, e.g., loading data from network into a local db cache.
A RemoteMediator is registered by passing it to Pager's constructor.
RemoteMediator allows hooking into the following events:
- Stream initialization
- REFRESH signal driven from UI
- PagingSource returns a LoadResult which signals a boundary condition, i.e., the most
recent LoadResult.Page in the PREPEND or APPEND direction has LoadResult.Page.prevKey
or LoadResult.Page.nextKey set to
null
respectively.
import androidx.room.withTransaction /** * Sample RemoteMediator for a DB + Network based PagingData stream, which triggers network * requests to fetch additional items when a user scrolls to the end of the list of items stored * in DB. * * This sample loads a list of [User] items from an item-keyed Retrofit paginated source. This * source is "item-keyed" because we're loading the next page using information from the items * themselves (the ID param) as a key to fetch more data. */ @OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // The network load method takes an optional `after=<user.id>` parameter. For every // page after the first, we pass the last user ID to let it continue from where it // left off. For REFRESH, pass `null` to load the first page. val loadKey = when (loadType) { LoadType.REFRESH -> null // In this example, we never need to prepend, since REFRESH will always load the // first page in the list. Immediately return, reporting end of pagination. LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) LoadType.APPEND -> { val lastItem = state.lastItemOrNull() // We must explicitly check if the last item is `null` when appending, // since passing `null` to networkService is only valid for initial load. // If lastItem is `null` it means no items were loaded after the initial // REFRESH and there are no more items to load. if (lastItem == null) { return MediatorResult.Success(endOfPaginationReached = true) } lastItem.id } } // Suspending network load via Retrofit. This doesn't need to be wrapped in a // withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter // dispatches on a worker thread. val response = networkService.searchUsers(query = query, after = loadKey) database.withTransaction { if (loadType == LoadType.REFRESH) { userDao.deleteByQuery(query) } // Insert new users into database, which invalidates the current // PagingData, allowing Paging to present the updates in the DB. userDao.insertAll(response.users) } MediatorResult.Success(endOfPaginationReached = response.nextKey == null) } catch (e: IOException) { MediatorResult.Error(e) } catch (e: HttpException) { MediatorResult.Error(e) } } }
import androidx.paging.samples.shared.RemoteKey import androidx.room.withTransaction /** * Sample RemoteMediator for a DB + Network based PagingData stream, which triggers network * requests to fetch additional items when a user scrolls to the end of the list of items stored * in DB. * * This sample loads a list of [User] via Retrofit from a page-keyed network service using * [String] tokens to load pages (each response has a next/previous token), and inserts them * into database. */ @OptIn(ExperimentalPagingApi::class) class ExampleRemoteMediator( private val query: String, private val database: RoomDb, private val networkService: ExampleBackendService ) : RemoteMediator<Int, User>() { val userDao = database.userDao() val remoteKeyDao = database.remoteKeyDao() override suspend fun load( loadType: LoadType, state: PagingState<Int, User> ): MediatorResult { return try { // The network load method takes an optional [String] parameter. For every page // after the first, we pass the [String] token returned from the previous page to // let it continue from where it left off. For REFRESH, pass `null` to load the // first page. val loadKey = when (loadType) { LoadType.REFRESH -> null // In this example, we never need to prepend, since REFRESH will always load the // first page in the list. Immediately return, reporting end of pagination. LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true) // Query remoteKeyDao for the next RemoteKey. LoadType.APPEND -> { val remoteKey = database.withTransaction { remoteKeyDao.remoteKeyByQuery(query) } // We must explicitly check if the page key is `null` when appending, // since `null` is only valid for initial load. If we receive `null` // for APPEND, that means we have reached the end of pagination and // there are no more items to load. if (remoteKey.nextKey == null) { return MediatorResult.Success(endOfPaginationReached = true) } remoteKey.nextKey } } // Suspending network load via Retrofit. This doesn't need to be wrapped in a // withContext(Dispatcher.IO) { ... } block since Retrofit's Coroutine CallAdapter // dispatches on a worker thread. val response = networkService.searchUsers(query, loadKey) // Store loaded data, and next key in transaction, so that they're always consistent database.withTransacti