ListenableFuturePagingData.kt

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

@file:JvmName("PagingDataFutures")

package androidx.paging

import androidx.annotation.CheckResult
import com.google.common.util.concurrent.AsyncFunction
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.withContext
import java.util.concurrent.Executor

/**
 * Returns a [PagingData] containing the result of applying the given [transform] to each
 * element, as it is loaded.
 *
 * @param transform [AsyncFunction] to transform an item of type [T] to [R].
 * @param executor [Executor] to run the [AsyncFunction] in.
 */
@JvmName("map")
@CheckResult
fun <T : Any, R : Any> PagingData<T>.mapAsync(
    transform: AsyncFunction<T, R>,
    executor: Executor
): PagingData<R> = map {
    withContext(executor.asCoroutineDispatcher()) {
        transform.apply(it).await()
    }
}

/**
 * Returns a [PagingData] of all elements returned from applying the given [transform] to each
 * element, as it is loaded.
 *
 * @param transform [AsyncFunction] to transform an item of type [T] a list of items of type [R].
 * @param executor [Executor] to run the [AsyncFunction] in.
 */
@JvmName("flatMap")
@CheckResult
fun <T : Any, R : Any> PagingData<T>.flatMapAsync(
    transform: AsyncFunction<T, Iterable<R>>,
    executor: Executor
): PagingData<R> = flatMap {
    withContext(executor.asCoroutineDispatcher()) {
        transform.apply(it).await()
    }
}

/**
 * Returns a [PagingData] containing only elements matching the given [predicate].
 *
 * @param predicate [AsyncFunction] returning `false` for unmatched items which should be filtered.
 * @param executor [Executor] to run the [AsyncFunction] in.
 */
@JvmName("filter")
@CheckResult
fun <T : Any> PagingData<T>.filterAsync(
    predicate: AsyncFunction<T, Boolean>,
    executor: Executor
): PagingData<T> = filter {
    withContext(executor.asCoroutineDispatcher()) {
        predicate.apply(it).await()
    }
}

/**
 * Returns a [PagingData] containing each original element, with an optional separator generated
 * by [generator], given the elements before and after (or null, in boundary conditions).
 *
 * Note that this transform is applied asynchronously, as pages are loaded. Potential separators
 * between pages are only computed once both pages are loaded.
 *
 * @param generator [AsyncFunction] used to generate separator between two [AdjacentItems] or the
 * header or footer if either [AdjacentItems.before] or [AdjacentItems.after] is `null`.
 * @param executor [Executor] to run the [AsyncFunction] in.
 *
 * @sample androidx.paging.samples.insertSeparatorsFutureSample
 * @sample androidx.paging.samples.insertSeparatorsUiModelFutureSample
 */
@JvmName("insertSeparators")
@CheckResult
fun <T : R, R : Any> PagingData<T>.insertSeparatorsAsync(
    generator: AsyncFunction<AdjacentItems<T>, R?>,
    executor: Executor
): PagingData<R> = insertSeparators { before, after ->
    withContext(executor.asCoroutineDispatcher()) {
        generator.apply(AdjacentItems(before, after)).await()
    }
}

/**
 * Represents a pair of adjacent items, null values are used to signal boundary conditions.
 */
data class AdjacentItems<T>(val before: T?, val after: T?)