# RemoteMediator

Artifact: [androidx.paging:paging-common](https://developer.android.com/jetpack/androidx/releases/paging) [View Source](https://cs.android.com/search?q=file:androidx/paging/RemoteMediator.kt+class:androidx.paging.RemoteMediator) Added in [3.0.0](https://developer.android.com/jetpack/androidx/releases/paging#3.0.0)

*** ** * ** ***

Common/AllAndroid/JVMNative/C/iOSJavaScript
- Cmn

  ```
  @ExperimentalPagingApi
  abstract class RemoteMediator<Key : Any, Value : Any>
  ```
Known direct subclasses [ListenableFutureRemoteMediator](https://developer.android.com/reference/kotlin/androidx/paging/ListenableFutureRemoteMediator), [RxRemoteMediator](https://developer.android.com/reference/kotlin/androidx/paging/rxjava2/RxRemoteMediator), [RxRemoteMediator](https://developer.android.com/reference/kotlin/androidx/paging/rxjava3/RxRemoteMediator)

|---|---|
| `https://developer.android.com/reference/kotlin/androidx/paging/ListenableFutureRemoteMediator` | `https://guava.dev/releases/snapshot-android/api/docs/com/google/common/util/concurrent/ListenableFuture.html`-based compatibility wrapper around `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator`'s suspending APIs. |
| `https://developer.android.com/reference/kotlin/androidx/paging/rxjava2/RxRemoteMediator` | RxJava2 compatibility wrapper around `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator`'s suspending APIs. |
| `https://developer.android.com/reference/kotlin/androidx/paging/rxjava3/RxRemoteMediator` | RxJava3 compatibility wrapper around `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator`'s suspending APIs. |

*** ** * ** ***

Defines a set of callbacks used to incrementally load data from a remote source into a local source wrapped by a `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource`, e.g., loading data from network into a local db cache.

A `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator` is registered by passing it to `https://developer.android.com/reference/kotlin/androidx/paging/Pager`'s constructor.

`https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator` allows hooking into the following events:

- Stream initialization

- `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` signal driven from UI

- `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource` returns a `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult` which signals a boundary condition, i.e., the most recent `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page` in the `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` or `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` direction has `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page#prevKey()` or `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page#nextKey()` set to `null` respectively.

```kotlin
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.paging.samples.shared.ExampleBackendService
import androidx.paging.samples.shared.RoomDb
import androidx.paging.samples.shared.User
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 initialize(): InitializeAction {
        val cacheTimeout = TimeUnit.HOURS.convert(1, TimeUnit.MILLISECONDS)
        return if (System.currentTimeMillis() - userDao.lastUpdated() >= cacheTimeout) {
            // Cached data is up-to-date, so there is no need to re-fetch from network.
            InitializeAction.SKIP_INITIAL_REFRESH
        } else {
            // Need to refresh cached data from network; returning LAUNCH_INITIAL_REFRESH here
            // will also block RemoteMediator's APPEND and PREPEND from running until REFRESH
            // succeeds.
            InitializeAction.LAUNCH_INITIAL_REFRESH
        }
    }

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

```kotlin
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.paging.samples.shared.ExampleBackendService
import androidx.paging.samples.shared.RemoteKey
import androidx.paging.samples.shared.RoomDb
import androidx.paging.samples.shared.User
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 initialize(): InitializeAction {
        val cacheTimeout = TimeUnit.HOURS.convert(1, TimeUnit.MILLISECONDS)
        return if (System.currentTimeMillis() - userDao.lastUpdated() >= cacheTimeout) {
            // Cached data is up-to-date, so there is no need to re-fetch from network.
            InitializeAction.SKIP_INITIAL_REFRESH
        } else {
            // Need to refresh cached data from network; returning LAUNCH_INITIAL_REFRESH here
            // will also block RemoteMediator's APPEND and PREPEND from running until REFRESH
            // succeeds.
            InitializeAction.LAUNCH_INITIAL_REFRESH
        }
    }

    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 types |
|---|
| `enum https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction : https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-enum/index.html<https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction>` Return type of `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()`, which signals the action to take after `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()` completes. |
| `sealed class https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult` Return type of `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)`, which determines `https://developer.android.com/reference/kotlin/androidx/paging/LoadState`. |
| `class https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult.Error : https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult` Recoverable error that can be retried, sets the `https://developer.android.com/reference/kotlin/androidx/paging/LoadState` to `https://developer.android.com/reference/kotlin/androidx/paging/LoadState.Error`. |
| `class https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult.Success : https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult` Success signaling that `https://developer.android.com/reference/kotlin/androidx/paging/LoadState` should be set to `https://developer.android.com/reference/kotlin/androidx/paging/LoadState.NotLoading` if `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult.Success#endOfPaginationReached()` is `true`, otherwise `https://developer.android.com/reference/kotlin/androidx/paging/LoadState` is kept at `https://developer.android.com/reference/kotlin/androidx/paging/LoadState.Loading` to await invalidation. |

| ### Public constructors |
|---|---|
| `<Key : https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/index.html, Value : https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-any/index.html> https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#RemoteMediator()()` | Cmn |

| ### Public functions |
|---|---|---|
| `open suspend https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction` | `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()()` Callback fired during initialization of a `https://developer.android.com/reference/kotlin/androidx/paging/PagingData` stream, before initial load. | Cmn |
| `abstract suspend https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult` | `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)(loadType: https://developer.android.com/reference/kotlin/androidx/paging/LoadType, state: https://developer.android.com/reference/kotlin/androidx/paging/PagingState<Key, Value>)` Callback triggered when Paging needs to request more data from a remote source due to any of the following events: \* Stream initialization if `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()` returns `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction#LAUNCH_INITIAL_REFRESH` | Cmn |

## Public constructors

### RemoteMediator

Cmn

```
<Key : Any, Value : Any> RemoteMediator()
```

## Public functions

### initialize

Cmn

```
open suspend fun initialize(): RemoteMediator.InitializeAction
```

Callback fired during initialization of a `https://developer.android.com/reference/kotlin/androidx/paging/PagingData` stream, before initial load.

This function runs to completion before any loading is performed.

| Returns |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction` | `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction` used to control whether `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)` with load type `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` will be immediately dispatched when the first `https://developer.android.com/reference/kotlin/androidx/paging/PagingData` is submitted: \* `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction#LAUNCH_INITIAL_REFRESH` to immediately dispatch `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)` asynchronously with load type `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH`, to update paginated content when the stream is initialized. Note: This also prevents `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator` from triggering `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` or `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` until `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` succeeds. \* `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction#SKIP_INITIAL_REFRESH` to wait for a refresh request from the UI before dispatching `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)` asynchronously with load type `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH`. |

### load

Cmn

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

Callback triggered when Paging needs to request more data from a remote source due to any of the following events: \* Stream initialization if `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()` returns `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction#LAUNCH_INITIAL_REFRESH`

- `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` signal driven from UI

- `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource` returns a `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult` which signals a boundary condition, i.e., the most recent `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page` in the `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` or `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` direction has `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page#prevKey()` or `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page#nextKey()` set to `null` respectively.

It is the responsibility of this method to update the backing dataset and trigger `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource#invalidate()` to allow androidx.paging.PagingDataAdapter to pick up new items found by `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)`.

The runtime and result of this method defines the remote `https://developer.android.com/reference/kotlin/androidx/paging/LoadState` behavior sent to the UI via `https://developer.android.com/reference/kotlin/androidx/paging/CombinedLoadStates`.

This method is never called concurrently *unless* `https://developer.android.com/reference/kotlin/androidx/paging/Pager#flow()` has multiple collectors. Note that Paging might cancel calls to this function if it is currently executing a `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` or `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` and a `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` is requested. In that case, `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` has higher priority and will be executed after the previous call is cancelled. If the `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)` call with `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` returns an error, Paging will call `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#load(androidx.paging.LoadType,androidx.paging.PagingState)` with the previously cancelled `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` or `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` request. If `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` succeeds, it won't make the `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` or `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` requests unless they are necessary again after the `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` is applied to the UI.

| Parameters |
|---|---|
| `loadType: https://developer.android.com/reference/kotlin/androidx/paging/LoadType` | `https://developer.android.com/reference/kotlin/androidx/paging/LoadType` of the condition which triggered this callback. \* `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` indicates the end of pagination in the `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#PREPEND` direction was reached. This occurs when `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource#load(androidx.paging.PagingSource.LoadParams)` returns a `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page` with `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page#prevKey()` == `null`. \* `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` indicates the end of pagination in the `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#APPEND` direction was reached. This occurs when `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource#load(androidx.paging.PagingSource.LoadParams)` returns a `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page` with `https://developer.android.com/reference/kotlin/androidx/paging/PagingSource.LoadResult.Page#nextKey()` == `null`. \* `https://developer.android.com/reference/kotlin/androidx/paging/LoadType#REFRESH` indicates this method was triggered due to a requested refresh. Generally, this means that a request to load remote data and **replace** all local data was made. This can happen when: \* Stream initialization if `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator#initialize()` returns `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.InitializeAction#LAUNCH_INITIAL_REFRESH` \* An explicit call to refresh driven by the UI |
| `state: https://developer.android.com/reference/kotlin/androidx/paging/PagingState<Key, Value>` | A copy of the state including the list of pages currently held in memory of the currently presented `https://developer.android.com/reference/kotlin/androidx/paging/PagingData` at the time of starting the load. E.g. for load(loadType = APPEND), you can use the page or item at the end as input for what to load from the network. |

| Returns |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult` | `https://developer.android.com/reference/kotlin/androidx/paging/RemoteMediator.MediatorResult` signifying what `https://developer.android.com/reference/kotlin/androidx/paging/LoadState` to be passed to the UI, and whether there's more data available. |