Google is committed to advancing racial equity for Black communities. See how.

RemoteMediator

abstract class RemoteMediator<Key : Any, Value : Any>
kotlin.Any
   ↳ androidx.paging.RemoteMediator

Defines a set of callbacks, which may be optionally registered when constructing a Pager, that allow for control of the following events:

These events together can be used to implement layered pagination - network + local storage.

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

Return type of load, which determines LoadState.

Public constructors

Defines a set of callbacks, which may be optionally registered when constructing a Pager, that allow for control of the following events:

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>)

Implement this method to load additional remote data, which will then be stored for the PagingSource to access.

Public constructors

<init>

RemoteMediator()

Defines a set of callbacks, which may be optionally registered when constructing a Pager, that allow for control of the following events:

These events together can be used to implement layered pagination - network + local storage.

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)
        }
    }
}

Public methods

initialize

open suspend fun initialize(): RemoteMediator.InitializeAction

Callback fired during initialization of a PagingData stream, before initial load.

This function runs to completion before any loading is performed.

Return
InitializeAction indicating the action to take after initialization:

load

abstract suspend fun load(
    loadType: LoadType,
    state: PagingState<Key, Value>
): RemoteMediator.MediatorResult

Implement this method to load additional remote data, which will then be stored for the PagingSource to access. These loads take one of two forms:

  • type == LoadType.PREPEND / LoadType.APPEND The PagingSource has loaded a 'boundary' page, with a null adjacent key. This means this method should load additional remote data to append / prepend as appropriate, and store it locally.
  • type == LoadType.REFRESH The app (or initialize) has requested a remote refresh of data. This means the method should generally load remote data, and replace all local data.

The runtime of this method defines loading state behavior in boundary conditions, which affects e.g., LoadState callbacks registered to androidx.paging.PagingDataAdapter.

NOTE: A PagingSource.load request which is fulfilled by a page that hits a boundary condition in either direction will trigger this callback with LoadType.PREPEND or LoadType.APPEND or both. LoadType.REFRESH occurs as a result of initialize.

Parameters
loadType: LoadType LoadType of the boundary condition which triggered this callback.
state: PagingState<Key, Value> A copy of the state including the list of pages currently held in memory of the currently presented PagingData at the time of starting the load. E.g. for load(loadType = END), you can use the page or item at the end as input for what to load from the network.
Return
MediatorResult signifying what LoadState to be passed to the UI, and whether there's more data available.