Implement ListenableFuturePagedSource Provides a Java-friendly API leveraging Guava's ListenableFuture wrapping the suspending PagedSource API Test: ./gradlew paging:paging-guava:cC Change-Id: I0add2127d23223bbcc8f1f19ce5b0b52003a1153
diff --git a/paging/guava/api/3.0.0-alpha01.txt b/paging/guava/api/3.0.0-alpha01.txt new file mode 100644 index 0000000..ccb1c62 --- /dev/null +++ b/paging/guava/api/3.0.0-alpha01.txt
@@ -0,0 +1,11 @@ +// Signature format: 3.0 +package androidx.paging { + + public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> { + ctor public ListenableFuturePagedSource(); + method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion); + method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params); + } + +} +
diff --git a/paging/guava/api/api_lint.ignore b/paging/guava/api/api_lint.ignore new file mode 100644 index 0000000..810fe69 --- /dev/null +++ b/paging/guava/api/api_lint.ignore
@@ -0,0 +1,7 @@ +// Baseline format: 1.0 +DocumentExceptions: androidx.paging.ListenableFutureKt#await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R>): + Method ListenableFutureKt.await appears to be throwing java.lang.Throwable; this should be recorded with a @Throws annotation; see https://android.github.io/kotlin-guides/interop.html#document-exceptions + + +MissingNullability: androidx.paging.ListenableFutureKt#await(com.google.common.util.concurrent.ListenableFuture<R>, kotlin.coroutines.Continuation<? super R>): + Missing nullability on method `await` return
diff --git a/paging/guava/api/current.txt b/paging/guava/api/current.txt new file mode 100644 index 0000000..ccb1c62 --- /dev/null +++ b/paging/guava/api/current.txt
@@ -0,0 +1,11 @@ +// Signature format: 3.0 +package androidx.paging { + + public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> { + ctor public ListenableFuturePagedSource(); + method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion); + method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params); + } + +} +
diff --git a/paging/guava/api/public_plus_experimental_3.0.0-alpha01.txt b/paging/guava/api/public_plus_experimental_3.0.0-alpha01.txt new file mode 100644 index 0000000..ccb1c62 --- /dev/null +++ b/paging/guava/api/public_plus_experimental_3.0.0-alpha01.txt
@@ -0,0 +1,11 @@ +// Signature format: 3.0 +package androidx.paging { + + public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> { + ctor public ListenableFuturePagedSource(); + method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion); + method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params); + } + +} +
diff --git a/paging/guava/api/public_plus_experimental_current.txt b/paging/guava/api/public_plus_experimental_current.txt new file mode 100644 index 0000000..ccb1c62 --- /dev/null +++ b/paging/guava/api/public_plus_experimental_current.txt
@@ -0,0 +1,11 @@ +// Signature format: 3.0 +package androidx.paging { + + public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> { + ctor public ListenableFuturePagedSource(); + method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion); + method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params); + } + +} +
diff --git a/paging/guava/api/res-3.0.0-alpha01.txt b/paging/guava/api/res-3.0.0-alpha01.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/paging/guava/api/res-3.0.0-alpha01.txt
diff --git a/paging/guava/api/restricted_3.0.0-alpha01.txt b/paging/guava/api/restricted_3.0.0-alpha01.txt new file mode 100644 index 0000000..ccb1c62 --- /dev/null +++ b/paging/guava/api/restricted_3.0.0-alpha01.txt
@@ -0,0 +1,11 @@ +// Signature format: 3.0 +package androidx.paging { + + public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> { + ctor public ListenableFuturePagedSource(); + method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion); + method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params); + } + +} +
diff --git a/paging/guava/api/restricted_current.txt b/paging/guava/api/restricted_current.txt new file mode 100644 index 0000000..ccb1c62 --- /dev/null +++ b/paging/guava/api/restricted_current.txt
@@ -0,0 +1,11 @@ +// Signature format: 3.0 +package androidx.paging { + + public abstract class ListenableFuturePagedSource<Key, Value> extends androidx.paging.PagedSource<Key,Value> { + ctor public ListenableFuturePagedSource(); + method public suspend Object load(androidx.paging.PagedSource.LoadParams<Key> p, kotlin.coroutines.Continuation<? super androidx.paging.PagedSource.LoadResult<Key,Value>> $completion); + method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedSource.LoadResult<Key,Value>> loadFuture(androidx.paging.PagedSource.LoadParams<Key> params); + } + +} +
diff --git a/paging/guava/build.gradle b/paging/guava/build.gradle new file mode 100644 index 0000000..d5360f9 --- /dev/null +++ b/paging/guava/build.gradle
@@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 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. + */ + +import static androidx.build.dependencies.DependenciesKt.* +import androidx.build.LibraryGroups +import androidx.build.LibraryVersions +import androidx.build.AndroidXExtension +import androidx.build.Publish + +plugins { + id("AndroidXPlugin") + id("com.android.library") + id("kotlin-android") +} + +dependencies { + api(project(":paging:paging-common")) + api(KOTLIN_STDLIB) + api(project(":concurrent:concurrent-futures-ktx")) + + testImplementation project(':internal-testutils-common') + testImplementation(JUNIT) + testImplementation(KOTLIN_TEST) +} + +androidx { + name = "Android Paging Guava" + publish = Publish.SNAPSHOT_AND_RELEASE + mavenVersion = LibraryVersions.PAGING + mavenGroup = LibraryGroups.PAGING + inceptionYear = "2019" + description = "Android Paging Guava" + url = AndroidXExtension.ARCHITECTURE_URL +}
diff --git a/paging/guava/src/main/AndroidManifest.xml b/paging/guava/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ac2ee69 --- /dev/null +++ b/paging/guava/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@ +<!-- + ~ Copyright (C) 2018 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. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="androidx.paging.guava"> +</manifest>
diff --git a/paging/guava/src/main/java/androidx/paging/ListenableFuturePagedSource.kt b/paging/guava/src/main/java/androidx/paging/ListenableFuturePagedSource.kt new file mode 100644 index 0000000..0db9e0d --- /dev/null +++ b/paging/guava/src/main/java/androidx/paging/ListenableFuturePagedSource.kt
@@ -0,0 +1,36 @@ +/* + * Copyright 2019 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. + */ + +package androidx.paging + +import androidx.concurrent.futures.await +import com.google.common.util.concurrent.ListenableFuture + +/** + * [ListenableFuture]-based compatibility wrapper around [PagedSource]'s suspending APIs. + */ +abstract class ListenableFuturePagedSource<Key : Any, Value : Any> : PagedSource<Key, Value>() { + /** + * Loading API for [PagedSource]. + * + * Implement this method to trigger your async load (e.g. from database or network). + */ + abstract fun loadFuture(params: LoadParams<Key>): ListenableFuture<LoadResult<Key, Value>> + + override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> { + return loadFuture(params).await() + } +}
diff --git a/paging/guava/src/test/java/androidx/paging/ListenableFuturePagedSourceTest.kt b/paging/guava/src/test/java/androidx/paging/ListenableFuturePagedSourceTest.kt new file mode 100644 index 0000000..dc59b05 --- /dev/null +++ b/paging/guava/src/test/java/androidx/paging/ListenableFuturePagedSourceTest.kt
@@ -0,0 +1,84 @@ +/* + * Copyright 2019 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. + */ + +package androidx.paging + +import androidx.concurrent.futures.ResolvableFuture +import androidx.paging.PagedSource.LoadParams +import androidx.paging.PagedSource.LoadResult.Page +import com.google.common.util.concurrent.ListenableFuture +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +@RunWith(JUnit4::class) +class ListenableFuturePagedSourceTest { + private fun loadInternal(params: LoadParams<Int>): Page<Int, Int> { + val key = params.key!! // Intentionally fail on null keys + require(key >= 0) // Intentionally throw on negative key + + return Page( + List(params.loadSize) { it + key }, + prevKey = key - params.loadSize, + nextKey = key + params.loadSize + ) + } + + private val pagedSource = object : PagedSource<Int, Int>() { + override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> { + return loadInternal(params) + } + } + + private val listenableFuturePagedSource = object : ListenableFuturePagedSource<Int, Int>() { + override fun loadFuture(params: LoadParams<Int>): ListenableFuture<LoadResult<Int, Int>> { + val future = ResolvableFuture.create<LoadResult<Int, Int>>() + try { + future.set(loadInternal(params)) + } catch (e: IllegalArgumentException) { + future.setException(e) + } + return future + } + } + + @Test + fun basic() = runBlocking { + val params = LoadParams(LoadType.REFRESH, 0, 2, false, 2) + assertEquals(pagedSource.load(params), listenableFuturePagedSource.load(params)) + } + + @Test + fun error() { + runBlocking { + val params = LoadParams<Int>(LoadType.REFRESH, null, 2, false, 2) + assertFailsWith<NullPointerException> { pagedSource.load(params) } + assertFailsWith<NullPointerException> { listenableFuturePagedSource.load(params) } + } + } + + @Test + fun errorWrapped() { + runBlocking { + val params = LoadParams(LoadType.REFRESH, -1, 2, false, 2) + assertFailsWith<IllegalArgumentException> { pagedSource.load(params) } + assertFailsWith<IllegalArgumentException> { listenableFuturePagedSource.load(params) } + } + } +}