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