Merge "Update RelationWriter to not expect an ArrayMap-like data structure." into androidx-master-dev
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index a073594..eecbaee 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -38,6 +38,9 @@
androidTestImplementation(ESPRESSO_CORE, libs.exclude_for_espresso)
androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it"s own MockMaker
+ androidTestImplementation project(':internal-testutils'), {
+ exclude group: 'androidx.activity', module: 'activity'
+ }
}
androidx {
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt
index d8972b9..4b084d1 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityLifecycleTest.kt
@@ -20,35 +20,31 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
-import org.junit.Rule
+import androidx.testutils.withActivity
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
@LargeTest
@RunWith(AndroidJUnit4::class)
class ComponentActivityLifecycleTest {
- @get:Rule
- val activityRule = ActivityTestRule(LifecycleComponentActivity::class.java, false, false)
-
@Test
@Throws(Throwable::class)
fun testLifecycleObserver() {
- activityRule.launchActivity(null)
- val activity = activityRule.activity
- val activityCallbackLifecycleOwner = activity.activityCallbackLifecycleOwner
- val lifecycleObserver = activity.lifecycleObserver
- val countDownLatch = activity.destroyCountDownLatch
- activityRule.finishActivity()
- countDownLatch.await(1, TimeUnit.SECONDS)
+ lateinit var activity: LifecycleComponentActivity
+ lateinit var activityCallbackLifecycleOwner: LifecycleOwner
+ lateinit var lifecycleObserver: LifecycleEventObserver
+ ActivityScenario.launch(LifecycleComponentActivity::class.java).use { scenario ->
+ activity = scenario.withActivity { this }
+ activityCallbackLifecycleOwner = activity.activityCallbackLifecycleOwner
+ lifecycleObserver = activity.lifecycleObserver
+ }
// The Activity's lifecycle callbacks should fire first,
// followed by the activity's lifecycle observers
@@ -84,7 +80,6 @@
class LifecycleComponentActivity : ComponentActivity() {
val activityCallbackLifecycleOwner: LifecycleOwner = mock(LifecycleOwner::class.java)
val lifecycleObserver: LifecycleEventObserver = mock(LifecycleEventObserver::class.java)
- val destroyCountDownLatch = CountDownLatch(1)
init {
lifecycle.addObserver(lifecycleObserver)
@@ -118,6 +113,5 @@
override fun onDestroy() {
lifecycleObserver.onStateChanged(activityCallbackLifecycleOwner, Lifecycle.Event.ON_DESTROY)
super.onDestroy()
- destroyCountDownLatch.countDown()
}
}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
index 09bd000..251a035 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
@@ -19,22 +19,19 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleRegistry
import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.rule.ActivityTestRule
+import androidx.test.filters.LargeTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.fail
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-@SmallTest
+@LargeTest
@RunWith(AndroidJUnit4::class)
class ComponentActivityOverrideLifecycleTest {
- @get:Rule
- val activityRule = ActivityTestRule(LazyOverrideLifecycleComponentActivity::class.java)
-
@UiThreadTest
@Test
fun testEagerOverride() {
@@ -46,19 +43,18 @@
}
}
- @UiThreadTest
@Test
fun testOverrideLifecycle() {
- val activity = activityRule.activity
-
- assertThat(activity.lifecycle.currentState)
- .isEqualTo(Lifecycle.State.RESUMED)
+ with(ActivityScenario.launch(LazyOverrideLifecycleComponentActivity::class.java)) {
+ assertThat(withActivity { lifecycle.currentState })
+ .isEqualTo(Lifecycle.State.RESUMED)
+ }
}
}
class EagerOverrideLifecycleComponentActivity : ComponentActivity() {
- val overrideLifecycle = LifecycleRegistry(this)
+ private val overrideLifecycle = LifecycleRegistry(this)
override fun getLifecycle(): Lifecycle {
return overrideLifecycle
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityRunOnNextRecreateTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityRunOnNextRecreateTest.kt
index 7920292e..a861d84c 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityRunOnNextRecreateTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityRunOnNextRecreateTest.kt
@@ -20,11 +20,11 @@
import androidx.lifecycle.LifecycleEventObserver
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
-import com.google.common.truth.Truth
-import org.junit.Rule
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,9 +32,6 @@
@RunWith(AndroidJUnit4::class)
class ComponentActivityRunOnNextRecreateTest {
- @get:Rule
- val activityRule = ActivityTestRule<AutoRestarterActivity>(AutoRestarterActivity::class.java)
-
private class Restarted2 : SavedStateRegistry.AutoRecreated {
override fun onRecreated(owner: SavedStateRegistryOwner) {
(owner as? AutoRestarterActivity)?.restartedValue = "restarted"
@@ -43,13 +40,12 @@
@Test
fun test() {
- activityRule.runOnUiThread {
- activityRule.activity.savedStateRegistry
- .runOnNextRecreation(Restarted2::class.java)
- }
- val newActivity = recreateActivity(activityRule)
- newActivity.runOnUiThread {
- Truth.assertThat(newActivity.observerExecuted).isTrue()
+ with(ActivityScenario.launch(AutoRestarterActivity::class.java)) {
+ withActivity {
+ savedStateRegistry.runOnNextRecreation(Restarted2::class.java)
+ }
+ recreate()
+ assertThat(withActivity { observerExecuted }).isTrue()
}
}
}
@@ -62,7 +58,7 @@
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
lifecycle.addObserver(LifecycleEventObserver { _, _ ->
- Truth.assertThat(restartedValue).isEqualTo("restarted")
+ assertThat(restartedValue).isEqualTo("restarted")
observerExecuted = true
})
}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
index 3c2c78a..139349f 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
@@ -18,15 +18,13 @@
import android.os.Bundle
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
import androidx.savedstate.SavedStateRegistry
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import androidx.test.rule.ActivityTestRule
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import org.junit.After
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -34,59 +32,53 @@
@RunWith(AndroidJUnit4::class)
class ComponentActivitySavedStateTest {
- @get:Rule
- val activityRule = ActivityTestRule<SavedStateActivity>(SavedStateActivity::class.java)
-
@After
fun clear() {
SavedStateActivity.checkEnabledInOnCreate = false
}
- @Throws(Throwable::class)
- private fun initializeSavedState(): SavedStateActivity {
- val activity = activityRule.activity
- activityRule.runOnUiThread {
- assertThat(activity.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
- val registry = activity.savedStateRegistry
- val savedState = registry.consumeRestoredStateForKey(CALLBACK_KEY)
- assertThat(savedState).isNull()
- registry.registerSavedStateProvider(CALLBACK_KEY, DefaultProvider())
- }
- return activity
+ private fun ActivityScenario<SavedStateActivity>.initializeSavedState() = withActivity {
+ assertThat(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
+ val registry = savedStateRegistry
+ val savedState = registry.consumeRestoredStateForKey(CALLBACK_KEY)
+ assertThat(savedState).isNull()
+ registry.registerSavedStateProvider(CALLBACK_KEY, DefaultProvider())
}
@Test
@Throws(Throwable::class)
fun savedState() {
- initializeSavedState()
- val recreated = recreateActivity(activityRule)
- activityRule.runOnUiThread {
- assertThat(recreated.lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
- checkDefaultSavedState(recreated.savedStateRegistry)
+ with(ActivityScenario.launch(SavedStateActivity::class.java)) {
+ initializeSavedState()
+ recreate()
+ moveToState(Lifecycle.State.CREATED)
+ withActivity {
+ assertThat(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
+ checkDefaultSavedState(savedStateRegistry)
+ }
}
}
@Test
@Throws(Throwable::class)
fun savedStateLateInit() {
- initializeSavedState()
- val recreated = recreateActivity(activityRule)
- activityRule.runOnUiThread {
- recreated.lifecycle.addObserver(object : LifecycleObserver {
- @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
- fun onResume() {
- checkDefaultSavedState(recreated.savedStateRegistry)
- }
- })
+ with(ActivityScenario.launch(SavedStateActivity::class.java)) {
+ initializeSavedState()
+ recreate()
+ withActivity {
+ checkDefaultSavedState(savedStateRegistry)
+ }
}
}
@Test
@Throws(Throwable::class)
fun savedStateEarlyRegister() {
- initializeSavedState()
- SavedStateActivity.checkEnabledInOnCreate = true
- recreateActivity(activityRule)
+ with(ActivityScenario.launch(SavedStateActivity::class.java)) {
+ initializeSavedState()
+ SavedStateActivity.checkEnabledInOnCreate = true
+ recreate()
+ }
}
}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt
index 51d2983..b7a8b3f 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityViewModelTest.kt
@@ -17,34 +17,23 @@
package androidx.activity
import android.os.Bundle
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.test.annotation.UiThreadTest
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
@LargeTest
@RunWith(AndroidJUnit4::class)
class ComponentActivityViewModelTest {
- companion object {
- private const val TIMEOUT = 2 // secs
- }
-
- @get:Rule
- var activityRule = ActivityTestRule(ViewModelActivity::class.java)
-
@Test(expected = IllegalStateException::class)
@UiThreadTest
fun testNotAttachedActivity() {
@@ -54,53 +43,41 @@
@Test
fun testSameViewModelStorePrePostOnCreate() {
- val activity = activityRule.activity
- assertWithMessage(
- "Pre-onCreate() ViewModelStore should equal the post-onCreate() ViewModelStore")
- .that(activity.preOnCreateViewModelStore)
- .isSameInstanceAs(activity.postOnCreateViewModelStore)
+ with(ActivityScenario.launch(ViewModelActivity::class.java)) {
+ assertWithMessage(
+ "Pre-onCreate() ViewModelStore should equal the post-onCreate() ViewModelStore")
+ .that(withActivity { preOnCreateViewModelStore })
+ .isSameInstanceAs(withActivity { postOnCreateViewModelStore })
+ }
}
@Test
fun testSameActivityViewModels() {
- val activityModel = arrayOfNulls<TestViewModel>(1)
- val defaultActivityModel = arrayOfNulls<TestViewModel>(1)
- val viewModelActivity = activityRule.activity
- activityRule.runOnUiThread {
- activityModel[0] = viewModelActivity.activityModel
- defaultActivityModel[0] = viewModelActivity.defaultActivityModel
- assertThat(defaultActivityModel[0]).isNotSameInstanceAs(activityModel[0])
- }
- val recreatedActivity = recreateActivity(activityRule)
- activityRule.runOnUiThread {
- assertThat(recreatedActivity.activityModel)
- .isSameInstanceAs(activityModel[0])
- assertThat(recreatedActivity.defaultActivityModel)
- .isSameInstanceAs(defaultActivityModel[0])
+ with(ActivityScenario.launch(ViewModelActivity::class.java)) {
+ val activityModel = withActivity { activityModel }
+ val defaultActivityModel = withActivity { defaultActivityModel }
+ assertThat(defaultActivityModel).isNotSameInstanceAs(activityModel)
+
+ recreate()
+
+ assertThat(withActivity { activityModel })
+ .isSameInstanceAs(activityModel)
+ assertThat(withActivity { defaultActivityModel })
+ .isSameInstanceAs(defaultActivityModel)
}
}
@Test
@Throws(Throwable::class)
fun testActivityOnCleared() {
- val activity = activityRule.activity
- val latch = CountDownLatch(1)
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_DESTROY) {
- activity.window.decorView.post {
- try {
- assertThat(activity.activityModel.cleared).isTrue()
- assertThat(activity.defaultActivityModel.cleared).isTrue()
- } finally {
- latch.countDown()
- }
- }
- }
+ lateinit var activityModel: TestViewModel
+ lateinit var defaultActivityModel: TestViewModel
+ ActivityScenario.launch(ViewModelActivity::class.java).use { scenario ->
+ activityModel = scenario.withActivity { this.activityModel }
+ defaultActivityModel = scenario.withActivity { this.defaultActivityModel }
}
-
- activityRule.runOnUiThread { activity.lifecycle.addObserver(observer) }
- activity.finish()
- assertThat(latch.await(TIMEOUT.toLong(), TimeUnit.SECONDS)).isTrue()
+ assertThat(activityModel.cleared).isTrue()
+ assertThat(defaultActivityModel.cleared).isTrue()
}
}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
index eff215c..ce319d6 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ContentViewTest.kt
@@ -18,11 +18,11 @@
import android.widget.TextView
import androidx.activity.test.R
+import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import androidx.test.rule.ActivityTestRule
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,15 +30,13 @@
@RunWith(AndroidJUnit4::class)
class ContentViewTest {
- @get:Rule
- val activityRule = ActivityTestRule(ContentViewActivity::class.java)
-
@Test
fun testLifecycleObserver() {
- val activity = activityRule.activity
- val inflatedTextView: TextView = activity.findViewById(R.id.inflated_text_view)
- assertThat(inflatedTextView)
- .isNotNull()
+ with(ActivityScenario.launch(ContentViewActivity::class.java)) {
+ val inflatedTextView: TextView = withActivity { findViewById(R.id.inflated_text_view) }
+ assertThat(inflatedTextView)
+ .isNotNull()
+ }
}
}
diff --git a/activity/activity/src/androidTest/java/androidx/activity/Recreater.kt b/activity/activity/src/androidTest/java/androidx/activity/Recreater.kt
deleted file mode 100644
index 1ab416a8..0000000
--- a/activity/activity/src/androidTest/java/androidx/activity/Recreater.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.activity
-
-import android.app.Activity
-import android.app.Instrumentation
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
-
-@Throws(Throwable::class)
-internal fun <T : Activity> recreateActivity(activityRule: ActivityTestRule<T>): T {
- val previous = activityRule.activity
- val monitor = Instrumentation.ActivityMonitor(previous.javaClass.canonicalName, null, false)
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- instrumentation.addMonitor(monitor)
- activityRule.runOnUiThread { previous.recreate() }
- var result = previous
-
- // this guarantee that we will reinstall monitor between notifications about onDestroy
- // and onCreate
-
- synchronized(monitor) {
- do {
- // the documentation says "Block until an Activity is created
- // that matches this monitor." This statement is true, but there are some other
- // true statements like: "Block until an Activity is destroyed" or
- // "Block until an Activity is resumed"...
-
- // this call will release synchronization monitor's monitor
- @Suppress("UNCHECKED_CAST")
- result = monitor.waitForActivityWithTimeout(4000) as T?
- ?: throw RuntimeException("Timeout. Failed to recreate an activity")
- } while (result === previous)
- }
- return result
-}
\ No newline at end of file
diff --git a/benchmark/api/1.0.0-alpha03.txt b/benchmark/api/1.0.0-alpha03.txt
index a1eb721..92ebcf3 100644
--- a/benchmark/api/1.0.0-alpha03.txt
+++ b/benchmark/api/1.0.0-alpha03.txt
@@ -23,7 +23,7 @@
public final class BenchmarkState {
method public boolean keepRunning();
method public void pauseTiming();
- method public static void reportData(String className, String testName, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=1) int repeatIterations);
+ method public static void reportData(String className, String testName, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
method public void resumeTiming();
}
diff --git a/benchmark/api/current.txt b/benchmark/api/current.txt
index a1eb721..92ebcf3 100644
--- a/benchmark/api/current.txt
+++ b/benchmark/api/current.txt
@@ -23,7 +23,7 @@
public final class BenchmarkState {
method public boolean keepRunning();
method public void pauseTiming();
- method public static void reportData(String className, String testName, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=1) int repeatIterations);
+ method public static void reportData(String className, String testName, java.util.List<java.lang.Long> dataNs, @IntRange(from=0) int warmupIterations, @IntRange(from=0) long thermalThrottleSleepSeconds, @IntRange(from=1) int repeatIterations);
method public void resumeTiming();
}
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
index fe7b13e..56da1d5 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
+++ b/benchmark/src/androidTest/java/androidx/benchmark/BenchmarkStateTest.kt
@@ -125,12 +125,13 @@
@Test
fun reportResult() {
- BenchmarkState.reportData("className", "testName", listOf(100), 1, 1)
+ BenchmarkState.reportData("className", "testName", listOf(100), 1, 0, 1)
val expectedReport = BenchmarkState.Report(
className = "className",
testName = "testName",
data = listOf(100),
repeatIterations = 1,
+ thermalThrottleSleepSeconds = 0,
warmupIterations = 1
)
assertEquals(expectedReport, ResultWriter.reports.last())
diff --git a/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt b/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
index d2f8c97..d4c9c3a 100644
--- a/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
+++ b/benchmark/src/androidTest/java/androidx/benchmark/ResultWriterTest.kt
@@ -37,6 +37,7 @@
className = "package.Class1",
data = listOf(100, 101, 102),
repeatIterations = 100000,
+ thermalThrottleSleepSeconds = 90000000,
warmupIterations = 8000
)
private val reportB = BenchmarkState.Report(
@@ -44,6 +45,7 @@
className = "package.Class2",
data = listOf(100, 101, 102),
repeatIterations = 100000,
+ thermalThrottleSleepSeconds = 90000000,
warmupIterations = 8000
)
@@ -97,7 +99,8 @@
}
},
"warmupIterations": 8000,
- "repeatIterations": 100000
+ "repeatIterations": 100000,
+ "thermalThrottleSleepSeconds": 90000000
},
{
"name": "MethodB",
@@ -115,7 +118,8 @@
}
},
"warmupIterations": 8000,
- "repeatIterations": 100000
+ "repeatIterations": 100000,
+ "thermalThrottleSleepSeconds": 90000000
}
]
}
diff --git a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
index 0540cb8..c241d5f 100644
--- a/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
+++ b/benchmark/src/main/java/androidx/benchmark/BenchmarkState.kt
@@ -21,6 +21,7 @@
import android.os.Bundle
import android.os.Debug
import android.util.Log
+import androidx.annotation.IntRange
import androidx.annotation.VisibleForTesting
import androidx.test.platform.app.InstrumentationRegistry
import java.io.File
@@ -70,9 +71,11 @@
private var startTimeNs: Long = 0 // System.nanoTime() at start of last warmup/test iter.
- private var paused: Boolean = false
+ private var paused = false
private var pausedTimeNs: Long = 0 // The System.nanoTime() when the pauseTiming() is called.
private var pausedDurationNs: Long = 0 // The duration of paused state in nano sec.
+ private var thermalThrottleSleepSeconds: Long =
+ 0 // The duration of sleep due to thermal throttling.
private var repeatCount = 0
@@ -205,6 +208,7 @@
pausedDurationNs = 0
iterationsRemaining = maxIterations
repeatCount = 0
+ thermalThrottleSleepSeconds = 0
state = RUNNING
startTimeNs = System.nanoTime()
}
@@ -218,7 +222,7 @@
if (repeatCount >= REPEAT_COUNT) {
if (performThrottleChecks &&
throttleRemainingRetries > 0 &&
- sleepIfThermalThrottled()
+ sleepIfThermalThrottled(THROTTLE_BACKOFF_S)
) {
// We've slept due to thermal throttle - retry benchmark!
throttleRemainingRetries -= 1
@@ -331,6 +335,7 @@
val testName: String,
val data: List<Long>,
val repeatIterations: Int,
+ val thermalThrottleSleepSeconds: Long,
val warmupIterations: Int
) {
val stats = Stats(data)
@@ -341,6 +346,7 @@
testName = testName,
data = results,
repeatIterations = maxIterations,
+ thermalThrottleSleepSeconds = thermalThrottleSleepSeconds,
warmupIterations = warmupIteration
)
@@ -375,6 +381,18 @@
InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, bundle)
}
+ private fun sleepIfThermalThrottled(sleepSeconds: Long) = when {
+ ThrottleDetector.isDeviceThermalThrottled() -> {
+ Log.d(TAG, "THERMAL THROTTLE DETECTED, SLEEPING FOR $sleepSeconds SECONDS")
+ val startTime = System.nanoTime()
+ Thread.sleep(TimeUnit.SECONDS.toMillis(sleepSeconds))
+ val sleepTime = System.nanoTime() - startTime
+ thermalThrottleSleepSeconds += TimeUnit.NANOSECONDS.toSeconds(sleepTime)
+ true
+ }
+ else -> false
+ }
+
internal companion object {
private const val TAG = "Benchmark"
private const val STUDIO_OUTPUT_KEY_PREFIX = "android.studio.display."
@@ -407,6 +425,8 @@
* @param dataNs List of all measured results, in nanoseconds
* @param warmupIterations Number of iterations of warmup before measurements started.
* Should be no less than 0.
+ * @param thermalThrottleSleepSeconds Number of seconds benchmark was paused during thermal
+ * throttling.
* @param repeatIterations Number of iterations in between each measurement. Should be no
* less than 1.
*/
@@ -416,14 +436,16 @@
className: String,
testName: String,
dataNs: List<Long>,
- @androidx.annotation.IntRange(from = 0) warmupIterations: Int,
- @androidx.annotation.IntRange(from = 1) repeatIterations: Int
+ @IntRange(from = 0) warmupIterations: Int,
+ @IntRange(from = 0) thermalThrottleSleepSeconds: Long,
+ @IntRange(from = 1) repeatIterations: Int
) {
val report = Report(
className = className,
testName = testName,
data = dataNs,
repeatIterations = repeatIterations,
+ thermalThrottleSleepSeconds = thermalThrottleSleepSeconds,
warmupIterations = warmupIterations
)
@@ -438,16 +460,6 @@
ResultWriter.appendReport(report)
}
- internal fun sleepIfThermalThrottled(): Boolean {
- return if (ThrottleDetector.isDeviceThermalThrottled()) {
- Log.d(TAG, "THERMAL THROTTLE DETECTED, SLEEPING FOR $THROTTLE_BACKOFF_S SECONDS")
- Thread.sleep(TimeUnit.SECONDS.toMillis(THROTTLE_BACKOFF_S))
- true
- } else {
- false
- }
- }
-
internal fun ideSummaryLineWrapped(key: String, nanos: Long): String {
val warningLines =
WarningState.acquireWarningStringForLogging()?.split("\n") ?: listOf()
diff --git a/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt b/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
index 3ec390a..6233ea0 100644
--- a/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
+++ b/benchmark/src/main/java/androidx/benchmark/ResultWriter.kt
@@ -90,6 +90,7 @@
.name("metrics").metricsObject(report)
.name("warmupIterations").value(report.warmupIterations)
.name("repeatIterations").value(report.repeatIterations)
+ .name("thermalThrottleSleepSeconds").value(report.thermalThrottleSleepSeconds)
return endObject()
}
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index da633e8..1c3c5ee 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -23,7 +23,7 @@
} else {
build_versions.kotlin = '1.3.31'
}
-build_versions.lint = '26.4.0'
+build_versions.lint = '26.5.0-beta04'
build_versions.dokka = '0.9.17-g002'
build_versions.studio = new Properties()
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index e963fcc..8be22fb 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -177,18 +177,10 @@
}
}
is KotlinBasePluginWrapper -> {
- if (project.name == "lifecycle-livedata-eap" || // b/130585490
- project.name == "lifecycle-runtime-eap" ||
- project.name == "lifecycle-runtime-ktx" ||
- project.name == "lifecycle-livedata-ktx" ||
- project.name == "work-runtime-ktx" || // b/130582237
- project.name == "room-compiler" || // b/130580662
- project.name == "room-testapp-kotlin" || // b/130643290
- project.name == "activity" ||
- project.name == "camera-testapp-timing" ||
- project.name == "fragment-testing" ||
- project.name == "benchmark" ||
- project.name == "navigation-safe-args-gradle-plugin") {
+ if (project.name == "lifecycle-runtime-ktx" || // b/134925073
+ project.name == "lifecycle-livedata-ktx" || // b/134925073
+ project.name == "benchmark" || // b/134925431
+ project.name == "navigation-safe-args-gradle-plugin") { // b/134925735
return@all
}
project.tasks.withType(KotlinCompile::class.java).configureEach { compile ->
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 3ca5070..e3d7718 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -90,6 +90,8 @@
prebuilts(LibraryGroups.MEDIA2, "1.0.0-beta02")
prebuilts(LibraryGroups.MEDIAROUTER, "1.1.0-beta02")
ignore(LibraryGroups.NAVIGATION.group, "navigation-testing")
+ ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-generator")
+ ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-gradle-plugin")
prebuilts(LibraryGroups.NAVIGATION, "2.1.0-alpha05")
prebuilts(LibraryGroups.PAGING, "2.1.0")
prebuilts(LibraryGroups.PALETTE, "1.0.0")
@@ -105,7 +107,7 @@
ignore(LibraryGroups.ROOM.group, "room-common-java8")
prebuilts(LibraryGroups.ROOM, "2.1.0-rc01")
prebuilts(LibraryGroups.SAVEDSTATE, "1.0.0-beta01")
- // TODO: Remove this ignore once androidx.security:security-identity-credential:1.0.0-alph01 is released
+ // TODO: Remove this ignore once androidx.security:security-identity-credential:1.0.0-alpha01 is released
ignore(LibraryGroups.SECURITY.group, "security-identity-credential")
prebuilts(LibraryGroups.SECURITY, "1.0.0-alpha02")
prebuilts(LibraryGroups.SHARETARGET, "1.0.0-alpha02")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 759ed6f..d32e9d4 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -63,9 +63,9 @@
const val LEAKCANARY_INSTRUMENTATION =
"com.squareup.leakcanary:leakcanary-android-instrumentation:1.6.2"
const val LINT_API_MIN = "com.android.tools.lint:lint-api:26.3.0"
-const val LINT_API_LATEST = "com.android.tools.lint:lint-api:26.5.0-alpha10"
-const val LINT_CORE = "com.android.tools.lint:lint:26.5.0-alpha10"
-const val LINT_TESTS = "com.android.tools.lint:lint-tests:26.5.0-alpha10"
+const val LINT_API_LATEST = "com.android.tools.lint:lint-api:26.5.0-beta04"
+const val LINT_CORE = "com.android.tools.lint:lint:26.5.0-beta04"
+const val LINT_TESTS = "com.android.tools.lint:lint-tests:26.5.0-beta04"
const val MATERIAL = "com.google.android.material:material:1.0.0"
const val MOCKITO_CORE = "org.mockito:mockito-core:2.19.0"
const val MOCKITO_KOTLIN = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
index e2ac7b9c..7e57766 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/GitClient.kt
@@ -16,7 +16,6 @@
package androidx.build.dependencyTracker
-import com.android.annotations.VisibleForTesting
import org.gradle.api.logging.Logger
import java.io.File
import java.util.concurrent.TimeUnit
@@ -110,9 +109,7 @@
}
companion object {
- @VisibleForTesting
internal const val PREV_MERGE_CMD = "git log -1 --merges --oneline"
- @VisibleForTesting
internal const val CHANGED_FILES_CMD_PREFIX = "git diff --name-only"
}
-}
\ No newline at end of file
+}
diff --git a/buildSrc/studio_versions.properties b/buildSrc/studio_versions.properties
index a28682e..c09109a 100644
--- a/buildSrc/studio_versions.properties
+++ b/buildSrc/studio_versions.properties
@@ -3,8 +3,8 @@
# This file specifies the version of the Android Gradle Plugin and Android Studio to use.
# Android Gradle Plugin version
-agp=3.4.0
+agp=3.5.0-beta04
# Version properties for ./studiow, which correspond to the version of AGP
-studio_version=3.4.0.18
-idea_major_version=183
-studio_build_number=5452501
+studio_version=3.5.0.16
+idea_major_version=191
+studio_build_number=5619324
diff --git a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java
index 31597cb..7015f02 100644
--- a/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java
+++ b/camera/camera2/src/main/java/androidx/camera/camera2/impl/compat/params/OutputConfigurationCompatBaseImpl.java
@@ -16,6 +16,7 @@
package androidx.camera.camera2.impl.compat.params;
+import android.annotation.SuppressLint;
import android.graphics.ImageFormat;
import android.util.Log;
import android.util.Size;
@@ -224,6 +225,7 @@
// only valid up to API 24, and are not guaranteed to work on API levels greater than 23.
//=========================================================================================
+ @SuppressLint("BlockedPrivateApi")
private static Size getSurfaceSize(@NonNull Surface surface) {
try {
Class<?> legacyCameraDeviceClass = Class.forName(LEGACY_CAMERA_DEVICE_CLASS);
@@ -240,6 +242,7 @@
}
}
+ @SuppressLint("BlockedPrivateApi")
private static int getSurfaceFormat(@NonNull Surface surface) {
try {
Class<?> legacyCameraDeviceClass = Class.forName(LEGACY_CAMERA_DEVICE_CLASS);
@@ -257,6 +260,7 @@
}
+ @SuppressLint("SoonBlockedPrivateApi")
private static int getSurfaceGenerationId(@NonNull Surface surface) {
try {
Method getGenerationId = Surface.class.getDeclaredMethod(GET_GENERATION_ID_METHOD);
diff --git a/camera/integration-tests/timingtestapp/src/androidTest/java/androidx/camera/integration/antelope/AntelopeInstrumentedTests.kt b/camera/integration-tests/timingtestapp/src/androidTest/java/androidx/camera/integration/antelope/AntelopeInstrumentedTests.kt
index 015fdaa..ed0d67c 100644
--- a/camera/integration-tests/timingtestapp/src/androidTest/java/androidx/camera/integration/antelope/AntelopeInstrumentedTests.kt
+++ b/camera/integration-tests/timingtestapp/src/androidTest/java/androidx/camera/integration/antelope/AntelopeInstrumentedTests.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.camera.integration.antelope
import android.content.Intent
@@ -608,11 +610,11 @@
val csvDir = File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOCUMENTS), MainActivity.LOG_DIR)
- if (csvDir.exists()) {
+ return if (csvDir.exists()) {
val children = csvDir.listFiles()
- return (children.isEmpty())
+ (children!!.isEmpty())
} else {
- return true
+ true
}
}
@@ -623,11 +625,11 @@
val photoDir = File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM), MainActivity.PHOTOS_DIR)
- if (photoDir.exists()) {
+ return if (photoDir.exists()) {
val children = photoDir.listFiles()
- return (children.isEmpty())
+ (children!!.isEmpty())
} else {
- return true
+ true
}
}
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
index 39547ab..4d27879 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/ImageUtils.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.camera.integration.antelope
import android.annotation.TargetApi
@@ -230,7 +232,7 @@
if (photosDir.exists()) {
- for (photo in photosDir.listFiles())
+ for (photo in photosDir.listFiles()!!)
photo.delete()
// Files are deleted, let media scanner know
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt
index e4ba10b..ea8f545 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/PrefHelper.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.camera.integration.antelope
import android.content.SharedPreferences
diff --git a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
index 6ce01e5..e82f9c3 100644
--- a/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
+++ b/camera/integration-tests/timingtestapp/src/main/java/androidx/camera/integration/antelope/TestResults.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:Suppress("DEPRECATION")
+
package androidx.camera.integration.antelope
import android.content.Intent
@@ -326,7 +328,7 @@
if (csvDir.exists()) {
- for (csv in csvDir.listFiles())
+ for (csv in csvDir.listFiles()!!)
csv.delete()
// Files are deleted, let media scanner know
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
index 689b4f6..0f949a9 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentTestUtil.kt
@@ -15,11 +15,9 @@
*/
package androidx.fragment.app
-import android.app.Activity
import android.os.Build
import android.view.ViewGroup
import androidx.annotation.LayoutRes
-import androidx.test.core.app.ActivityScenario
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.testutils.runOnUiThreadRethrow
@@ -107,13 +105,3 @@
}
}
}
-
-inline fun <reified A : Activity, T : Any> ActivityScenario<A>.withActivity(
- crossinline block: A.() -> T
-): T {
- lateinit var value: T
- onActivity { activity ->
- value = block(activity)
- }
- return value
-}
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
index 1d7806e..e8b042f 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/OnBackPressedCallbackTest.kt
@@ -24,6 +24,7 @@
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Test
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
index dc5c2a4..fe86963 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
@@ -25,6 +25,7 @@
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import androidx.testutils.withActivity
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index b2215a7..fac4b3b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,6 @@
#Tue Aug 16 10:43:36 PDT 2016
+# IMPORTANT: Also update ui/gradle/wrapper/gradle-wrapper.properties
+# when changing this file
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/lifecycle/integration-tests/incrementality/src/test/kotlin/androidx/lifecycle/IncrementalAnnotationProcessingTest.kt b/lifecycle/integration-tests/incrementality/src/test/kotlin/androidx/lifecycle/IncrementalAnnotationProcessingTest.kt
index 438f17a..a0e7b5f 100644
--- a/lifecycle/integration-tests/incrementality/src/test/kotlin/androidx/lifecycle/IncrementalAnnotationProcessingTest.kt
+++ b/lifecycle/integration-tests/incrementality/src/test/kotlin/androidx/lifecycle/IncrementalAnnotationProcessingTest.kt
@@ -39,9 +39,8 @@
private const val BUILD_DIR = "app/build"
private const val SOURCE_DIR = "$MAIN_DIR/java/androidx/lifecycle/incap"
private const val GENERATED_SOURCE_DIR = BUILD_DIR +
- "/generated/source/apt/debug/androidx/lifecycle/incap"
- private const val CLASSES_DIR = BUILD_DIR +
- "/intermediates/javac/debug/compileDebugJavaWithJavac/classes"
+ "/generated/ap_generated_sources/debug/out/androidx/lifecycle/incap"
+ private const val CLASSES_DIR = "$BUILD_DIR/intermediates/javac/debug/classes"
private const val GENERATED_PROGUARD_DIR = "$CLASSES_DIR/META-INF/proguard"
private const val APP_CLASS_DIR = "$CLASSES_DIR/androidx/lifecycle/incap"
}
@@ -350,4 +349,4 @@
supportRepo = properties.getProperty("supportRepo")
}
}
-}
\ No newline at end of file
+}
diff --git a/paging/common/api/2.2.0-alpha01.ignore b/paging/common/api/2.2.0-alpha01.ignore
index e81e503..6d27f48 100644
--- a/paging/common/api/2.2.0-alpha01.ignore
+++ b/paging/common/api/2.2.0-alpha01.ignore
@@ -1,7 +1,9 @@
// Baseline format: 1.0
+AddedAbstractMethod: androidx.paging.PagedList#isContiguous():
+ Added method androidx.paging.PagedList.isContiguous()
+
+
ChangedAbstract: androidx.paging.PagedList#detach():
Method androidx.paging.PagedList.detach has changed 'abstract' qualifier
ChangedAbstract: androidx.paging.PagedList#isDetached():
Method androidx.paging.PagedList.isDetached has changed 'abstract' qualifier
-
-
diff --git a/paging/common/api/2.2.0-alpha01.txt b/paging/common/api/2.2.0-alpha01.txt
index cf51b44..8f87db7 100644
--- a/paging/common/api/2.2.0-alpha01.txt
+++ b/paging/common/api/2.2.0-alpha01.txt
@@ -2,20 +2,34 @@
package androidx.paging {
public abstract class DataSource<Key, Value> {
- method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ method protected final java.util.concurrent.Executor getExecutor();
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
- method public boolean isRetryableError(Throwable);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
- method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method public boolean isRetryableError(Throwable error);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+ method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ property protected final java.util.concurrent.Executor executor;
+ property @WorkerThread public boolean isInvalid;
+ }
+
+ public static class DataSource.BaseResult<Value> {
+ ctor protected DataSource.BaseResult(java.util.List<? extends Value> data, Object? prevKey, Object? nextKey, int leadingNulls, int trailingNulls, int offset, boolean counted);
+ method public final boolean getCounted();
+ method public final int getLeadingNulls();
+ method public final Object? getNextKey();
+ method public final int getOffset();
+ method public final Object? getPrevKey();
+ method public final int getTrailingNulls();
+ field public final java.util.List<Value> data;
}
public abstract static class DataSource.Factory<Key, Value> {
ctor public DataSource.Factory();
- method public abstract androidx.paging.DataSource<Key!,Value!> create();
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public abstract androidx.paging.DataSource<Key,Value> create();
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public static interface DataSource.InvalidatedCallback {
@@ -24,113 +38,108 @@
public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.ListenableItemKeyedDataSource<Key,Value> {
ctor public ItemKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data);
}
public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadInitialCallback();
- method public abstract void onResult(java.util.List<Value!>, int, int);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
}
public static class ItemKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
}
public static class ItemKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenableItemKeyedDataSource();
- method public abstract Key? getKey(Value);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract Key getKey(Value item);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenableItemKeyedDataSource.InitialResult<V> {
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data);
}
public static class ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final Key? requestedInitialKey;
field public final int requestedLoadSize;
}
public static class ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenableItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenableItemKeyedDataSource.Result<V> {
- ctor public ListenableItemKeyedDataSource.Result(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.Result<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.Result(java.util.List<? extends V> data);
}
public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenablePageKeyedDataSource();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> {
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, int, int, Key?, Key?);
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, Key?, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public ListenablePageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
}
public static class ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenablePageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenablePageKeyedDataSource.Result<Key, Value> {
- ctor public ListenablePageKeyedDataSource.Result(java.util.List<Value!>, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.Result<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.Result(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
ctor public ListenablePositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int, int);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
+ method public static final int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
}
- public static class ListenablePositionalDataSource.InitialResult<V> {
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position);
}
public static class ListenablePositionalDataSource.LoadInitialParams {
- ctor public ListenablePositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public ListenablePositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
field public final int pageSize;
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
@@ -138,92 +147,101 @@
}
public static class ListenablePositionalDataSource.LoadRangeParams {
- ctor public ListenablePositionalDataSource.LoadRangeParams(int, int);
+ ctor public ListenablePositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
field public final int loadSize;
field public final int startPosition;
}
- public static class ListenablePositionalDataSource.RangeResult<V> {
- ctor public ListenablePositionalDataSource.RangeResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.RangeResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.RangeResult(java.util.List<? extends V> data);
}
public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.ListenablePageKeyedDataSource<Key,Value> {
ctor public PageKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key!,Value!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, int, int, Key?, Key?);
- method public abstract void onResult(java.util.List<Value!>, Key?, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class PageKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
}
public static class PageKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public PageKeyedDataSource.LoadParams(Key, int);
+ ctor public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class PagedList<T> extends java.util.AbstractList<T> {
- method public void addWeakCallback(java.util.List<T!>?, androidx.paging.PagedList.Callback);
- method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+ method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public abstract void detach();
- method public T? get(int);
+ method public T? get(int index);
method public androidx.paging.PagedList.Config getConfig();
- method public abstract androidx.paging.DataSource<?,T!> getDataSource();
+ method public abstract androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
method public int getPositionOffset();
+ method public int getSize();
+ method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
- method public void loadAround(int);
- method public void removeWeakCallback(androidx.paging.PagedList.Callback);
- method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void loadAround(int index);
+ method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+ method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public void retry();
- method public int size();
- method public java.util.List<T!> snapshot();
+ method public java.util.List<T> snapshot();
+ property public androidx.paging.PagedList.Config config;
+ property public abstract androidx.paging.DataSource<?,T> dataSource;
+ property public abstract boolean isContiguous;
+ property public abstract boolean isDetached;
+ property public boolean isImmutable;
+ property public abstract Object? lastKey;
+ property public int loadedCount;
+ property public int positionOffset;
+ property public int size;
}
@MainThread public abstract static class PagedList.BoundaryCallback<T> {
ctor public PagedList.BoundaryCallback();
- method public void onItemAtEndLoaded(T);
- method public void onItemAtFrontLoaded(T);
+ method public void onItemAtEndLoaded(T? itemAtEnd);
+ method public void onItemAtFrontLoaded(T? itemAtFront);
method public void onZeroItemsLoaded();
}
public static final class PagedList.Builder<Key, Value> {
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, androidx.paging.PagedList.Config);
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, int);
- method @Deprecated @WorkerThread public androidx.paging.PagedList<Value!> build();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value!>!> buildAsync();
- method public androidx.paging.PagedList.Builder<Key!,Value!> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value!>?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setFetchExecutor(java.util.concurrent.Executor);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setInitialKey(Key?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setNotifyExecutor(java.util.concurrent.Executor);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+ method @Deprecated @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value>> buildAsync();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+ method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+ method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+ method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
}
public abstract static class PagedList.Callback {
ctor public PagedList.Callback();
- method public abstract void onChanged(int, int);
- method public abstract void onInserted(int, int);
- method public abstract void onRemoved(int, int);
+ method public abstract void onChanged(int position, int count);
+ method public abstract void onInserted(int position, int count);
+ method public abstract void onRemoved(int position, int count);
}
public static class PagedList.Config {
@@ -238,11 +256,11 @@
public static final class PagedList.Config.Builder {
ctor public PagedList.Config.Builder();
method public androidx.paging.PagedList.Config build();
- method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean);
- method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int);
- method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int);
+ method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+ method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+ method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+ method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+ method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
public enum PagedList.LoadState {
@@ -254,7 +272,7 @@
}
public static interface PagedList.LoadStateListener {
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType, androidx.paging.PagedList.LoadState, Throwable?);
+ method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
}
public enum PagedList.LoadType {
@@ -263,37 +281,55 @@
enum_constant public static final androidx.paging.PagedList.LoadType START;
}
+ public final class PagedListConfigKt {
+ ctor public PagedListConfigKt();
+ method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
+ }
+
+ public final class PagedListKt {
+ ctor public PagedListKt();
+ method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
+ }
+
public abstract class PositionalDataSource<T> extends androidx.paging.ListenablePositionalDataSource<T> {
ctor public PositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
- method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> map(androidx.arch.core.util.Function<T!,V!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> mapByPage(androidx.arch.core.util.Function<java.util.List<T!>!,java.util.List<V!>!>);
+ method public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
+ method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+ method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+ method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
}
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>, int, int);
- method public abstract void onResult(java.util.List<T!>, int);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+ method public abstract void onResult(java.util.List<? extends T> data, int position);
}
public static class PositionalDataSource.LoadInitialParams extends androidx.paging.ListenablePositionalDataSource.LoadInitialParams {
- ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
}
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data);
}
public static class PositionalDataSource.LoadRangeParams extends androidx.paging.ListenablePositionalDataSource.LoadRangeParams {
- ctor public PositionalDataSource.LoadRangeParams(int, int);
+ ctor public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+ }
+
+}
+
+package androidx.paging.futures {
+
+ public final class Futures {
+ ctor public Futures();
}
}
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index cf51b44..8f87db7 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -2,20 +2,34 @@
package androidx.paging {
public abstract class DataSource<Key, Value> {
- method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method @AnyThread public void addInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ method protected final java.util.concurrent.Executor getExecutor();
method @AnyThread public void invalidate();
method @WorkerThread public boolean isInvalid();
- method public boolean isRetryableError(Throwable);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
- method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback);
+ method public boolean isRetryableError(Throwable error);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
+ method @AnyThread public void removeInvalidatedCallback(androidx.paging.DataSource.InvalidatedCallback onInvalidatedCallback);
+ property protected final java.util.concurrent.Executor executor;
+ property @WorkerThread public boolean isInvalid;
+ }
+
+ public static class DataSource.BaseResult<Value> {
+ ctor protected DataSource.BaseResult(java.util.List<? extends Value> data, Object? prevKey, Object? nextKey, int leadingNulls, int trailingNulls, int offset, boolean counted);
+ method public final boolean getCounted();
+ method public final int getLeadingNulls();
+ method public final Object? getNextKey();
+ method public final int getOffset();
+ method public final Object? getPrevKey();
+ method public final int getTrailingNulls();
+ field public final java.util.List<Value> data;
}
public abstract static class DataSource.Factory<Key, Value> {
ctor public DataSource.Factory();
- method public abstract androidx.paging.DataSource<Key!,Value!> create();
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public <ToValue> androidx.paging.DataSource.Factory<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public abstract androidx.paging.DataSource<Key,Value> create();
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public <ToValue> androidx.paging.DataSource.Factory<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public static interface DataSource.InvalidatedCallback {
@@ -24,113 +38,108 @@
public abstract class ItemKeyedDataSource<Key, Value> extends androidx.paging.ListenableItemKeyedDataSource<Key,Value> {
ctor public ItemKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadCallback<Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.ItemKeyedDataSource.LoadParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadCallback<Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.ItemKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.ItemKeyedDataSource.LoadInitialCallback<Value> callback);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.ItemKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data);
}
public abstract static class ItemKeyedDataSource.LoadInitialCallback<Value> extends androidx.paging.ItemKeyedDataSource.LoadCallback<Value> {
ctor public ItemKeyedDataSource.LoadInitialCallback();
- method public abstract void onResult(java.util.List<Value!>, int, int);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount);
}
public static class ItemKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
}
public static class ItemKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenableItemKeyedDataSource();
- method public abstract Key? getKey(Value);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value!>!> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value!>!> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract Key getKey(Value item);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadAfter(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.Result<Value>> loadBefore(androidx.paging.ListenableItemKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenableItemKeyedDataSource.InitialResult<Value>> loadInitial(androidx.paging.ListenableItemKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenableItemKeyedDataSource.InitialResult<V> {
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenableItemKeyedDataSource.InitialResult(java.util.List<? extends V> data);
}
public static class ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key?, int, boolean);
+ ctor public ListenableItemKeyedDataSource.LoadInitialParams(Key? requestedInitialKey, int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final Key? requestedInitialKey;
field public final int requestedLoadSize;
}
public static class ListenableItemKeyedDataSource.LoadParams<Key> {
- ctor public ListenableItemKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenableItemKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenableItemKeyedDataSource.Result<V> {
- ctor public ListenableItemKeyedDataSource.Result(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenableItemKeyedDataSource.Result<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenableItemKeyedDataSource.Result(java.util.List<? extends V> data);
}
public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
ctor public ListenablePageKeyedDataSource();
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
}
- public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> {
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, int, int, Key?, Key?);
- ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<Value!>, Key?, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.InitialResult<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ ctor public ListenablePageKeyedDataSource.InitialResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public ListenablePageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
}
public static class ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public ListenablePageKeyedDataSource.LoadParams(Key, int);
+ ctor public ListenablePageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
field public final Key key;
field public final int requestedLoadSize;
}
- public static class ListenablePageKeyedDataSource.Result<Key, Value> {
- ctor public ListenablePageKeyedDataSource.Result(java.util.List<Value!>, Key?);
- method public boolean equals(Object!);
+ public static class ListenablePageKeyedDataSource.Result<Key, Value> extends androidx.paging.DataSource.BaseResult<Value> {
+ ctor public ListenablePageKeyedDataSource.Result(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
ctor public ListenablePositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams, int, int);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
+ method public static final int computeInitialLoadPosition(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
}
- public static class ListenablePositionalDataSource.InitialResult<V> {
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int, int);
- ctor public ListenablePositionalDataSource.InitialResult(java.util.List<V!>, int);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.InitialResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position, int totalCount);
+ ctor public ListenablePositionalDataSource.InitialResult(java.util.List<? extends V> data, int position);
}
public static class ListenablePositionalDataSource.LoadInitialParams {
- ctor public ListenablePositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public ListenablePositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
field public final int pageSize;
field public final boolean placeholdersEnabled;
field public final int requestedLoadSize;
@@ -138,92 +147,101 @@
}
public static class ListenablePositionalDataSource.LoadRangeParams {
- ctor public ListenablePositionalDataSource.LoadRangeParams(int, int);
+ ctor public ListenablePositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
field public final int loadSize;
field public final int startPosition;
}
- public static class ListenablePositionalDataSource.RangeResult<V> {
- ctor public ListenablePositionalDataSource.RangeResult(java.util.List<V!>);
- method public boolean equals(Object!);
+ public static class ListenablePositionalDataSource.RangeResult<V> extends androidx.paging.DataSource.BaseResult<V> {
+ ctor public ListenablePositionalDataSource.RangeResult(java.util.List<? extends V> data);
}
public abstract class PageKeyedDataSource<Key, Value> extends androidx.paging.ListenablePageKeyedDataSource<Key,Value> {
ctor public PageKeyedDataSource();
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key!,Value!>!> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key!>);
- method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key!>, androidx.paging.PageKeyedDataSource.LoadCallback<Key!,Value!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key!,Value!>!> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key!>);
- method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key!>, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key!,Value!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> map(androidx.arch.core.util.Function<Value!,ToValue!>);
- method public final <ToValue> androidx.paging.PageKeyedDataSource<Key!,ToValue!> mapByPage(androidx.arch.core.util.Function<java.util.List<Value!>!,java.util.List<ToValue!>!>);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadAfter(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadAfter(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.Result<Key,Value>> loadBefore(androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> params);
+ method public abstract void loadBefore(androidx.paging.PageKeyedDataSource.LoadParams<Key> params, androidx.paging.PageKeyedDataSource.LoadCallback<Key,Value> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePageKeyedDataSource.InitialResult<Key,Value>> loadInitial(androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> params);
+ method public abstract void loadInitial(androidx.paging.PageKeyedDataSource.LoadInitialParams<Key> params, androidx.paging.PageKeyedDataSource.LoadInitialCallback<Key,Value> callback);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> map(androidx.arch.core.util.Function<Value,ToValue> function);
+ method public final <ToValue> androidx.paging.PageKeyedDataSource<Key,ToValue> mapByPage(androidx.arch.core.util.Function<java.util.List<Value>,java.util.List<ToValue>> function);
}
public abstract static class PageKeyedDataSource.LoadCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? adjacentPageKey);
}
public abstract static class PageKeyedDataSource.LoadInitialCallback<Key, Value> {
ctor public PageKeyedDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<Value!>, int, int, Key?, Key?);
- method public abstract void onResult(java.util.List<Value!>, Key?, Key?);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends Value> data, int position, int totalCount, Key? previousPageKey, Key? nextPageKey);
+ method public abstract void onResult(java.util.List<? extends Value> data, Key? previousPageKey, Key? nextPageKey);
}
public static class PageKeyedDataSource.LoadInitialParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- ctor public PageKeyedDataSource.LoadInitialParams(int, boolean);
+ ctor public PageKeyedDataSource.LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled);
}
public static class PageKeyedDataSource.LoadParams<Key> extends androidx.paging.ListenablePageKeyedDataSource.LoadParams<Key> {
- ctor public PageKeyedDataSource.LoadParams(Key, int);
+ ctor public PageKeyedDataSource.LoadParams(Key key, int requestedLoadSize);
}
public abstract class PagedList<T> extends java.util.AbstractList<T> {
- method public void addWeakCallback(java.util.List<T!>?, androidx.paging.PagedList.Callback);
- method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void addWeakCallback(java.util.List<? extends T>? previousSnapshot, androidx.paging.PagedList.Callback callback);
+ method public void addWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public abstract void detach();
- method public T? get(int);
+ method public T? get(int index);
method public androidx.paging.PagedList.Config getConfig();
- method public abstract androidx.paging.DataSource<?,T!> getDataSource();
+ method public abstract androidx.paging.DataSource<?,T> getDataSource();
method public abstract Object? getLastKey();
method public int getLoadedCount();
method public int getPositionOffset();
+ method public int getSize();
+ method public abstract boolean isContiguous();
method public abstract boolean isDetached();
method public boolean isImmutable();
- method public void loadAround(int);
- method public void removeWeakCallback(androidx.paging.PagedList.Callback);
- method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener);
+ method public void loadAround(int index);
+ method public void removeWeakCallback(androidx.paging.PagedList.Callback callback);
+ method public void removeWeakLoadStateListener(androidx.paging.PagedList.LoadStateListener listener);
method public void retry();
- method public int size();
- method public java.util.List<T!> snapshot();
+ method public java.util.List<T> snapshot();
+ property public androidx.paging.PagedList.Config config;
+ property public abstract androidx.paging.DataSource<?,T> dataSource;
+ property public abstract boolean isContiguous;
+ property public abstract boolean isDetached;
+ property public boolean isImmutable;
+ property public abstract Object? lastKey;
+ property public int loadedCount;
+ property public int positionOffset;
+ property public int size;
}
@MainThread public abstract static class PagedList.BoundaryCallback<T> {
ctor public PagedList.BoundaryCallback();
- method public void onItemAtEndLoaded(T);
- method public void onItemAtFrontLoaded(T);
+ method public void onItemAtEndLoaded(T? itemAtEnd);
+ method public void onItemAtFrontLoaded(T? itemAtFront);
method public void onZeroItemsLoaded();
}
public static final class PagedList.Builder<Key, Value> {
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, androidx.paging.PagedList.Config);
- ctor public PagedList.Builder(androidx.paging.DataSource<Key!,Value!>, int);
- method @Deprecated @WorkerThread public androidx.paging.PagedList<Value!> build();
- method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value!>!> buildAsync();
- method public androidx.paging.PagedList.Builder<Key!,Value!> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value!>?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setFetchExecutor(java.util.concurrent.Executor);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setInitialKey(Key?);
- method public androidx.paging.PagedList.Builder<Key!,Value!> setNotifyExecutor(java.util.concurrent.Executor);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config);
+ ctor public PagedList.Builder(androidx.paging.DataSource<Key,Value> dataSource, int pageSize);
+ method @Deprecated @WorkerThread public androidx.paging.PagedList<Value> build();
+ method public com.google.common.util.concurrent.ListenableFuture<androidx.paging.PagedList<Value>> buildAsync();
+ method public androidx.paging.PagedList.Builder<Key,Value> setBoundaryCallback(androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback);
+ method public androidx.paging.PagedList.Builder<Key,Value> setFetchExecutor(java.util.concurrent.Executor fetchExecutor);
+ method public androidx.paging.PagedList.Builder<Key,Value> setInitialKey(Key? initialKey);
+ method public androidx.paging.PagedList.Builder<Key,Value> setNotifyExecutor(java.util.concurrent.Executor notifyExecutor);
}
public abstract static class PagedList.Callback {
ctor public PagedList.Callback();
- method public abstract void onChanged(int, int);
- method public abstract void onInserted(int, int);
- method public abstract void onRemoved(int, int);
+ method public abstract void onChanged(int position, int count);
+ method public abstract void onInserted(int position, int count);
+ method public abstract void onRemoved(int position, int count);
}
public static class PagedList.Config {
@@ -238,11 +256,11 @@
public static final class PagedList.Config.Builder {
ctor public PagedList.Config.Builder();
method public androidx.paging.PagedList.Config build();
- method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean);
- method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int);
- method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int);
- method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int);
+ method public androidx.paging.PagedList.Config.Builder setEnablePlaceholders(boolean enablePlaceholders);
+ method public androidx.paging.PagedList.Config.Builder setInitialLoadSizeHint(@IntRange(from=1) int initialLoadSizeHint);
+ method public androidx.paging.PagedList.Config.Builder setMaxSize(@IntRange(from=2) int maxSize);
+ method public androidx.paging.PagedList.Config.Builder setPageSize(@IntRange(from=1) int pageSize);
+ method public androidx.paging.PagedList.Config.Builder setPrefetchDistance(@IntRange(from=0) int prefetchDistance);
}
public enum PagedList.LoadState {
@@ -254,7 +272,7 @@
}
public static interface PagedList.LoadStateListener {
- method public void onLoadStateChanged(androidx.paging.PagedList.LoadType, androidx.paging.PagedList.LoadState, Throwable?);
+ method public void onLoadStateChanged(androidx.paging.PagedList.LoadType type, androidx.paging.PagedList.LoadState state, Throwable? error);
}
public enum PagedList.LoadType {
@@ -263,37 +281,55 @@
enum_constant public static final androidx.paging.PagedList.LoadType START;
}
+ public final class PagedListConfigKt {
+ ctor public PagedListConfigKt();
+ method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
+ }
+
+ public final class PagedListKt {
+ ctor public PagedListKt();
+ method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
+ }
+
public abstract class PositionalDataSource<T> extends androidx.paging.ListenablePositionalDataSource<T> {
ctor public PositionalDataSource();
- method public static int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams, int);
- method public static int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams, int, int);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T!>!> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams);
- method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T!>!> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams);
- method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> map(androidx.arch.core.util.Function<T!,V!>);
- method public final <V> androidx.paging.PositionalDataSource<V!> mapByPage(androidx.arch.core.util.Function<java.util.List<T!>!,java.util.List<V!>!>);
+ method public static final int computeInitialLoadPosition(androidx.paging.PositionalDataSource.LoadInitialParams params, int totalCount);
+ method public static final int computeInitialLoadSize(androidx.paging.PositionalDataSource.LoadInitialParams params, int initialLoadPosition, int totalCount);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.InitialResult<T>> loadInitial(androidx.paging.ListenablePositionalDataSource.LoadInitialParams params);
+ method @WorkerThread public abstract void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams params, androidx.paging.PositionalDataSource.LoadInitialCallback<T> callback);
+ method public final com.google.common.util.concurrent.ListenableFuture<androidx.paging.ListenablePositionalDataSource.RangeResult<T>> loadRange(androidx.paging.ListenablePositionalDataSource.LoadRangeParams params);
+ method @WorkerThread public abstract void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams params, androidx.paging.PositionalDataSource.LoadRangeCallback<T> callback);
+ method public final <V> androidx.paging.PositionalDataSource<V> map(androidx.arch.core.util.Function<T,V> function);
+ method public final <V> androidx.paging.PositionalDataSource<V> mapByPage(androidx.arch.core.util.Function<java.util.List<T>,java.util.List<V>> function);
}
public abstract static class PositionalDataSource.LoadInitialCallback<T> {
ctor public PositionalDataSource.LoadInitialCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>, int, int);
- method public abstract void onResult(java.util.List<T!>, int);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data, int position, int totalCount);
+ method public abstract void onResult(java.util.List<? extends T> data, int position);
}
public static class PositionalDataSource.LoadInitialParams extends androidx.paging.ListenablePositionalDataSource.LoadInitialParams {
- ctor public PositionalDataSource.LoadInitialParams(int, int, int, boolean);
+ ctor public PositionalDataSource.LoadInitialParams(int requestedStartPosition, int requestedLoadSize, int pageSize, boolean placeholdersEnabled);
}
public abstract static class PositionalDataSource.LoadRangeCallback<T> {
ctor public PositionalDataSource.LoadRangeCallback();
- method public void onError(Throwable);
- method public abstract void onResult(java.util.List<T!>);
+ method public void onError(Throwable error);
+ method public abstract void onResult(java.util.List<? extends T> data);
}
public static class PositionalDataSource.LoadRangeParams extends androidx.paging.ListenablePositionalDataSource.LoadRangeParams {
- ctor public PositionalDataSource.LoadRangeParams(int, int);
+ ctor public PositionalDataSource.LoadRangeParams(int startPosition, int loadSize);
+ }
+
+}
+
+package androidx.paging.futures {
+
+ public final class Futures {
+ ctor public Futures();
}
}
diff --git a/paging/common/api/restricted_2.2.0-alpha01.ignore b/paging/common/api/restricted_2.2.0-alpha01.ignore
new file mode 100644
index 0000000..e7785e82
--- /dev/null
+++ b/paging/common/api/restricted_2.2.0-alpha01.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedDeprecatedClass: androidx.paging.TiledDataSource:
+ Removed deprecated class androidx.paging.TiledDataSource
diff --git a/paging/common/api/restricted_2.2.0-alpha01.txt b/paging/common/api/restricted_2.2.0-alpha01.txt
index c7a8fc2..b320f69 100644
--- a/paging/common/api/restricted_2.2.0-alpha01.txt
+++ b/paging/common/api/restricted_2.2.0-alpha01.txt
@@ -1,31 +1,45 @@
// Signature format: 3.0
package androidx.paging {
- @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TiledDataSource<T> extends androidx.paging.PositionalDataSource<T> {
- ctor @Deprecated public TiledDataSource();
- method @Deprecated @WorkerThread public abstract int countItems();
- method @Deprecated public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method @Deprecated @WorkerThread public abstract java.util.List<T!>? loadRange(int, int);
- method @Deprecated public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+
+ public abstract class DataSource<Key, Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void initExecutor(java.util.concurrent.Executor executor);
}
+
+
+
+ public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Integer! getKeyInternal$lintWithKotlin(T item);
+ }
+
+ public abstract class PagedList<T> extends java.util.AbstractList<T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected final androidx.paging.PagedStorage<T> getStorage();
+ }
+
+
+
+
}
package androidx.paging.futures {
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class DirectExecutor implements java.util.concurrent.Executor {
- method public void execute(Runnable);
- field public static androidx.paging.futures.DirectExecutor INSTANCE;
- }
-
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface FutureCallback<V> {
- method public void onError(Throwable);
- method public void onSuccess(V!);
+ method public void onError(Throwable throwable);
+ method public void onSuccess(V? value);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class Futures {
- method public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<V!>, androidx.paging.futures.FutureCallback<? super V>, java.util.concurrent.Executor);
- method public static <I, O> com.google.common.util.concurrent.ListenableFuture<O!> transform(com.google.common.util.concurrent.ListenableFuture<I!>, androidx.arch.core.util.Function<? super I,? extends O>, java.util.concurrent.Executor);
+ public final class Futures {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<? extends V>, androidx.paging.futures.FutureCallback<? super V> callback, java.util.concurrent.Executor executor);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <I, O> com.google.common.util.concurrent.ListenableFuture<O> transform(com.google.common.util.concurrent.ListenableFuture<? extends I>, androidx.arch.core.util.Function<? super I,? extends O> function, java.util.concurrent.Executor executor);
}
}
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index c7a8fc2..b320f69 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -1,31 +1,45 @@
// Signature format: 3.0
package androidx.paging {
- @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public abstract class TiledDataSource<T> extends androidx.paging.PositionalDataSource<T> {
- ctor @Deprecated public TiledDataSource();
- method @Deprecated @WorkerThread public abstract int countItems();
- method @Deprecated public void loadInitial(androidx.paging.PositionalDataSource.LoadInitialParams, androidx.paging.PositionalDataSource.LoadInitialCallback<T!>);
- method @Deprecated @WorkerThread public abstract java.util.List<T!>? loadRange(int, int);
- method @Deprecated public void loadRange(androidx.paging.PositionalDataSource.LoadRangeParams, androidx.paging.PositionalDataSource.LoadRangeCallback<T!>);
+
+ public abstract class DataSource<Key, Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final void initExecutor(java.util.concurrent.Executor executor);
}
+
+
+
+ public abstract class ListenableItemKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePageKeyedDataSource<Key, Value> extends androidx.paging.DataSource<Key,Value> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public Key getKeyInternal$lintWithKotlin(Value item);
+ }
+
+ public abstract class ListenablePositionalDataSource<T> extends androidx.paging.DataSource<java.lang.Integer,T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final Integer! getKeyInternal$lintWithKotlin(T item);
+ }
+
+ public abstract class PagedList<T> extends java.util.AbstractList<T> {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) protected final androidx.paging.PagedStorage<T> getStorage();
+ }
+
+
+
+
}
package androidx.paging.futures {
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class DirectExecutor implements java.util.concurrent.Executor {
- method public void execute(Runnable);
- field public static androidx.paging.futures.DirectExecutor INSTANCE;
- }
-
@RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public interface FutureCallback<V> {
- method public void onError(Throwable);
- method public void onSuccess(V!);
+ method public void onError(Throwable throwable);
+ method public void onSuccess(V? value);
}
- @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public class Futures {
- method public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<V!>, androidx.paging.futures.FutureCallback<? super V>, java.util.concurrent.Executor);
- method public static <I, O> com.google.common.util.concurrent.ListenableFuture<O!> transform(com.google.common.util.concurrent.ListenableFuture<I!>, androidx.arch.core.util.Function<? super I,? extends O>, java.util.concurrent.Executor);
+ public final class Futures {
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <V> void addCallback(com.google.common.util.concurrent.ListenableFuture<? extends V>, androidx.paging.futures.FutureCallback<? super V> callback, java.util.concurrent.Executor executor);
+ method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static <I, O> com.google.common.util.concurrent.ListenableFuture<O> transform(com.google.common.util.concurrent.ListenableFuture<? extends I>, androidx.arch.core.util.Function<? super I,? extends O> function, java.util.concurrent.Executor executor);
}
}
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index c643f56..2156399 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -34,10 +34,13 @@
compile("androidx.annotation:annotation:1.1.0")
compile(ARCH_CORE_COMMON)
compile("androidx.concurrent:concurrent-futures:1.0.0-alpha02")
+ implementation(KOTLIN_STDLIB)
testCompile(JUNIT)
testCompile(MOCKITO_CORE)
- testCompile(KOTLIN_STDLIB)
+ testImplementation MOCKITO_KOTLIN, {
+ exclude group: 'org.mockito' // to keep control on the mockito version
+ }
testCompile(GUAVA)
testImplementation project(':internal-testutils-common')
}
diff --git a/paging/common/ktx/api/2.2.0-alpha01.ignore b/paging/common/ktx/api/2.2.0-alpha01.ignore
new file mode 100644
index 0000000..1cc6306
--- /dev/null
+++ b/paging/common/ktx/api/2.2.0-alpha01.ignore
@@ -0,0 +1,2 @@
+RemovedPackage: androidx.paging:
+ Removed package androidx.paging
\ No newline at end of file
diff --git a/paging/common/ktx/api/2.2.0-alpha01.txt b/paging/common/ktx/api/2.2.0-alpha01.txt
index 6d9cc829..da4f6cc 100644
--- a/paging/common/ktx/api/2.2.0-alpha01.txt
+++ b/paging/common/ktx/api/2.2.0-alpha01.txt
@@ -1,15 +1 @@
// Signature format: 3.0
-package androidx.paging {
-
- public final class PagedListConfigKt {
- ctor public PagedListConfigKt();
- method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
- }
-
- public final class PagedListKt {
- ctor public PagedListKt();
- method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
- }
-
-}
-
diff --git a/paging/common/ktx/api/current.txt b/paging/common/ktx/api/current.txt
index 6d9cc829..da4f6cc 100644
--- a/paging/common/ktx/api/current.txt
+++ b/paging/common/ktx/api/current.txt
@@ -1,15 +1 @@
// Signature format: 3.0
-package androidx.paging {
-
- public final class PagedListConfigKt {
- ctor public PagedListConfigKt();
- method public static androidx.paging.PagedList.Config Config(int pageSize, int prefetchDistance = pageSize, boolean enablePlaceholders = true, int initialLoadSizeHint = pageSize * androidx.paging.PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER, int maxSize = 2147483647);
- }
-
- public final class PagedListKt {
- ctor public PagedListKt();
- method public static <Key, Value> androidx.paging.PagedList<Value> PagedList(androidx.paging.DataSource<Key,Value> dataSource, androidx.paging.PagedList.Config config, java.util.concurrent.Executor notifyExecutor, java.util.concurrent.Executor fetchExecutor, androidx.paging.PagedList.BoundaryCallback<Value>? boundaryCallback = null, Key? initialKey = null);
- }
-
-}
-
diff --git a/paging/common/ktx/build.gradle b/paging/common/ktx/build.gradle
index 6bdb7d5..7d79e72 100644
--- a/paging/common/ktx/build.gradle
+++ b/paging/common/ktx/build.gradle
@@ -26,10 +26,7 @@
}
dependencies {
- compile(project(":paging:paging-common"))
- compile(KOTLIN_STDLIB)
-
- testImplementation(JUNIT)
+ api(project(":paging:paging-common"))
}
androidx {
diff --git a/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt b/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
deleted file mode 100644
index 383a10f..0000000
--- a/paging/common/ktx/src/main/java/androidx/paging/PagedList.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging
-
-import java.util.concurrent.Executor
-
-/**
- * Constructs a [PagedList], convenience for [PagedList.Builder].
- *
- * @param [Key] Type of key used to load data from the DataSource.
- * @param [Value] Type of items held and loaded by the PagedList.
- *
- * @param dataSource DataSource the PagedList will load from.
- * @param config Config that defines how the PagedList loads data from its DataSource.
- * @param notifyExecutor Executor that receives PagedList updates, and where
- * [PagedList.Callback] calls are dispatched. Generally, this is the UI/main thread.
- * @param fetchExecutor Executor used to fetch from DataSources, generally a background thread pool
- * for e.g. I/O or network loading.
- * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
- * @param initialKey Key the DataSource should load around as part of initialization.
- */
-@Suppress("FunctionName")
-fun <Key, Value> PagedList(
- dataSource: DataSource<Key, Value>,
- config: PagedList.Config,
- notifyExecutor: Executor,
- fetchExecutor: Executor,
- boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
- initialKey: Key? = null
-): PagedList<Value> {
- @Suppress("DEPRECATION")
- return PagedList.Builder(dataSource, config)
- .setNotifyExecutor(notifyExecutor)
- .setFetchExecutor(fetchExecutor)
- .setBoundaryCallback(boundaryCallback)
- .setInitialKey(initialKey)
- .build()
-}
diff --git a/paging/common/ktx/src/test/java/PagedListTest.kt b/paging/common/ktx/src/test/java/PagedListTest.kt
deleted file mode 100644
index ac5e511..0000000
--- a/paging/common/ktx/src/test/java/PagedListTest.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging
-
-import androidx.paging.futures.DirectExecutor
-import org.junit.Assert.assertEquals
-import org.junit.Assert.fail
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@RunWith(JUnit4::class)
-class PagedListTest {
- @Test
- fun defaults() {
- val pagedList = PagedList(
- dataSource = dataSource,
- config = config,
- fetchExecutor = DirectExecutor.INSTANCE,
- notifyExecutor = DirectExecutor.INSTANCE
- )
-
- assertEquals(dataSource, pagedList.dataSource)
- assertEquals(config, pagedList.config)
- }
-
- companion object {
- private val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(
- params: LoadInitialParams,
- callback: LoadInitialCallback<String>
- ) {
- callback.onResult(listOf("a"), 0, 1)
- }
-
- override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
- fail()
- }
- }
-
- private val config = Config(10)
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java b/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
deleted file mode 100644
index d5ad40d..0000000
--- a/paging/common/src/main/java/androidx/paging/ContiguousPagedList.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-class ContiguousPagedList<K, V> extends PagedList<V> implements PagedStorage.Callback,
- Pager.PageConsumer<V> {
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final DataSource<K, V> mDataSource;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- int mPrependItemsRequested = 0;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- int mAppendItemsRequested = 0;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- boolean mReplacePagesWithNulls = false;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final boolean mShouldTrim;
-
- /**
- * Given a page result, apply or drop it, and return whether more loading is needed.
- */
- @Override
- public boolean onPageResult(@NonNull LoadType type,
- @NonNull DataSource.BaseResult<V> pageResult) {
- boolean continueLoading = false;
- @NonNull List<V> page = pageResult.data;
-
-
- // if we end up trimming, we trim from side that's furthest from most recent access
- boolean trimFromFront = mLastLoad > mStorage.getMiddleOfLoadedRange();
-
- // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
- boolean skipNewPage = mShouldTrim
- && mStorage.shouldPreTrimNewPage(
- mConfig.maxSize, mRequiredRemainder, page.size());
-
- if (type == LoadType.END) {
- if (skipNewPage && !trimFromFront) {
- // don't append this data, drop it
- mAppendItemsRequested = 0;
- } else {
- mStorage.appendPage(page, ContiguousPagedList.this);
- mAppendItemsRequested -= page.size();
- if (mAppendItemsRequested > 0 && page.size() != 0) {
- continueLoading = true;
- }
- }
- } else if (type == LoadType.START) {
- if (skipNewPage && trimFromFront) {
- // don't append this data, drop it
- mPrependItemsRequested = 0;
- } else {
- mStorage.prependPage(page, ContiguousPagedList.this);
- mPrependItemsRequested -= page.size();
- if (mPrependItemsRequested > 0 && page.size() != 0) {
- continueLoading = true;
- }
- }
- } else {
- throw new IllegalArgumentException("unexpected result type " + type);
- }
-
- if (mShouldTrim) {
- // Try and trim, but only if the side being trimmed isn't actually fetching.
- // For simplicity (both of impl here, and contract w/ DataSource) we don't
- // allow fetches in same direction - this means reading the load state is safe.
- if (trimFromFront) {
- if (mPager.mLoadStateManager.getStart() != LoadState.LOADING) {
- if (mStorage.trimFromFront(
- mReplacePagesWithNulls,
- mConfig.maxSize,
- mRequiredRemainder,
- ContiguousPagedList.this)) {
- // trimmed from front, ensure we can fetch in that dir
- mPager.mLoadStateManager.setState(LoadType.START, LoadState.IDLE, null);
- }
- }
- } else {
- if (mPager.mLoadStateManager.getEnd() != LoadState.LOADING) {
- if (mStorage.trimFromEnd(
- mReplacePagesWithNulls,
- mConfig.maxSize,
- mRequiredRemainder,
- ContiguousPagedList.this)) {
- mPager.mLoadStateManager.setState(LoadType.END, LoadState.IDLE, null);
- }
- }
- }
- }
-
- triggerBoundaryCallback(type, page);
- return continueLoading;
- }
-
- @Override
- public void onStateChanged(@NonNull LoadType type, @NonNull LoadState state,
- @Nullable Throwable error) {
- dispatchStateChange(type, state, error);
- }
-
- private void triggerBoundaryCallback(@NonNull LoadType type, @NonNull List<V> page) {
- if (mBoundaryCallback != null) {
- boolean deferEmpty = mStorage.size() == 0;
- boolean deferBegin = !deferEmpty
- && type == LoadType.START
- && page.size() == 0;
- boolean deferEnd = !deferEmpty
- && type == LoadType.END
- && page.size() == 0;
- deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd);
- }
- }
-
- @NonNull
- private final Pager mPager;
-
- @Override
- public void retry() {
- super.retry();
- mPager.retry();
-
- if (mRefreshRetryCallback != null
- && mPager.mLoadStateManager.getRefresh() == LoadState.RETRYABLE_ERROR) {
- // Loading the next PagedList failed, signal the retry callback.
- mRefreshRetryCallback.run();
- }
- }
-
- static final int LAST_LOAD_UNSPECIFIED = -1;
-
- ContiguousPagedList(
- @NonNull DataSource<K, V> dataSource,
- @NonNull Executor mainThreadExecutor,
- @NonNull Executor backgroundThreadExecutor,
- @Nullable BoundaryCallback<V> boundaryCallback,
- @NonNull Config config,
- @NonNull DataSource.BaseResult<V> initialResult,
- int lastLoad) {
- super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
- boundaryCallback, config);
- mDataSource = dataSource;
- mLastLoad = lastLoad;
- mPager = new Pager<>(config, dataSource, mainThreadExecutor, backgroundThreadExecutor,
- this, mStorage, initialResult);
-
- if (config.enablePlaceholders) {
- // Placeholders enabled, pass raw data to storage init
- mStorage.init(initialResult.leadingNulls, initialResult.data,
- initialResult.trailingNulls, initialResult.offset, this);
- } else {
- // If placeholder are disabled, avoid passing leading/trailing nulls,
- // since DataSource may have passed them anyway
- mStorage.init(0, initialResult.data,
- 0, initialResult.offset + initialResult.leadingNulls, this);
- }
-
- mShouldTrim = mDataSource.supportsPageDropping()
- && mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
-
- if (mLastLoad == LAST_LOAD_UNSPECIFIED) {
- // Because the ContiguousPagedList wasn't initialized with a last load position,
- // initialize it to the middle of the initial load
- mLastLoad = initialResult.leadingNulls + initialResult.offset
- + initialResult.data.size() / 2;
- }
- triggerBoundaryCallback(LoadType.REFRESH, initialResult.data);
- }
-
- @Override
- void dispatchCurrentLoadState(LoadStateListener listener) {
- mPager.mLoadStateManager.dispatchCurrentLoadState(listener);
- }
-
- @Override
- void setInitialLoadState(@NonNull LoadState loadState, @Nullable Throwable error) {
- mPager.mLoadStateManager.setState(LoadType.REFRESH, loadState, error);
- }
-
- @MainThread
- @Override
- void dispatchUpdatesSinceSnapshot(
- @NonNull PagedList<V> pagedListSnapshot, @NonNull Callback callback) {
- final PagedStorage<V> snapshot = pagedListSnapshot.mStorage;
-
- final int newlyAppended = mStorage.getNumberAppended() - snapshot.getNumberAppended();
- final int newlyPrepended = mStorage.getNumberPrepended() - snapshot.getNumberPrepended();
-
- final int previousTrailing = snapshot.getTrailingNullCount();
- final int previousLeading = snapshot.getLeadingNullCount();
-
- // Validate that the snapshot looks like a previous version of this list - if it's not,
- // we can't be sure we'll dispatch callbacks safely
- if (snapshot.isEmpty()
- || newlyAppended < 0
- || newlyPrepended < 0
- || mStorage.getTrailingNullCount() != Math.max(previousTrailing - newlyAppended, 0)
- || mStorage.getLeadingNullCount() != Math.max(previousLeading - newlyPrepended, 0)
- || (mStorage.getStorageCount()
- != snapshot.getStorageCount() + newlyAppended + newlyPrepended)) {
- throw new IllegalArgumentException("Invalid snapshot provided - doesn't appear"
- + " to be a snapshot of this PagedList");
- }
-
- if (newlyAppended != 0) {
- final int changedCount = Math.min(previousTrailing, newlyAppended);
- final int addedCount = newlyAppended - changedCount;
-
- final int endPosition = snapshot.getLeadingNullCount() + snapshot.getStorageCount();
- if (changedCount != 0) {
- callback.onChanged(endPosition, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(endPosition + changedCount, addedCount);
- }
- }
- if (newlyPrepended != 0) {
- final int changedCount = Math.min(previousLeading, newlyPrepended);
- final int addedCount = newlyPrepended - changedCount;
-
- if (changedCount != 0) {
- callback.onChanged(previousLeading, changedCount);
- }
- if (addedCount != 0) {
- callback.onInserted(0, addedCount);
- }
- }
- }
-
- static int getPrependItemsRequested(int prefetchDistance, int index, int leadingNulls) {
- return prefetchDistance - (index - leadingNulls);
- }
-
- static int getAppendItemsRequested(
- int prefetchDistance, int index, int itemsBeforeTrailingNulls) {
- return index + prefetchDistance + 1 - itemsBeforeTrailingNulls;
- }
-
- @MainThread
- @Override
- protected void loadAroundInternal(int index) {
- int prependItems = getPrependItemsRequested(mConfig.prefetchDistance, index,
- mStorage.getLeadingNullCount());
- int appendItems = getAppendItemsRequested(mConfig.prefetchDistance, index,
- mStorage.getLeadingNullCount() + mStorage.getStorageCount());
-
- mPrependItemsRequested = Math.max(prependItems, mPrependItemsRequested);
- if (mPrependItemsRequested > 0) {
- mPager.trySchedulePrepend();
- }
-
- mAppendItemsRequested = Math.max(appendItems, mAppendItemsRequested);
- if (mAppendItemsRequested > 0) {
- mPager.tryScheduleAppend();
- }
- }
-
- @Override
- public boolean isDetached() {
- return mPager.isDetached();
- }
-
- @Override
- public void detach() {
- mPager.detach();
- }
-
- @Override
- boolean isContiguous() {
- return true;
- }
-
- @NonNull
- @Override
- public DataSource<?, V> getDataSource() {
- return mDataSource;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return mDataSource.getKey(mLastLoad, mLastItem);
- }
-
- @MainThread
- @Override
- public void onInitialized(int count) {
- notifyInserted(0, count);
- // simple heuristic to decide if, when dropping pages, we should replace with placeholders
- mReplacePagesWithNulls =
- mStorage.getLeadingNullCount() > 0 || mStorage.getTrailingNullCount() > 0;
- }
-
- @MainThread
- @Override
- public void onPagePrepended(int leadingNulls, int changedCount, int addedCount) {
-
- // finally dispatch callbacks, after prepend may have already been scheduled
- notifyChanged(leadingNulls, changedCount);
- notifyInserted(0, addedCount);
-
- offsetAccessIndices(addedCount);
- }
-
- @MainThread
- @Override
- public void onPageAppended(int endPosition, int changedCount, int addedCount) {
- // finally dispatch callbacks, after append may have already been scheduled
- notifyChanged(endPosition, changedCount);
- notifyInserted(endPosition + changedCount, addedCount);
- }
-
-
- @MainThread
- @Override
- public void onPagePlaceholderInserted(int pageIndex) {
- throw new IllegalStateException("Tiled callback on ContiguousPagedList");
- }
-
- @MainThread
- @Override
- public void onPageInserted(int start, int count) {
- throw new IllegalStateException("Tiled callback on ContiguousPagedList");
- }
-
- @Override
- public void onPagesRemoved(int startOfDrops, int count) {
- notifyRemoved(startOfDrops, count);
- }
-
- @Override
- public void onPagesSwappedToPlaceholder(int startOfDrops, int count) {
- notifyChanged(startOfDrops, count);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/DataSource.java b/paging/common/src/main/java/androidx/paging/DataSource.java
deleted file mode 100644
index 2313653..0000000
--- a/paging/common/src/main/java/androidx/paging/DataSource.java
+++ /dev/null
@@ -1,545 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-import androidx.arch.core.util.Function;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Base class for loading pages of snapshot data into a {@link PagedList}.
- * <p>
- * DataSource is queried to load pages of content into a {@link PagedList}. A PagedList can grow as
- * it loads more data, but the data loaded cannot be updated. If the underlying data set is
- * modified, a new PagedList / DataSource pair must be created to represent the new data.
- * <h4>Loading Pages</h4>
- * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter
- * calls {@link PagedList#loadAround(int)} to load content as the user scrolls in a RecyclerView.
- * <p>
- * To control how and when a PagedList queries data from its DataSource, see
- * {@link PagedList.Config}. The Config object defines things like load sizes and prefetch distance.
- * <h4>Updating Paged Data</h4>
- * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
- * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
- * content update occurs. A DataSource must detect that it cannot continue loading its
- * snapshot (for instance, when Database query notices a table being invalidated), and call
- * {@link #invalidate()}. Then a new PagedList / DataSource pair would be created to load data from
- * the new state of the Database query.
- * <p>
- * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
- * PagedList. For example, loading from network when the network's paging API doesn't provide
- * updates.
- * <p>
- * To page in data from a source that does provide updates, you can create a
- * {@link DataSource.Factory}, where each DataSource created is invalidated when an update to the
- * data set occurs that makes the current snapshot invalid. For example, when paging a query from
- * the Database, and the table being queried inserts or removes items. You can also use a
- * DataSource.Factory to provide multiple versions of network-paged lists. If reloading all content
- * (e.g. in response to an action like swipe-to-refresh) is required to get a new version of data,
- * you can connect an explicit refresh signal to call {@link #invalidate()} on the current
- * DataSource.
- * <p>
- * If you have more granular update signals, such as a network API signaling an update to a single
- * item in the list, it's recommended to load data from network into memory. Then present that
- * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
- * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
- * snapshot can be created.
- * <h4>Implementing a DataSource</h4>
- * To implement, extend one of the subclasses: {@link PageKeyedDataSource},
- * {@link ItemKeyedDataSource}, or {@link PositionalDataSource}.
- * <p>
- * Use {@link PageKeyedDataSource} if pages you load embed keys for loading adjacent pages. For
- * example a network response that returns some items, and a next/previous page links.
- * <p>
- * Use {@link ItemKeyedDataSource} if you need to use data from item {@code N-1} to load item
- * {@code N}. For example, if requesting the backend for the next comments in the list
- * requires the ID or timestamp of the most recent loaded comment, or if querying the next users
- * from a name-sorted database query requires the name and unique ID of the previous.
- * <p>
- * Use {@link PositionalDataSource} if you can load pages of a requested size at arbitrary
- * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
- * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
- * PositionalDataSource is required to respect page size for efficient tiling. If you want to
- * override page size (e.g. when network page size constraints are only known at runtime), use one
- * of the other DataSource classes.
- * <p>
- * Because a {@code null} item indicates a placeholder in {@link PagedList}, DataSource may not
- * return {@code null} items in lists that it loads. This is so that users of the PagedList
- * can differentiate unloaded placeholder items from content that has been paged in.
- *
- * @param <Key> Unique identifier for item loaded from DataSource. Often an integer to represent
- * position in data set. Note - this is distinct from e.g. Room's {@code @PrimaryKey}.
- * @param <Value> Value type loaded by the DataSource.
- */
-@SuppressWarnings("unused") // suppress warning to remove Key/Value, needed for subclass type safety
-public abstract class DataSource<Key, Value> {
- /**
- * Factory for DataSources.
- * <p>
- * Data-loading systems of an application or library can implement this interface to allow
- * {@code LiveData<PagedList>}s to be created. For example, Room can provide a
- * DataSource.Factory for a given SQL query:
- *
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
- * public abstract DataSource.Factory<Integer, User> usersByLastName();
- * }
- * </pre>
- * In the above sample, {@code Integer} is used because it is the {@code Key} type of
- * PositionalDataSource. Currently, Room uses the {@code LIMIT}/{@code OFFSET} SQL keywords to
- * page a large query with a PositionalDataSource.
- *
- * @param <Key> Key identifying items in DataSource.
- * @param <Value> Type of items in the list loaded by the DataSources.
- */
- public abstract static class Factory<Key, Value> {
- /**
- * Create a DataSource.
- * <p>
- * The DataSource should invalidate itself if the snapshot is no longer valid. If a
- * DataSource becomes invalid, the only way to query more data is to create a new DataSource
- * from the Factory.
- * <p>
- * {@link androidx.paging.LivePagedListBuilder} for example will construct a new PagedList and DataSource
- * when the current DataSource is invalidated, and pass the new PagedList through the
- * {@code LiveData<PagedList>} to observers.
- *
- * @return the new DataSource.
- */
- @NonNull
- public abstract DataSource<Key, Value> create();
-
- /**
- * Applies the given function to each value emitted by DataSources produced by this Factory.
- * <p>
- * Same as {@link #mapByPage(Function)}, but operates on individual items.
- *
- * @param function Function that runs on each loaded item, returning items of a potentially
- * new type.
- * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
- *
- * @return A new DataSource.Factory, which transforms items using the given function.
- *
- * @see #mapByPage(Function)
- * @see DataSource#map(Function)
- * @see DataSource#mapByPage(Function)
- */
- @NonNull
- public <ToValue> DataSource.Factory<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-
- /**
- * Applies the given function to each value emitted by DataSources produced by this Factory.
- * <p>
- * Same as {@link #map(Function)}, but allows for batch conversions.
- *
- * @param function Function that runs on each loaded page, returning items of a potentially
- * new type.
- * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
- *
- * @return A new DataSource.Factory, which transforms items using the given function.
- *
- * @see #map(Function)
- * @see DataSource#map(Function)
- * @see DataSource#mapByPage(Function)
- */
- @NonNull
- public <ToValue> DataSource.Factory<Key, ToValue> mapByPage(
- @NonNull final Function<List<Value>, List<ToValue>> function) {
- return new Factory<Key, ToValue>() {
- @Override
- public DataSource<Key, ToValue> create() {
- return Factory.this.create().mapByPage(function);
- }
- };
- }
- }
-
- @NonNull
- static <X, Y> Function<List<X>, List<Y>> createListFunction(
- final @NonNull Function<X, Y> innerFunc) {
- return new Function<List<X>, List<Y>>() {
- @Override
- public List<Y> apply(@NonNull List<X> source) {
- List<Y> out = new ArrayList<>(source.size());
- for (int i = 0; i < source.size(); i++) {
- out.add(innerFunc.apply(source.get(i)));
- }
- return out;
- }
- };
- }
-
- static <A, B> List<B> convert(Function<List<A>, List<B>> function, List<A> source) {
- List<B> dest = function.apply(source);
- if (dest.size() != source.size()) {
- throw new IllegalStateException("Invalid Function " + function
- + " changed return size. This is not supported.");
- }
- return dest;
- }
-
-
- /**
- * Applies the given function to each value emitted by the DataSource.
- * <p>
- * Same as {@link #map(Function)}, but allows for batch conversions.
- *
- * @param function Function that runs on each loaded page, returning items of a potentially
- * new type.
- * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
- *
- * @return A new DataSource, which transforms items using the given function.
- *
- * @see #map(Function)
- * @see DataSource.Factory#map(Function)
- * @see DataSource.Factory#mapByPage(Function)
- */
- @NonNull
- public <ToValue> DataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- return new WrapperDataSource<>(this, function);
- }
-
- /**
- * Applies the given function to each value emitted by the DataSource.
- * <p>
- * Same as {@link #mapByPage(Function)}, but operates on individual items.
- *
- * @param function Function that runs on each loaded item, returning items of a potentially
- * new type.
- * @param <ToValue> Type of items produced by the new DataSource, from the passed function.
- *
- * @return A new DataSource, which transforms items using the given function.
- *
- * @see #mapByPage(Function)
- * @see DataSource.Factory#map(Function)
- * @see DataSource.Factory#mapByPage(Function)
- */
- @NonNull
- public <ToValue> DataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-
- /**
- * Returns true if the data source guaranteed to produce a contiguous set of items,
- * never producing gaps.
- */
- boolean isContiguous() {
- return true;
- }
-
- boolean supportsPageDropping() {
- return true;
- }
-
- /**
- * Invalidation callback for DataSource.
- * <p>
- * Used to signal when a DataSource a data source has become invalid, and that a new data source
- * is needed to continue loading data.
- */
- public interface InvalidatedCallback {
- /**
- * Called when the data backing the list has become invalid. This callback is typically used
- * to signal that a new data source is needed.
- * <p>
- * This callback will be invoked on the thread that calls {@link #invalidate()}. It is valid
- * for the data source to invalidate itself during its load methods, or for an outside
- * source to invalidate it.
- */
- @AnyThread
- void onInvalidated();
- }
-
- private AtomicBoolean mInvalid = new AtomicBoolean(false);
-
- private CopyOnWriteArrayList<InvalidatedCallback> mOnInvalidatedCallbacks =
- new CopyOnWriteArrayList<>();
-
- /**
- * Add a callback to invoke when the DataSource is first invalidated.
- * <p>
- * Once invalidated, a data source will not become valid again.
- * <p>
- * A data source will only invoke its callbacks once - the first time {@link #invalidate()}
- * is called, on that thread.
- *
- * @param onInvalidatedCallback The callback, will be invoked on thread that invalidates the
- * DataSource.
- */
- @AnyThread
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- //noinspection ConstantConditions
- if ( null) {
- throw new IllegalArgumentException("onInvalidatedCallback must be non-null");
- }
- mOnInvalidatedCallbacks.add(onInvalidatedCallback);
- }
-
- /**
- * Remove a previously added invalidate callback.
- *
- * @param onInvalidatedCallback The previously added callback.
- */
- @AnyThread
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mOnInvalidatedCallbacks.remove(onInvalidatedCallback);
- }
-
- /**
- * Signal the data source to stop loading, and notify its callback.
- * <p>
- * If invalidate has already been called, this method does nothing.
- */
- @AnyThread
- public void invalidate() {
- if (mInvalid.compareAndSet(false, true)) {
- for (InvalidatedCallback callback : mOnInvalidatedCallbacks) {
- callback.onInvalidated();
- }
- }
- }
-
- /**
- * Returns true if the data source is invalid, and can no longer be queried for data.
- *
- * @return True if the data source is invalid, and can no longer return data.
- */
- @WorkerThread
- public boolean isInvalid() {
- return mInvalid.get();
- }
-
- enum LoadType {
- INITIAL,
- START,
- END
- }
-
- @SuppressWarnings("WeakerAccess")
- static class Params<K> {
- @NonNull
- public final LoadType type;
- /* can be NULL for init, otherwise non-null */
- @Nullable
- public final K key;
- public final int initialLoadSize;
- public final boolean placeholdersEnabled;
- public final int pageSize;
-
- Params(@NonNull LoadType type, @Nullable K key, int initialLoadSize,
- boolean placeholdersEnabled, int pageSize) {
- this.type = type;
- this.key = key;
- this.initialLoadSize = initialLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
- this.pageSize = pageSize;
- }
- }
-
- @SuppressWarnings("WeakerAccess")
- static class BaseResult<Value> {
- @SuppressWarnings("unchecked")
- static <T> BaseResult<T> empty() {
- return (BaseResult<T>) EMPTY;
- }
-
- private static final BaseResult<Object> EMPTY =
- new BaseResult<>(Collections.emptyList(), null, null, 0, 0, 0, true);
-
- public final List<Value> data;
- public final Object prevKey;
- public final Object nextKey;
- public final int leadingNulls;
- public final int trailingNulls;
- public final int offset;
- /**
- * Set to true if the result is an initial load that is passed totalCount
- */
- public final boolean counted;
-
- protected BaseResult(List<Value> data, Object prevKey, Object nextKey, int leadingNulls,
- int trailingNulls, int offset, boolean counted) {
- this.data = data;
- this.prevKey = prevKey;
- this.nextKey = nextKey;
- this.leadingNulls = leadingNulls;
- this.trailingNulls = trailingNulls;
- this.offset = offset;
- this.counted = counted;
- validate();
- }
-
- <ToValue> BaseResult(@NonNull BaseResult<ToValue> result,
- @NonNull Function<List<ToValue>, List<Value>> function) {
- data = convert(function, result.data);
- prevKey = result.prevKey;
- nextKey = result.nextKey;
- leadingNulls = result.leadingNulls;
- trailingNulls = result.trailingNulls;
- offset = result.offset;
- counted = result.counted;
- validate();
- }
-
- private int position() {
- // only one of leadingNulls / offset may be used
- return leadingNulls + offset;
- }
-
- static final int TOTAL_COUNT_UNKNOWN = -1;
-
- int totalCount() {
- // only one of leadingNulls / offset may be used
- if (counted) {
- return position() + data.size() + trailingNulls;
- } else {
- return TOTAL_COUNT_UNKNOWN;
- }
-
- }
-
- void validate() {
- if (leadingNulls < 0 || offset < 0) {
- throw new IllegalArgumentException("Position must be non-negative");
- }
- if (data.isEmpty() && (leadingNulls != 0 || trailingNulls != 0)) {
- throw new IllegalArgumentException("Initial result cannot be empty if items are"
- + " present in data set.");
- }
- if (trailingNulls < 0) {
- throw new IllegalArgumentException(
- "List size + position too large, last item in list beyond totalCount.");
- }
- }
-
- void validateForInitialTiling(int pageSize) {
- if (!counted) {
- throw new IllegalStateException("Placeholders requested, but totalCount not"
- + " provided. Please call the three-parameter onResult method, or"
- + " disable placeholders in the PagedList.Config");
- }
- if (trailingNulls != 0
- && data.size() % pageSize != 0) {
- int totalCount = leadingNulls + data.size() + trailingNulls;
- throw new IllegalArgumentException("PositionalDataSource requires initial load size"
- + " to be a multiple of page size to support internal tiling. loadSize "
- + data.size() + ", position " + leadingNulls + ", totalCount " + totalCount
- + ", pageSize " + pageSize);
- }
- if (position() % pageSize != 0) {
- throw new IllegalArgumentException("Initial load must be pageSize aligned."
- + "Position = " + position() + ", pageSize = " + pageSize);
- }
- }
-
- @SuppressWarnings("EqualsHashCode")
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof BaseResult)) {
- return false;
- }
- BaseResult other = (BaseResult) o;
- return data.equals(other.data)
- && PagedList.equalsHelper(prevKey, other.prevKey)
- && PagedList.equalsHelper(nextKey, other.nextKey)
- && leadingNulls == other.leadingNulls
- && trailingNulls == other.trailingNulls
- && offset == other.offset
- && counted == other.counted;
- }
- }
-
- enum KeyType {
- POSITIONAL,
- PAGE_KEYED,
- ITEM_KEYED,
- }
-
- @NonNull
- final KeyType mType;
-
- // Since we currently rely on implementation details of two implementations,
- // prevent external subclassing, except through exposed subclasses
- DataSource(@NonNull KeyType type) {
- mType = type;
- }
-
- abstract ListenableFuture<? extends BaseResult<Value>> load(
- @NonNull Params<Key> params);
-
- @Nullable
- abstract Key getKey(@NonNull Value item);
-
- @Nullable
- final Key getKey(int lastLoad, @Nullable Value item) {
- if (mType == KeyType.POSITIONAL) {
- //noinspection unchecked
- return (Key) ((Integer) lastLoad);
- }
- if (item == null) {
- return null;
- }
- return getKey(item);
- }
-
- /**
- * Determine whether an error passed to a loading method is retryable.
- *
- * @param error Throwable returned from an attempted load from this DataSource.
- * @return true if the error is retryable, otherwise false.
- */
- public boolean isRetryableError(@NonNull Throwable error) {
- return false;
- }
-
- final void initExecutor(@NonNull Executor executor) {
- mExecutor = executor;
- }
-
- /**
- * Null until loadInitial is called by PagedList construction
- */
- @Nullable
- private Executor mExecutor;
-
- @NonNull
- Executor getExecutor() {
- if (mExecutor == null) {
- throw new IllegalStateException(
- "This DataSource has not been passed to a PagedList, has no executor yet.");
- }
- return mExecutor;
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/InitialPagedList.java b/paging/common/src/main/java/androidx/paging/InitialPagedList.java
deleted file mode 100644
index 2589bdf..0000000
--- a/paging/common/src/main/java/androidx/paging/InitialPagedList.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.paging.futures.DirectExecutor;
-
-/**
- * InitialPagedList is an empty placeholder that's sent at the front of a stream of PagedLists.
- *
- * It's used solely for listening to {@link PagedList.LoadType#REFRESH} loading events, and retrying
- * any errors that occur during initial load.
- */
-class InitialPagedList<K, V> extends ContiguousPagedList<K, V> {
- @Nullable
- private K mInitialKey;
-
- InitialPagedList(
- @NonNull DataSource<K, V> dataSource,
- @NonNull Config config,
- @Nullable K initialKey) {
- super(dataSource,
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
- null,
- config,
- DataSource.BaseResult.<V>empty(),
- /* no previous load, so pass 0 */ 0);
- mInitialKey = initialKey;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return mInitialKey;
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
deleted file mode 100644
index bba7477d..0000000
--- a/paging/common/src/main/java/androidx/paging/ItemKeyedDataSource.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for paging keyed content, where loaded content uses previously loaded
- * items as input to future loads.
- * <p>
- * Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
- * to load item {@code N}. This is common, for example, in uniquely sorted database queries where
- * attributes of the item such just before the next query define how to execute it.
- * <p>
- * The {@code InMemoryByItemRepository} in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network ItemKeyedDataSource using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- *
- * @see ListenableItemKeyedDataSource
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ItemKeyedDataSource<Key, Value> extends
- ListenableItemKeyedDataSource<Key, Value> {
-
- /**
- * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- @SuppressWarnings("WeakerAccess")
- public static class LoadInitialParams<Key> extends
- ListenableItemKeyedDataSource.LoadInitialParams<Key> {
- public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
- boolean placeholdersEnabled) {
- super(requestedInitialKey, requestedLoadSize, placeholdersEnabled);
- }
- }
-
- /**
- * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)}
- * and {@link #loadAfter(LoadParams, LoadCallback)}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- @SuppressWarnings("WeakerAccess")
- public static class LoadParams<Key> extends ListenableItemKeyedDataSource.LoadParams<Key> {
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- super(key, requestedLoadSize);
- }
- }
-
- /**
- * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
- * to return data and, optionally, position/count information.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * If you can compute the number of items in the data set before and after the loaded range,
- * call the three parameter {@link #onResult(List, int, int)} to pass that information. You
- * can skip passing this information by calling the single parameter {@link #onResult(List)},
- * either if it's difficult to compute, or if {@link LoadInitialParams#placeholdersEnabled} is
- * {@code false}, so the positioning information will be ignored.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadInitialCallback<Value> extends LoadCallback<Value> {
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be loaded from this DataSource,
- * pass {@code N}.
- * @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial {@code data} parameter
- * as well as any items that can be loaded in front or behind of
- * {@code data}.
- */
- public abstract void onResult(@NonNull List<Value> data, int position, int totalCount);
- }
-
- /**
- * Callback for ItemKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)}
- * and {@link #loadAfter(LoadParams, LoadCallback)} to return data.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadCallback<Value> {
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this method from your ItemKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
- * <p>
- * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
- * initialize without counting available data, or supporting placeholders.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the ItemKeyedDataSource.
- */
- public abstract void onResult(@NonNull List<Value> data);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
- * {@link #loadBefore(LoadParams, LoadCallback)}, or
- * {@link #loadAfter(LoadParams, LoadCallback)} methods.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- @NonNull
- @Override
- public final ListenableFuture<InitialResult<Value>> loadInitial(
- final @NonNull ListenableItemKeyedDataSource.LoadInitialParams<Key> params) {
- final ResolvableFuture<InitialResult<Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- LoadInitialCallback<Value> callback = new LoadInitialCallback<Value>() {
- @Override
- public void onResult(@NonNull List<Value> data, int position, int totalCount) {
- future.set(new InitialResult<>(data, position, totalCount));
- }
-
- @Override
- public void onResult(@NonNull List<Value> data) {
- future.set(new InitialResult<>(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadInitial(new LoadInitialParams<>(
- params.requestedInitialKey,
- params.requestedLoadSize,
- params.placeholdersEnabled),
- callback);
- }
- });
- return future;
- }
-
- @SuppressWarnings("WeakerAccess")
- LoadCallback<Value> getFutureAsCallback(
- final @NonNull ResolvableFuture<Result<Value>> future) {
- return new LoadCallback<Value>() {
- @Override
- public void onResult(@NonNull List<Value> data) {
- future.set(new Result<>(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Value>> loadBefore(
- final @NonNull ListenableItemKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadBefore(new LoadParams<>(params.key, params.requestedLoadSize),
- getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Value>> loadAfter(
- final @NonNull ListenableItemKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadAfter(new LoadParams<>(params.key, params.requestedLoadSize),
- getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
- * the callback via the three-parameter
- * {@link LoadInitialCallback#onResult(List, int, int)}. This enables PagedLists
- * presenting data from this source to display placeholders to represent unloaded items.
- * <p>
- * {@link LoadInitialParams#requestedInitialKey} and {@link LoadInitialParams#requestedLoadSize}
- * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
- * {@code requestedInitialKey} can prevent subsequent PagedList/DataSource pairs from
- * initializing at the same location. If your DataSource never invalidates (for example,
- * loading from the network without the network ever signalling that old data must be reloaded),
- * it's fine to ignore the {@code initialLoadKey} and always start from the beginning of the
- * data set.
- *
- * @param params Parameters for initial load, including initial key and requested size.
- * @param callback Callback that receives initial load data.
- */
- public abstract void loadInitial(
- @NonNull LoadInitialParams<Key> params,
- @NonNull LoadInitialCallback<Value> callback);
-
- /**
- * Load list data after the key specified in {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * Data may be passed synchronously during the loadAfter method, or deferred and called at a
- * later time. Further loads going down will be blocked until the callback is called.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load after, and requested size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadAfter(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Value> callback);
-
- /**
- * Load list data before the key specified in {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
- * passed, so if you vary size, ensure that the last item is adjacent to the passed key.
- * <p>
- * Data may be passed synchronously during the loadBefore method, or deferred and called at a
- * later time. Further loads going up will be blocked until the callback is called.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load before, and requested size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadBefore(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Value> callback);
-
- /**
- * Return a key associated with the given item.
- * <p>
- * If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
- * integer ID, you would return {@code item.getID()} here. This key can then be passed to
- * {@link #loadBefore(LoadParams, LoadCallback)} or
- * {@link #loadAfter(LoadParams, LoadCallback)} to load additional items adjacent to the item
- * passed to this function.
- * <p>
- * If your key is more complex, such as when you're sorting by name, then resolving collisions
- * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
- * such as {@code Pair<String, Integer>} or, in Kotlin,
- * {@code data class Key(val name: String, val id: Int)}
- *
- * @param item Item to get the key from.
- * @return Key associated with given item.
- */
- @NonNull
- @Override
- public abstract Key getKey(@NonNull Value item);
-
- @NonNull
- @Override
- public final <ToValue> ItemKeyedDataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- return new WrapperItemKeyedDataSource<>(this, function);
- }
-
- @NonNull
- @Override
- public final <ToValue> ItemKeyedDataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListDataSource.java b/paging/common/src/main/java/androidx/paging/ListDataSource.java
deleted file mode 100644
index 3f0971f..0000000
--- a/paging/common/src/main/java/androidx/paging/ListDataSource.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class ListDataSource<T> extends PositionalDataSource<T> {
- private final List<T> mList;
-
- public ListDataSource(List<T> list) {
- mList = new ArrayList<>(list);
- }
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback<T> callback) {
- final int totalCount = mList.size();
- final int position = computeInitialLoadPosition(params, totalCount);
- final int loadSize = computeInitialLoadSize(params, position, totalCount);
-
- // for simplicity, we could return everything immediately,
- // but we tile here since it's expected behavior
- List<T> sublist = mList.subList(position, position + loadSize);
- callback.onResult(sublist, position, totalCount);
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- @NonNull LoadRangeCallback<T> callback) {
- callback.onResult(mList.subList(params.startPosition,
- Math.min(mList.size(), params.startPosition + params.loadSize)));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java
deleted file mode 100644
index e81193d..0000000
--- a/paging/common/src/main/java/androidx/paging/ListenableItemKeyedDataSource.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for paging keyed content, where loaded content uses previously loaded
- * items as input to future loads.
- * <p>
- * Implement a DataSource using ListenableItemKeyedDataSource if you need to use data from item
- * {@code N - 1} to load item {@code N}. This is common, for example, in uniquely sorted database
- * queries where attributes of the item such just before the next query define how to execute it.
- *
- * @see ItemKeyedDataSource
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ListenableItemKeyedDataSource<Key, Value> extends DataSource<Key, Value> {
- public ListenableItemKeyedDataSource() {
- super(KeyType.ITEM_KEYED);
- }
-
- @Override
- final ListenableFuture<? extends BaseResult<Value>> load(@NonNull Params<Key> params) {
- if (params.type == LoadType.INITIAL) {
- ItemKeyedDataSource.LoadInitialParams<Key> initParams =
- new ItemKeyedDataSource.LoadInitialParams<>(params.key,
- params.initialLoadSize, params.placeholdersEnabled);
- return loadInitial(initParams);
- } else {
- //noinspection ConstantConditions (key is known to be non-null for non-initial queries)
- ItemKeyedDataSource.LoadParams<Key> loadParams =
- new ItemKeyedDataSource.LoadParams<>(params.key, params.pageSize);
-
- if (params.type == LoadType.START) {
- return loadBefore(loadParams);
- } else if (params.type == LoadType.END) {
- return loadAfter(loadParams);
- }
- }
- throw new IllegalArgumentException("Unsupported type " + params.type.toString());
- }
-
-
- /**
- * Holder object for inputs to {@code loadInitial()}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- public static class LoadInitialParams<Key> {
- /**
- * Load items around this key, or at the beginning of the data set if {@code null} is
- * passed.
- * <p>
- * Note that this key is generally a hint, and may be ignored if you want to always load
- * from the beginning.
- */
- @Nullable
- public final Key requestedInitialKey;
-
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the loaded total count will be
- * ignored.
- */
- public final boolean placeholdersEnabled;
-
- public LoadInitialParams(@Nullable Key requestedInitialKey, int requestedLoadSize,
- boolean placeholdersEnabled) {
- this.requestedInitialKey = requestedInitialKey;
- this.requestedLoadSize = requestedLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
- }
- }
-
- /**
- * Holder object for inputs to {@code loadBefore()} and {@code loadAfter()}.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- */
- public static class LoadParams<Key> {
- /**
- * Load items before/after this key.
- * <p>
- * Returned data must begin directly adjacent to this position.
- */
- @NonNull
- public final Key key;
- /**
- * Requested number of items to load.
- * <p>
- * Returned page can be of this size, but it may be altered if that is easier, e.g. a
- * network data source where the backend defines page size.
- */
- public final int requestedLoadSize;
-
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- this.key = key;
- this.requestedLoadSize = requestedLoadSize;
- }
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass {@code totalCount}
- * to the {@link InitialResult} constructor. This enables PagedLists presenting data from this
- * source to display placeholders to represent unloaded items.
- * <p>
- * {@link ItemKeyedDataSource.LoadInitialParams#requestedInitialKey} and
- * {@link ItemKeyedDataSource.LoadInitialParams#requestedLoadSize} are hints, not requirements,
- * so they may be altered or ignored. Note that ignoring the {@code requestedInitialKey} can
- * prevent subsequent PagedList/DataSource pairs from initializing at the same location. If your
- * DataSource never invalidates (for example, loading from the network without the network ever
- * signalling that old data must be reloaded), it's fine to ignore the {@code initialLoadKey}
- * and always start from the beginning of the data set.
- *
- * @param params Parameters for initial load, including initial key and requested size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<InitialResult<Value>> loadInitial(
- @NonNull LoadInitialParams<Key> params);
-
- /**
- * Load list data after the key specified in
- * {@link ItemKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load after, and requested size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Value>> loadAfter(@NonNull LoadParams<Key> params);
-
-
- /**
- * Load list data after the key specified in
- * {@link ItemKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * <p class="note"><strong>Note:</strong> Data returned will be prepended just before the key
- * passed, so if you don't return a page of the requested size, ensure that the last item is
- * adjacent to the passed key.
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key to load before, and requested size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Value>> loadBefore(@NonNull LoadParams<Key> params);
-
-
- @Nullable
- @Override
- public abstract Key getKey(@NonNull Value item);
-
- /**
- * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
- * initially loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class InitialResult<V> extends BaseResult<V> {
- public InitialResult(@NonNull List<V> data, int position, int totalCount) {
- super(data, null, null, position, totalCount - data.size() - position, position, true);
- }
-
- public InitialResult(@NonNull List<V> data) {
- super(data, null, null, 0, 0, 0, false);
- }
- }
-
- /**
- * Type produced by {@link #loadBefore(LoadParams)} and
- * {@link #loadAfter(LoadParams)} to represent a page of loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class Result<V> extends BaseResult<V> {
- public Result(@NonNull List<V> data) {
- super(data, null, null, 0, 0, 0, false);
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java
deleted file mode 100644
index f15075c..0000000
--- a/paging/common/src/main/java/androidx/paging/ListenablePageKeyedDataSource.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for page-keyed content, where requests return keys for next/previous
- * pages.
- * <p>
- * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
- * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
- * link or key with each page load.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class ListenablePageKeyedDataSource<Key, Value> extends DataSource<Key, Value> {
- public ListenablePageKeyedDataSource() {
- super(KeyType.PAGE_KEYED);
- }
-
- @Override
- final ListenableFuture<? extends BaseResult<Value>> load(
- @NonNull Params<Key> params) {
- if (params.type == LoadType.INITIAL) {
- PageKeyedDataSource.LoadInitialParams<Key> initParams =
- new PageKeyedDataSource.LoadInitialParams<>(
- params.initialLoadSize, params.placeholdersEnabled);
- return loadInitial(initParams);
- } else {
- if (params.key == null) {
- // null key, immediately return empty data
- ResolvableFuture<BaseResult<Value>> future = ResolvableFuture.create();
- future.set(BaseResult.<Value>empty());
- return future;
- }
-
- PageKeyedDataSource.LoadParams<Key> loadParams =
- new PageKeyedDataSource.LoadParams<>(params.key, params.pageSize);
-
- if (params.type == LoadType.START) {
- return loadBefore(loadParams);
- } else if (params.type == LoadType.END) {
- return loadAfter(loadParams);
- }
- }
- throw new IllegalArgumentException("Unsupported type " + params.type.toString());
- }
-
- /**
- * Holder object for inputs to {@code loadInitial()}.
- *
- * @param <Key> Type of data used to query pages.
- */
- @SuppressWarnings("unused")
- public static class LoadInitialParams<Key> {
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the loaded total count will be
- * ignored.
- */
- public final boolean placeholdersEnabled;
-
-
- public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
- this.requestedLoadSize = requestedLoadSize;
- this.placeholdersEnabled = placeholdersEnabled;
- }
- }
-
- /**
- * Holder object for inputs to {@code loadBefore()} and {@code loadAfter()}.
- *
- * @param <Key> Type of data used to query pages.
- */
- public static class LoadParams<Key> {
- /**
- * Load items before/after this key.
- * <p>
- * Returned data must begin directly adjacent to this position.
- */
- @NonNull
- public final Key key;
-
- /**
- * Requested number of items to load.
- * <p>
- * Returned page can be of this size, but it may be altered if that is easier, e.g. a
- * network data source where the backend defines page size.
- */
- public final int requestedLoadSize;
-
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- this.key = key;
- this.requestedLoadSize = requestedLoadSize;
- }
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass the position and
- * count to the
- * {@link InitialResult InitialResult constructor}. This
- * enables PagedLists presenting data from this source to display placeholders to represent
- * unloaded items.
- * <p>
- * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement,
- * so it may be may be altered or ignored.
- *
- * @param params Parameters for initial load, including requested load size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<InitialResult<Key, Value>> loadInitial(
- @NonNull LoadInitialParams<Key> params);
-
- /**
- * Prepend page with the key specified by
- * {@link PageKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Key, Value>> loadBefore(
- @NonNull LoadParams<Key> params);
- /**
- * Append page with the key specified by
- * {@link PageKeyedDataSource.LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<Result<Key, Value>> loadAfter(
- @NonNull LoadParams<Key> params);
-
- @Nullable
- @Override
- Key getKey(@NonNull Value item) {
- return null;
- }
-
- @Override
- boolean supportsPageDropping() {
- /* To support page dropping when PageKeyed, we'll need to:
- * - Stash keys for every page we have loaded (can id by index relative to loadInitial)
- * - Drop keys for any page not adjacent to loaded content
- * - And either:
- * - Allow impl to signal previous page key: onResult(data, nextPageKey, prevPageKey)
- * - Re-trigger loadInitial, and break assumption it will only occur once.
- */
- return false;
- }
-
- /**
- * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
- * initially loaded data.
- *
- * @param <Key> Type of key used to identify pages.
- * @param <Value> Type of items being loaded by the DataSource.
- */
- public static class InitialResult<Key, Value> extends BaseResult<Value> {
- public InitialResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
- super(data, previousPageKey, nextPageKey,
- position, totalCount - data.size() - position, position, true);
- }
-
- public InitialResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey) {
- super(data, previousPageKey, nextPageKey, 0, 0, 0, false);
- }
- }
-
- /**
- * Type produced by {@link #loadBefore(LoadParams)} and {@link #loadAfter(LoadParams)} to
- * represent a page of loaded data.
- *
- * @param <Key> Type of key used to identify pages.
- * @param <Value> Type of items being loaded by the DataSource.
- */
- public static class Result<Key, Value> extends BaseResult<Value> {
- public Result(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
- super(data, adjacentPageKey, adjacentPageKey, 0, 0, 0, false);
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java b/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java
deleted file mode 100644
index a28e156..0000000
--- a/paging/common/src/main/java/androidx/paging/ListenablePositionalDataSource.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
- * arbitrary page positions.
- * <p>
- * Extend ListenablePositionalDataSource if you can load pages of a requested size at arbitrary
- * positions, and provide a fixed item count. If your data source can't support loading arbitrary
- * requested page sizes (e.g. when network page size constraints are only known at runtime), either
- * use {@link PageKeyedDataSource} or {@link ItemKeyedDataSource}, or pass the initial result with
- * the two parameter {@link InitialResult InitialResult constructor}.
- *
- * @see PositionalDataSource
- *
- * @param <T> Type of items being loaded by the PositionalDataSource.
- */
-public abstract class ListenablePositionalDataSource<T> extends DataSource<Integer, T> {
- public ListenablePositionalDataSource() {
- super(KeyType.POSITIONAL);
- }
-
- @Override
- final ListenableFuture<? extends BaseResult<T>> load(@NonNull Params<Integer> params) {
- if (params.type == LoadType.INITIAL) {
- int initialPosition = 0;
- int initialLoadSize = params.initialLoadSize;
- if (params.key != null) {
- initialPosition = params.key;
-
- if (params.placeholdersEnabled) {
- // snap load size to page multiple (minimum two)
- initialLoadSize = Math.max(initialLoadSize / params.pageSize, 2)
- * params.pageSize;
-
- // move start so the load is centered around the key, not starting at it
- final int idealStart = initialPosition - initialLoadSize / 2;
- initialPosition = Math.max(0, idealStart / params.pageSize * params.pageSize);
- } else {
- // not tiled, so don't try to snap or force multiple of a page size
- initialPosition = initialPosition - initialLoadSize / 2;
- }
-
- }
- PositionalDataSource.LoadInitialParams initParams =
- new PositionalDataSource.LoadInitialParams(
- initialPosition,
- initialLoadSize,
- params.pageSize,
- params.placeholdersEnabled);
- return loadInitial(initParams);
- } else {
- int startIndex = params.key;
- int loadSize = params.pageSize;
- if (params.type == LoadType.START) {
- loadSize = Math.min(loadSize, startIndex + 1);
- startIndex = startIndex - loadSize + 1;
- }
- return loadRange(new PositionalDataSource.LoadRangeParams(startIndex, loadSize));
- }
- }
-
- /**
- * Holder object for inputs to {@code loadInitial()}.
- */
- public static class LoadInitialParams {
- /**
- * Initial load position requested.
- * <p>
- * Note that this may not be within the bounds of your data set, it may need to be adjusted
- * before you execute your load.
- */
- public final int requestedStartPosition;
-
- /**
- * Requested number of items to load.
- * <p>
- * Note that this may be larger than available data.
- */
- public final int requestedLoadSize;
-
- /**
- * Defines page size acceptable for return values.
- * <p>
- * List of items passed to the callback must be an integer multiple of page size.
- */
- public final int pageSize;
-
- /**
- * Defines whether placeholders are enabled, and whether the loaded total count will be
- * ignored.
- */
- public final boolean placeholdersEnabled;
-
- public LoadInitialParams(
- int requestedStartPosition,
- int requestedLoadSize,
- int pageSize,
- boolean placeholdersEnabled) {
- this.requestedStartPosition = requestedStartPosition;
- this.requestedLoadSize = requestedLoadSize;
- this.pageSize = pageSize;
- this.placeholdersEnabled = placeholdersEnabled;
- }
- }
-
- /**
- * Holder object for inputs to {@code loadRange()}.
- */
- public static class LoadRangeParams {
- /**
- * START position of data to load.
- * <p>
- * Returned data must start at this position.
- */
- public final int startPosition;
- /**
- * Number of items to load.
- * <p>
- * Returned data must be of this size, unless at end of the list.
- */
- public final int loadSize;
-
- public LoadRangeParams(int startPosition, int loadSize) {
- this.startPosition = startPosition;
- this.loadSize = loadSize;
- }
- }
-
- /**
- * Load initial list data.
- * <p>
- * This method is called to load the initial page(s) from the DataSource.
- * <p>
- * Result list must be a multiple of pageSize to enable efficient tiling.
- *
- * @param params Parameters for initial load, including requested start position, load size, and
- * page size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<InitialResult<T>> loadInitial(
- @NonNull LoadInitialParams params);
-
- /**
- * Called to load a range of data from the DataSource.
- * <p>
- * This method is called to load additional pages from the DataSource after the
- * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
- * <p>
- * Unlike {@link #loadInitial(LoadInitialParams)}, this method must return
- * the number of items requested, at the position requested.
- *
- * @param params Parameters for load, including start position and load size.
- * @return ListenableFuture of the loaded data.
- */
- @NonNull
- public abstract ListenableFuture<RangeResult<T>> loadRange(@NonNull LoadRangeParams params);
-
- @Nullable
- @Override
- final Integer getKey(@NonNull T item) {
- return null;
- }
-
- /**
- * Type produced by {@link #loadInitial(LoadInitialParams)} to represent
- * initially loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class InitialResult<V> extends BaseResult<V> {
- public InitialResult(@NonNull List<V> data, int position, int totalCount) {
- super(data, null, null, position, totalCount - data.size() - position, 0, true);
- if (data.isEmpty() && position != 0) {
- throw new IllegalArgumentException(
- "Initial result cannot be empty if items are present in data set.");
- }
- }
-
- public InitialResult(@NonNull List<V> data, int position) {
- super(data, null, null, 0, 0, position, false);
- if (data.isEmpty() && position != 0) {
- throw new IllegalArgumentException(
- "Initial result cannot be empty if items are present in data set.");
- }
- }
- }
-
- /**
- * Type produced by {@link #loadRange(LoadRangeParams)} to represent a page
- * of loaded data.
- *
- * @param <V> The type of the data loaded.
- */
- public static class RangeResult<V> extends BaseResult<V> {
- public RangeResult(@NonNull List<V> data) {
- super(data, null, null, 0, 0, 0, false);
- }
- }
-
- /**
- * Helper for computing an initial position in
- * {@link #loadInitial(LoadInitialParams)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * The value computed by this function will do bounds checking, page alignment, and positioning
- * based on initial load size requested.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * class ItemDataSource extends PositionalDataSource<Item> {
- * private int computeCount() {
- * // actual count code here
- * }
- *
- * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
- * // actual load code here
- * }
- *
- * {@literal @}Override
- * public void loadInitial({@literal @}NonNull LoadInitialParams params,
- * {@literal @}NonNull LoadInitialCallback<Item> callback) {
- * int totalCount = computeCount();
- * int position = computeInitialLoadPosition(params, totalCount);
- * int loadSize = computeInitialLoadSize(params, position, totalCount);
- * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
- * }
- *
- * {@literal @}Override
- * public void loadRange({@literal @}NonNull LoadRangeParams params,
- * {@literal @}NonNull LoadRangeCallback<Item> callback) {
- * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
- * }
- * }</pre>
- *
- * @param params Params passed to {@link #loadInitial(LoadInitialParams)},
- * including page size, and requested start/loadSize.
- * @param totalCount Total size of the data set.
- * @return Position to start loading at.
- *
- *
- * @see #computeInitialLoadSize(ListenablePositionalDataSource.LoadInitialParams, int, int)
- */
- public static int computeInitialLoadPosition(
- @NonNull ListenablePositionalDataSource.LoadInitialParams params,
- int totalCount) {
- int position = params.requestedStartPosition;
- int initialLoadSize = params.requestedLoadSize;
- int pageSize = params.pageSize;
-
- int pageStart = position / pageSize * pageSize;
-
- // maximum start pos is that which will encompass end of list
- int maximumLoadPage = ((totalCount - initialLoadSize + pageSize - 1) / pageSize) * pageSize;
- pageStart = Math.min(maximumLoadPage, pageStart);
-
- // minimum start position is 0
- pageStart = Math.max(0, pageStart);
-
- return pageStart;
- }
-
- /**
- * Helper for computing an initial load size in
- * {@link #loadInitial(LoadInitialParams)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * This function takes the requested load size, and bounds checks it against the value returned
- * by
- * {@link #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)}.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * class ItemDataSource extends PositionalDataSource<Item> {
- * private int computeCount() {
- * // actual count code here
- * }
- *
- * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
- * // actual load code here
- * }
- *
- * {@literal @}Override
- * public void loadInitial({@literal @}NonNull LoadInitialParams params,
- * {@literal @}NonNull LoadInitialCallback<Item> callback) {
- * int totalCount = computeCount();
- * int position = computeInitialLoadPosition(params, totalCount);
- * int loadSize = computeInitialLoadSize(params, position, totalCount);
- * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
- * }
- *
- * {@literal @}Override
- * public void loadRange({@literal @}NonNull LoadRangeParams params,
- * {@literal @}NonNull LoadRangeCallback<Item> callback) {
- * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
- * }
- * }</pre>
- *
- * @param params Params passed to {@link #loadInitial(LoadInitialParams)},
- * including page size, and requested start/loadSize.
- * @param initialLoadPosition Value returned by
- * {@link #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)}
- * @param totalCount Total size of the data set.
- * @return Number of items to load.
- *
- * @see #computeInitialLoadPosition(ListenablePositionalDataSource.LoadInitialParams, int)
- */
- public static int computeInitialLoadSize(@NonNull
- ListenablePositionalDataSource.LoadInitialParams params,
- int initialLoadPosition, int totalCount) {
- return Math.min(totalCount - initialLoadPosition, params.requestedLoadSize);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
deleted file mode 100644
index 65f9c0d..0000000
--- a/paging/common/src/main/java/androidx/paging/PageKeyedDataSource.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-
-/**
- * Incremental data loader for page-keyed content, where requests return keys for next/previous
- * pages.
- * <p>
- * Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
- * to load page {@code N}. This is common, for example, in network APIs that include a next/previous
- * link or key with each page load.
- * <p>
- * The {@code InMemoryByPageRepository} in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network PageKeyedDataSource using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- *
- * @param <Key> Type of data used to query Value types out of the DataSource.
- * @param <Value> Type of items being loaded by the DataSource.
- */
-public abstract class PageKeyedDataSource<Key, Value>
- extends ListenablePageKeyedDataSource<Key, Value> {
- /**
- * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param <Key> Type of data used to query pages.
- */
- public static class LoadInitialParams<Key> extends
- ListenablePageKeyedDataSource.LoadInitialParams<Key> {
- public LoadInitialParams(int requestedLoadSize, boolean placeholdersEnabled) {
- super(requestedLoadSize, placeholdersEnabled);
- }
- }
-
- /**
- * Holder object for inputs to {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)}.
- *
- * @param <Key> Type of data used to query pages.
- */
- public static class LoadParams<Key> extends ListenablePageKeyedDataSource.LoadParams<Key> {
- public LoadParams(@NonNull Key key, int requestedLoadSize) {
- super(key, requestedLoadSize);
- }
- }
-
- /**
- * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
- * to return data and, optionally, position/count information.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * If you can compute the number of items in the data set before and after the loaded range,
- * call the five parameter {@link #onResult(List, int, int, Key, Key)} to pass that
- * information. You can skip passing this information by calling the three parameter
- * {@link #onResult(List, Key, Key)}, either if it's difficult to compute, or if
- * {@link LoadInitialParams#placeholdersEnabled} is {@code false}, so the positioning
- * information will be ignored.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Key> Type of data used to query pages.
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadInitialCallback<Key, Value> {
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be loaded from this DataSource,
- * pass {@code N}.
- * @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial {@code data} parameter
- * as well as any items that can be loaded in front or behind of
- * {@code data}.
- */
- public abstract void onResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey);
-
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this from {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} to
- * initialize without counting available data, or supporting placeholders.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- *
- * @param data List of items loaded from the PageKeyedDataSource.
- * @param previousPageKey Key for page before the initial load result, or {@code null} if no
- * more data can be loaded before.
- * @param nextPageKey Key for page after the initial load result, or {@code null} if no
- * more data can be loaded after.
- */
- public abstract void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- /**
- * Callback for PageKeyedDataSource {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} to return data.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <Key> Type of data used to query pages.
- * @param <Value> Type of items being loaded.
- */
- public abstract static class LoadCallback<Key, Value> {
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this method from your PageKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods to return data.
- * <p>
- * It is always valid to pass a different amount of data than what is requested. Pass an
- * empty list if there is no more data to load.
- * <p>
- * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
- * loaded a page in {@link #loadBefore(LoadParams, LoadCallback)}, pass the key for the
- * previous page, or {@code null} if the loaded page is the first. If in
- * {@link #loadAfter(LoadParams, LoadCallback)}, pass the key for the next page, or
- * {@code null} if the loaded page is the last.
- *
- * @param data List of items loaded from the PageKeyedDataSource.
- * @param adjacentPageKey Key for subsequent page load (previous page in {@link #loadBefore}
- * / next page in {@link #loadAfter}), or {@code null} if there are
- * no more pages to load in the current load direction.
- */
- public abstract void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from your PageKeyedDataSource's
- * {@link #loadBefore(LoadParams, LoadCallback)} and
- * {@link #loadAfter(LoadParams, LoadCallback)} methods.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- @NonNull
- @Override
- public final ListenableFuture<InitialResult<Key, Value>> loadInitial(
- final @NonNull ListenablePageKeyedDataSource.LoadInitialParams<Key> params) {
- final ResolvableFuture<InitialResult<Key, Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- LoadInitialCallback<Key, Value> callback = new LoadInitialCallback<Key, Value>() {
- @Override
- public void onResult(@NonNull List<Value> data, int position, int totalCount,
- @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
- future.set(new InitialResult<>(data, position, totalCount, previousPageKey,
- nextPageKey));
- }
-
- @Override
- public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
- @Nullable Key nextPageKey) {
- future.set(new InitialResult<>(data, previousPageKey, nextPageKey));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadInitial(new LoadInitialParams<Key>(
- params.requestedLoadSize,
- params.placeholdersEnabled),
- callback);
- }
- });
- return future;
- }
-
- @SuppressWarnings("WeakerAccess")
- LoadCallback<Key, Value> getFutureAsCallback(
- final @NonNull ResolvableFuture<Result<Key, Value>> future) {
- return new LoadCallback<Key, Value>() {
- @Override
- public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
- future.set(new Result<>(data, adjacentPageKey));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Key, Value>> loadBefore(
- final @NonNull ListenablePageKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Key, Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadBefore(new LoadParams<>(
- params.key,
- params.requestedLoadSize),
- getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- @NonNull
- @Override
- public final ListenableFuture<Result<Key, Value>> loadAfter(
- final @NonNull ListenablePageKeyedDataSource.LoadParams<Key> params) {
- final ResolvableFuture<Result<Key, Value>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- loadAfter(new LoadParams<>(
- params.key,
- params.requestedLoadSize), getFutureAsCallback(future));
- }
- });
- return future;
- }
-
- /**
- * Load initial data.
- * <p>
- * This method is called first to initialize a PagedList with data. If it's possible to count
- * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
- * the callback via the three-parameter
- * {@link LoadInitialCallback#onResult(List, int, int, Key, Key)}. This enables PagedLists
- * presenting data from this source to display placeholders to represent unloaded items.
- * <p>
- * {@link LoadInitialParams#requestedLoadSize} is a hint, not a requirement, so it may be may be
- * altered or ignored.
- *
- * @param params Parameters for initial load, including requested load size.
- * @param callback Callback that receives initial load data.
- */
- public abstract void loadInitial(@NonNull LoadInitialParams<Key> params,
- @NonNull LoadInitialCallback<Key, Value> callback);
-
- /**
- * Prepend page with the key specified by {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * Data may be passed synchronously during the load method, or deferred and called at a
- * later time. Further loads going down will be blocked until the callback is called.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadBefore(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Key, Value> callback);
-
- /**
- * Append page with the key specified by {@link LoadParams#key LoadParams.key}.
- * <p>
- * It's valid to return a different list size than the page size if it's easier, e.g. if your
- * backend defines page sizes. It is generally preferred to increase the number loaded than
- * reduce.
- * <p>
- * Data may be passed synchronously during the load method, or deferred and called at a
- * later time. Further loads going down will be blocked until the callback is called.
- * <p>
- * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
- * and inconsistent), it is valid to call {@link #invalidate()} to invalidate the data source,
- * and prevent further loading.
- *
- * @param params Parameters for the load, including the key for the new page, and requested load
- * size.
- * @param callback Callback that receives loaded data.
- */
- public abstract void loadAfter(@NonNull LoadParams<Key> params,
- @NonNull LoadCallback<Key, Value> callback);
-
- @NonNull
- @Override
- public final <ToValue> PageKeyedDataSource<Key, ToValue> mapByPage(
- @NonNull Function<List<Value>, List<ToValue>> function) {
- return new WrapperPageKeyedDataSource<>(this, function);
- }
-
- @NonNull
- @Override
- public final <ToValue> PageKeyedDataSource<Key, ToValue> map(
- @NonNull Function<Value, ToValue> function) {
- return mapByPage(createListFunction(function));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PagedList.java b/paging/common/src/main/java/androidx/paging/PagedList.java
deleted file mode 100644
index 2339f2b..0000000
--- a/paging/common/src/main/java/androidx/paging/PagedList.java
+++ /dev/null
@@ -1,1443 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.IntRange;
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.WorkerThread;
-import androidx.arch.core.util.Function;
-import androidx.paging.futures.DirectExecutor;
-import androidx.paging.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.lang.ref.WeakReference;
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-
-/**
- * Lazy loading list that pages in immutable content from a {@link DataSource}.
- * <p>
- * A PagedList is a {@link List} which loads its data in chunks (pages) from a {@link DataSource}.
- * Items can be accessed with {@link #get(int)}, and further loading can be triggered with
- * {@link #loadAround(int)}. To display a PagedList, see {@link androidx.paging.PagedListAdapter}, which enables the
- * binding of a PagedList to a {@link androidx.recyclerview.widget.RecyclerView}.
- * <h4>Loading Data</h4>
- * <p>
- * All data in a PagedList is loaded from its {@link DataSource}. Creating a PagedList loads the
- * first chunk of data from the DataSource immediately, and should for this reason be done on a
- * background thread. The constructed PagedList may then be passed to and used on the UI thread.
- * This is done to prevent passing a list with no loaded content to the UI thread, which should
- * generally not be presented to the user.
- * <p>
- * A PagedList initially presents this first partial load as its content, and expands over time as
- * content is loaded in. When {@link #loadAround} is called, items will be loaded in near the passed
- * list index. If placeholder {@code null}s are present in the list, they will be replaced as
- * content is loaded. If not, newly loaded items will be inserted at the beginning or end of the
- * list.
- * <p>
- * PagedList can present data for an unbounded, infinite scrolling list, or a very large but
- * countable list. Use {@link Config} to control how many items a PagedList loads, and when.
- * <p>
- * If you use {@link androidx.paging.LivePagedListBuilder} to get a
- * {@link androidx.lifecycle.LiveData}, it will initialize PagedLists on a
- * background thread for you.
- * <h4>Placeholders</h4>
- * <p>
- * There are two ways that PagedList can represent its not-yet-loaded data - with or without
- * {@code null} placeholders.
- * <p>
- * With placeholders, the PagedList is always the full size of the data set. {@code get(N)} returns
- * the {@code N}th item in the data set, or {@code null} if its not yet loaded.
- * <p>
- * Without {@code null} placeholders, the PagedList is the sublist of data that has already been
- * loaded. The size of the PagedList is the number of currently loaded items, and {@code get(N)}
- * returns the {@code N}th <em>loaded</em> item. This is not necessarily the {@code N}th item in the
- * data set.
- * <p>
- * Placeholders have several benefits:
- * <ul>
- * <li>They express the full sized list to the presentation layer (often a
- * {@link androidx.paging.PagedListAdapter}), and so can support scrollbars (without jumping as pages are
- * loaded or dropped) and fast-scrolling to any position, loaded or not.
- * <li>They avoid the need for a loading spinner at the end of the loaded list, since the list
- * is always full sized.
- * </ul>
- * <p>
- * They also have drawbacks:
- * <ul>
- * <li>Your Adapter needs to account for {@code null} items. This often means providing default
- * values in data you bind to a {@link androidx.recyclerview.widget.RecyclerView.ViewHolder}.
- * <li>They don't work well if your item views are of different sizes, as this will prevent
- * loading items from cross-fading nicely.
- * <li>They require you to count your data set, which can be expensive or impossible, depending
- * on your DataSource.
- * </ul>
- * <p>
- * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
- * DataSource does not count its data set in its initial load, or if {@code false} is passed to
- * {@link Config.Builder#setEnablePlaceholders(boolean)} when building a {@link Config}.
- * <h4>Mutability and Snapshots</h4>
- * A PagedList is <em>mutable</em> while loading, or ready to load from its DataSource.
- * As loads succeed, a mutable PagedList will be updated via Runnables on the main thread. You can
- * listen to these updates with a {@link Callback}. (Note that {@link androidx.paging.PagedListAdapter} will listen
- * to these to signal RecyclerView about the updates/changes).
- * <p>
- * If a PagedList attempts to load from an invalid DataSource, it will {@link #detach()}
- * from the DataSource, meaning that it will no longer attempt to load data. It will return true
- * from {@link #isImmutable()}, and a new DataSource / PagedList pair must be created to load
- * further data. See {@link DataSource} and {@link androidx.paging.LivePagedListBuilder} for how new PagedLists are
- * created to represent changed data.
- * <p>
- * A PagedList snapshot is simply an immutable shallow copy of the current state of the PagedList as
- * a {@code List}. It will reference the same inner items, and contain the same {@code null}
- * placeholders, if present.
- *
- * @param <T> The type of the entries in the list.
- */
-public abstract class PagedList<T> extends AbstractList<T> {
-
- /**
- * Type of load a PagedList can perform.
- * <p>
- * You can use a {@link LoadStateListener} to observe {@link LoadState} of
- * any {@link LoadType}. For UI purposes (swipe refresh, loading spinner, retry button), this
- * is typically done by registering a Listener with the {@code PagedListAdapter} or
- * {@code AsyncPagedListDiffer}.
- *
- * @see LoadState
- */
- public enum LoadType {
- /**
- * PagedList content being reloaded, may contain content updates.
- */
- REFRESH,
-
- /**
- * Load at the start of the PagedList.
- */
- START,
-
- /**
- * Load at the end of the PagedList.
- */
- END
- }
-
- /**
- * State of a PagedList load - associated with a {@code LoadType}
- * <p>
- * You can use a {@link LoadStateListener} to observe {@link LoadState} of
- * any {@link LoadType}. For UI purposes (swipe refresh, loading spinner, retry button), this
- * is typically done by registering a Listener with the {@code PagedListAdapter} or
- * {@code AsyncPagedListDiffer}.
- */
- public enum LoadState {
- /**
- * Indicates the PagedList is not currently loading, and no error currently observed.
- */
- IDLE,
-
- /**
- * Loading is in progress.
- */
- LOADING,
-
- /**
- * Loading is complete.
- */
- DONE,
-
- /**
- * Loading hit a non-retryable error.
- */
- ERROR,
-
- /**
- * Loading hit a retryable error.
- *
- * @see #retry()
- */
- RETRYABLE_ERROR,
- }
-
- /**
- * Listener for changes to loading state - whether the refresh, prepend, or append is idle,
- * loading, or has an error.
- * <p>
- * Can be used to observe the {@link LoadState} of any {@link LoadType} (REFRESH/START/END).
- * For UI purposes (swipe refresh, loading spinner, retry button), this is typically done by
- * registering a Listener with the {@code PagedListAdapter} or {@code AsyncPagedListDiffer}.
- * <p>
- * These calls will be dispatched on the executor defined by
- * {@link Builder#setNotifyExecutor(Executor)}, which is generally the main/UI thread.
- *
- * @see LoadType
- * @see LoadState
- */
- public interface LoadStateListener {
- /**
- * Called when the LoadState has changed - whether the refresh, prepend, or append is
- * idle, loading, or has an error.
- * <p>
- * REFRESH events can be used to drive a {@code SwipeRefreshLayout}, or START/END events
- * can be used to drive loading spinner items in your {@code RecyclerView}.
- *
- * @param type Type of load - START, END, or REFRESH.
- * @param state State of load - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
- * @param error Error, if in an error state, null otherwise.
- *
- * @see #retry()
- */
- void onLoadStateChanged(@NonNull LoadType type,
- @NonNull LoadState state, @Nullable Throwable error);
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- static boolean equalsHelper(@Nullable Object a, @Nullable Object b) {
- // (Because Objects.equals() is API 19+)
- //noinspection EqualsReplaceableByObjectsCall
- return a == b || (a != null && a.equals(b));
- }
-
- abstract static class LoadStateManager {
- @NonNull
- private LoadState mRefresh = LoadState.IDLE;
- @Nullable
- private Throwable mRefreshError = null;
- @NonNull
- private LoadState mStart = LoadState.IDLE;
- @Nullable
- private Throwable mStartError = null;
- @NonNull
- private LoadState mEnd = LoadState.IDLE;
- @Nullable
- private Throwable mEndError = null;
-
- @NonNull
- public LoadState getRefresh() {
- return mRefresh;
- }
-
- @NonNull
- public LoadState getStart() {
- return mStart;
- }
-
- @NonNull
- public LoadState getEnd() {
- return mEnd;
- }
-
- void setState(@NonNull LoadType type, @NonNull LoadState state, @Nullable Throwable error) {
- boolean expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR;
- boolean hasError = error != null;
- if (expectError != hasError) {
- throw new IllegalArgumentException(
- "Error states must be accompanied by a throwable, other states must not");
- }
-
- // deduplicate signals
- switch (type) {
- case REFRESH:
- if (mRefresh.equals(state) && equalsHelper(mRefreshError, error)) return;
- mRefresh = state;
- mRefreshError = error;
- break;
- case START:
- if (mStart.equals(state) && equalsHelper(mStartError, error)) return;
- mStart = state;
- mStartError = error;
- break;
- case END:
- if (mEnd.equals(state) && equalsHelper(mEndError, error)) return;
- mEnd = state;
- mEndError = error;
- break;
- }
- onStateChanged(type, state, error);
- }
-
- protected abstract void onStateChanged(@NonNull LoadType type,
- @NonNull LoadState state, @Nullable Throwable error);
-
- void dispatchCurrentLoadState(LoadStateListener listener) {
- listener.onLoadStateChanged(PagedList.LoadType.REFRESH, mRefresh, mRefreshError);
- listener.onLoadStateChanged(PagedList.LoadType.START, mStart, mStartError);
- listener.onLoadStateChanged(PagedList.LoadType.END, mEnd, mEndError);
- }
- }
-
- void setInitialLoadState(@NonNull LoadState loadState, @Nullable Throwable error) {}
-
- /**
- * Retry any retryable errors associated with this PagedList.
- * <p>
- * If for example a network DataSource append timed out, calling this method will retry the
- * failed append load. Note that your DataSource will need to pass {@code true} to
- * {@code onError()} to signify the error as retryable.
- * <p>
- * You can observe loading state via {@link #addWeakLoadStateListener(LoadStateListener)},
- * though generally this is done through the {@link androidx.paging.PagedListAdapter} or
- * {@link androidx.paging.AsyncPagedListDiffer}.
- *
- * @see #addWeakLoadStateListener(LoadStateListener)
- * @see #removeWeakLoadStateListener(LoadStateListener)
- */
- public void retry() {}
-
- @NonNull
- final Executor mMainThreadExecutor;
- @NonNull
- final Executor mBackgroundThreadExecutor;
- @Nullable
- final BoundaryCallback<T> mBoundaryCallback;
- @NonNull
- final Config mConfig;
- @NonNull
- final PagedStorage<T> mStorage;
- @Nullable
- Runnable mRefreshRetryCallback = null;
-
- void setRetryCallback(@Nullable Runnable refreshRetryCallback) {
- mRefreshRetryCallback = refreshRetryCallback;
- }
-
- /**
- * Last access location, in total position space (including offset).
- * <p>
- * Used by positional data
- * sources to initialize loading near viewport
- */
- int mLastLoad = 0;
- T mLastItem = null;
-
- final int mRequiredRemainder;
-
- // if set to true, mBoundaryCallback is non-null, and should
- // be dispatched when nearby load has occurred
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- boolean mBoundaryCallbackBeginDeferred = false;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- boolean mBoundaryCallbackEndDeferred = false;
-
- // lowest and highest index accessed by loadAround. Used to
- // decide when mBoundaryCallback should be dispatched
- private int mLowestIndexAccessed = Integer.MAX_VALUE;
- private int mHighestIndexAccessed = Integer.MIN_VALUE;
-
- private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final ArrayList<WeakReference<LoadStateListener>> mListeners = new ArrayList<>();
-
- void dispatchStateChange(@NonNull LoadType type, @NonNull LoadState state,
- @Nullable Throwable error) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null) {
- mListeners.remove(i);
- } else {
- currentListener.onLoadStateChanged(type, state, error);
- }
- }
- }
-
- PagedList(@NonNull PagedStorage<T> storage,
- @NonNull Executor mainThreadExecutor,
- @NonNull Executor backgroundThreadExecutor,
- @Nullable BoundaryCallback<T> boundaryCallback,
- @NonNull Config config) {
- mStorage = storage;
- mMainThreadExecutor = mainThreadExecutor;
- mBackgroundThreadExecutor = backgroundThreadExecutor;
- mBoundaryCallback = boundaryCallback;
- mConfig = config;
- mRequiredRemainder = mConfig.prefetchDistance * 2 + mConfig.pageSize;
- }
-
- /**
- * Create a PagedList which loads data from the provided data source on a background thread,
- * posting updates to the main thread.
- *
- *
- * @param dataSource DataSource providing data to the PagedList
- * @param notifyExecutor Thread tat will use and consume data from the PagedList.
- * Generally, this is the UI/main thread.
- * @param fetchExecutor Data loading will be done via this executor -
- * should be a background thread.
- * @param boundaryCallback Optional boundary callback to attach to the list.
- * @param config PagedList Config, which defines how the PagedList will load data.
- * @param <K> Key type that indicates to the DataSource what data to load.
- * @param <T> Type of items to be held and loaded by the PagedList.
- *
- * @return ListenableFuture for newly created PagedList, which will page in data from the
- * DataSource as needed.
- */
- @NonNull
- static <K, T> ListenableFuture<PagedList<T>> create(
- @NonNull final DataSource<K, T> dataSource,
- @NonNull final Executor notifyExecutor,
- @NonNull final Executor fetchExecutor,
- @NonNull final Executor initialLoadExecutor,
- @Nullable final BoundaryCallback<T> boundaryCallback,
- @NonNull final Config config,
- @Nullable K key) {
- dataSource.initExecutor(initialLoadExecutor);
-
- final int lastLoad = (dataSource.mType == DataSource.KeyType.POSITIONAL && key != null)
- ? (Integer) key : ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
-
- return Futures.transform(
- dataSource.load(
- new DataSource.Params<>(
- DataSource.LoadType.INITIAL,
- key,
- config.initialLoadSizeHint,
- config.enablePlaceholders,
- config.pageSize)),
- new Function<DataSource.BaseResult<T>, PagedList<T>>() {
- @Override
- public PagedList<T> apply(DataSource.BaseResult<T> initialResult) {
- dataSource.initExecutor(fetchExecutor);
- return new ContiguousPagedList<>(dataSource,
- notifyExecutor,
- fetchExecutor,
- boundaryCallback,
- config,
- initialResult,
- lastLoad);
- }
- },
- DirectExecutor.INSTANCE);
- }
-
- /**
- * Builder class for PagedList.
- * <p>
- * DataSource, Config, main thread and background executor must all be provided.
- * <p>
- * A PagedList queries initial data from its DataSource during construction, to avoid empty
- * PagedLists being presented to the UI when possible. It's preferred to present initial data,
- * so that the UI doesn't show an empty list, or placeholders for a few frames, just before
- * showing initial content.
- * <p>
- * {@link androidx.paging.LivePagedListBuilder} does this creation on a background thread automatically, if you
- * want to receive a {@code LiveData<PagedList<...>>}.
- *
- * @param <Key> Type of key used to load data from the DataSource.
- * @param <Value> Type of items held and loaded by the PagedList.
- */
- @SuppressWarnings("WeakerAccess")
- public static final class Builder<Key, Value> {
- private final DataSource<Key, Value> mDataSource;
- private final Config mConfig;
- private Executor mNotifyExecutor;
- private Executor mFetchExecutor;
- private BoundaryCallback<Value> mBoundaryCallback;
- private Key mInitialKey;
-
- /**
- * Create a PagedList.Builder with the provided {@link DataSource} and {@link Config}.
- *
- * @param dataSource DataSource the PagedList will load from.
- * @param config Config that defines how the PagedList loads data from its DataSource.
- */
- public Builder(@NonNull DataSource<Key, Value> dataSource, @NonNull Config config) {
- //noinspection ConstantConditions
- if (dataSource == null) {
- throw new IllegalArgumentException("DataSource may not be null");
- }
- //noinspection ConstantConditions
- if (config == null) {
- throw new IllegalArgumentException("Config may not be null");
- }
- mDataSource = dataSource;
- mConfig = config;
- }
-
- /**
- * Create a PagedList.Builder with the provided {@link DataSource} and page size.
- * <p>
- * This method is a convenience for:
- * <pre>
- * PagedList.Builder(dataSource,
- * new PagedList.Config.Builder().setPageSize(pageSize).build());
- * </pre>
- *
- * @param dataSource DataSource the PagedList will load from.
- * @param pageSize Config that defines how the PagedList loads data from its DataSource.
- */
- public Builder(@NonNull DataSource<Key, Value> dataSource, int pageSize) {
- this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
- }
- /**
- * The executor defining where page loading updates are dispatched.
- *
- * @param notifyExecutor Executor that receives PagedList updates, and where
- * {@link Callback} calls are dispatched. Generally, this is the ui/main thread.
- * @return this
- */
- @NonNull
- public Builder<Key, Value> setNotifyExecutor(@NonNull Executor notifyExecutor) {
- mNotifyExecutor = notifyExecutor;
- return this;
- }
-
- /**
- * The executor used to fetch additional pages from the DataSource.
- *
- * Does not affect initial load, which will be done immediately on whichever thread the
- * PagedList is created on.
- *
- * @param fetchExecutor Executor used to fetch from DataSources, generally a background
- * thread pool for e.g. I/O or network loading.
- * @return this
- */
- @NonNull
- public Builder<Key, Value> setFetchExecutor(@NonNull Executor fetchExecutor) {
- mFetchExecutor = fetchExecutor;
- return this;
- }
-
- /**
- * The BoundaryCallback for out of data events.
- * <p>
- * Pass a BoundaryCallback to listen to when the PagedList runs out of data to load.
- *
- * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
- * @return this
- */
- @SuppressWarnings("unused")
- @NonNull
- public Builder<Key, Value> setBoundaryCallback(
- @Nullable BoundaryCallback<Value> boundaryCallback) {
- mBoundaryCallback = boundaryCallback;
- return this;
- }
-
- /**
- * Sets the initial key the DataSource should load around as part of initialization.
- *
- * @param initialKey Key the DataSource should load around as part of initialization.
- * @return this
- */
- @NonNull
- public Builder<Key, Value> setInitialKey(@Nullable Key initialKey) {
- mInitialKey = initialKey;
- return this;
- }
-
- /**
- * Creates a {@link PagedList} with the given parameters.
- * <p>
- * This call will dispatch the {@link androidx.paging.DataSource}'s loadInitial method immediately on the
- * current thread, and block the current on the result. This method should always be called
- * on a worker thread to prevent blocking the main thread.
- * <p>
- * It's fine to create a PagedList with an async DataSource on the main thread, such as in
- * the constructor of a ViewModel. An async network load won't block the initialLoad
- * function. For a synchronous DataSource such as one created from a Room database, a
- * {@code LiveData<PagedList>} can be safely constructed with {@link androidx.paging.LivePagedListBuilder}
- * on the main thread, since actual construction work is deferred, and done on a background
- * thread.
- * <p>
- * While build() will always return a PagedList, it's important to note that the PagedList
- * initial load may fail to acquire data from the DataSource. This can happen for example if
- * the DataSource is invalidated during its initial load. If this happens, the PagedList
- * will be immediately {@link PagedList#isDetached() detached}, and you can retry
- * construction (including setting a new DataSource).
- *
- * @deprecated This method has no means of handling errors encountered during initial load,
- * and blocks on the initial load result. Use {@link #buildAsync()} instead.
- *
- * @return The newly constructed PagedList
- */
- @Deprecated
- @WorkerThread
- @NonNull
- public PagedList<Value> build() {
- // TODO: define defaults, once they can be used in module without android dependency
- if (mNotifyExecutor == null) {
- throw new IllegalArgumentException("MainThreadExecutor required");
- }
- if (mFetchExecutor == null) {
- throw new IllegalArgumentException("BackgroundThreadExecutor required");
- }
-
- try {
- return create(DirectExecutor.INSTANCE).get();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Creates a {@link PagedList} asynchronously with the given parameters.
- * <p>
- * This call will dispatch the {@link DataSource}'s loadInitial method immediately, and
- * return a {@code ListenableFuture<PagedList<T>>} that will resolve (triggering listeners)
- * once the initial load is completed (success or failure).
- *
- * @return The newly constructed PagedList
- */
- @SuppressWarnings("unused")
- @NonNull
- public ListenableFuture<PagedList<Value>> buildAsync() {
- // TODO: define defaults, once they can be used in module without android dependency
- if (mNotifyExecutor == null) {
- throw new IllegalArgumentException("MainThreadExecutor required");
- }
- if (mFetchExecutor == null) {
- throw new IllegalArgumentException("BackgroundThreadExecutor required");
- }
-
- return create(mFetchExecutor);
- }
-
- private ListenableFuture<PagedList<Value>> create(@NonNull Executor initialFetchExecutor) {
- return PagedList.create(
- mDataSource,
- mNotifyExecutor,
- mFetchExecutor,
- initialFetchExecutor,
- mBoundaryCallback,
- mConfig,
- mInitialKey);
- }
- }
-
- /**
- * Get the item in the list of loaded items at the provided index.
- *
- * @param index Index in the loaded item list. Must be >= 0, and < {@link #size()}
- * @return The item at the passed index, or null if a null placeholder is at the specified
- * position.
- *
- * @see #size()
- */
- @Override
- @Nullable
- public T get(int index) {
- T item = mStorage.get(index);
- if (item != null) {
- mLastItem = item;
- }
- return item;
- }
-
- /**
- * Load adjacent items to passed index.
- *
- * @param index Index at which to load.
- */
- public void loadAround(int index) {
- if (index < 0 || index >= size()) {
- throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size());
- }
-
- mLastLoad = index + getPositionOffset();
- loadAroundInternal(index);
-
- mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
- mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
-
- /*
- * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
- * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
- * and accesses happen near the boundaries.
- *
- * Note: we post here, since RecyclerView may want to add items in response, and this
- * call occurs in PagedListAdapter bind.
- */
- tryDispatchBoundaryCallbacks(true);
- }
-
- // Creation thread for initial synchronous load, otherwise main thread
- // Safe to access main thread only state - no other thread has reference during construction
- @AnyThread
- void deferBoundaryCallbacks(final boolean deferEmpty,
- final boolean deferBegin, final boolean deferEnd) {
- if (mBoundaryCallback == null) {
- throw new IllegalStateException("Can't defer BoundaryCallback, no instance");
- }
-
- /*
- * If lowest/highest haven't been initialized, set them to storage size,
- * since placeholders must already be computed by this point.
- *
- * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately
- * if the initial load size is smaller than the prefetch window (see
- * TiledPagedListTest#boundaryCallback_immediate())
- */
- if (mLowestIndexAccessed == Integer.MAX_VALUE) {
- mLowestIndexAccessed = mStorage.size();
- }
- if (mHighestIndexAccessed == Integer.MIN_VALUE) {
- mHighestIndexAccessed = 0;
- }
-
- if (deferEmpty || deferBegin || deferEnd) {
- // Post to the main thread, since we may be on creation thread currently
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- // on is dispatched immediately, since items won't be accessed
- //noinspection ConstantConditions
- if (deferEmpty) {
- mBoundaryCallback.onZeroItemsLoaded();
- }
-
- // for other callbacks, mark deferred, and only dispatch if loadAround
- // has been called near to the position
- if (deferBegin) {
- mBoundaryCallbackBeginDeferred = true;
- }
- if (deferEnd) {
- mBoundaryCallbackEndDeferred = true;
- }
- tryDispatchBoundaryCallbacks(false);
- }
- });
- }
- }
-
- /**
- * Call this when mLowest/HighestIndexAccessed are changed, or
- * mBoundaryCallbackBegin/EndDeferred is set.
- */
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void tryDispatchBoundaryCallbacks(boolean post) {
- final boolean dispatchBegin = mBoundaryCallbackBeginDeferred
- && mLowestIndexAccessed <= mConfig.prefetchDistance;
- final boolean dispatchEnd = mBoundaryCallbackEndDeferred
- && mHighestIndexAccessed >= size() - 1 - mConfig.prefetchDistance;
-
- if (!dispatchBegin && !dispatchEnd) {
- return;
- }
-
- if (dispatchBegin) {
- mBoundaryCallbackBeginDeferred = false;
- }
- if (dispatchEnd) {
- mBoundaryCallbackEndDeferred = false;
- }
- if (post) {
- mMainThreadExecutor.execute(new Runnable() {
- @Override
- public void run() {
- dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
- }
- });
- } else {
- dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd);
- }
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void dispatchBoundaryCallbacks(boolean begin, boolean end) {
- // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present
- if (begin) {
- //noinspection ConstantConditions
- mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem());
- }
- if (end) {
- //noinspection ConstantConditions
- mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem());
- }
- }
-
- /** @hide */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
- void offsetAccessIndices(int offset) {
- // update last loadAround index
- mLastLoad += offset;
-
- // update access range
- mLowestIndexAccessed += offset;
- mHighestIndexAccessed += offset;
- }
-
- /**
- * Returns size of the list, including any not-yet-loaded null padding.
- *
- * To get the number of loaded items, not counting placeholders, use {@link #getLoadedCount()}.
- *
- * @return Current total size of the list, including placeholders.
- *
- * @see #getLoadedCount()
- */
- @Override
- public int size() {
- return mStorage.size();
- }
-
- /**
- * Returns the number of items loaded in the PagedList.
- *
- * Unlike {@link #size()} this counts only loaded items, not placeholders.
- * <p>
- * If placeholders are {@link Config#enablePlaceholders disabled}, this method is equivalent to
- * {@link #size()}.
- *
- * @return Number of items currently loaded, not counting placeholders.
- *
- * @see #size()
- */
- public int getLoadedCount() {
- return mStorage.getLoadedCount();
- }
-
- /**
- * Returns whether the list is immutable.
- *
- * Immutable lists may not become mutable again, and may safely be accessed from any thread.
- * <p>
- * In the future, this method may return true when a PagedList has completed loading from its
- * DataSource. Currently, it is equivalent to {@link #isDetached()}.
- *
- * @return True if the PagedList is immutable.
- */
- @SuppressWarnings("WeakerAccess")
- public boolean isImmutable() {
- return isDetached();
- }
-
- /**
- * Returns an immutable snapshot of the PagedList in its current state.
- *
- * If this PagedList {@link #isImmutable() is immutable} due to its DataSource being invalid, it
- * will be returned.
- *
- * @return Immutable snapshot of PagedList data.
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public List<T> snapshot() {
- if (isImmutable()) {
- return this;
- }
- return new SnapshotPagedList<>(this);
- }
-
- abstract boolean isContiguous();
-
- /**
- * Return the Config used to construct this PagedList.
- *
- * @return the Config of this PagedList
- */
- @NonNull
- public Config getConfig() {
- return mConfig;
- }
-
- /**
- * Return the DataSource that provides data to this PagedList.
- *
- * @return the DataSource of this PagedList.
- */
- @NonNull
- public abstract DataSource<?, T> getDataSource();
-
- /**
- * Return the key for the position passed most recently to {@link #loadAround(int)}.
- * <p>
- * When a PagedList is invalidated, you can pass the key returned by this function to initialize
- * the next PagedList. This ensures (depending on load times) that the next PagedList that
- * arrives will have data that overlaps. If you use androidx.paging.LivePagedListBuilder, it will do
- * this for you.
- *
- * @return Key of position most recently passed to {@link #loadAround(int)}.
- */
- @Nullable
- public abstract Object getLastKey();
-
- /**
- * True if the PagedList has detached the DataSource it was loading from, and will no longer
- * load new data.
- * <p>
- * A detached list is {@link #isImmutable() immutable}.
- *
- * @return True if the data source is detached.
- */
- public abstract boolean isDetached();
-
- /**
- * Detach the PagedList from its DataSource, and attempt to load no more data.
- * <p>
- * This is called automatically when a DataSource is observed to be invalid, which is a
- * signal to stop loading. The PagedList will continue to present existing data, but will not
- * initiate new loads.
- */
- @SuppressWarnings("unused")
- public abstract void detach();
-
- /**
- * Position offset of the data in the list.
- * <p>
- * If data is supplied by a {@link PositionalDataSource}, the item returned from
- * <code>get(i)</code> has a position of <code>i + getPositionOffset()</code>.
- * <p>
- * If the DataSource is a {@link ItemKeyedDataSource} or {@link PageKeyedDataSource}, it
- * doesn't use positions, returns 0.
- */
- public int getPositionOffset() {
- return mStorage.getPositionOffset();
- }
-
- /**
- * Add a LoadStateListener to observe the loading state of the PagedList.
- *
- * @param listener Listener to receive updates.
- *
- * @see #removeWeakLoadStateListener(LoadStateListener)
- */
- public void addWeakLoadStateListener(@NonNull LoadStateListener listener) {
- // first, clean up any empty weak refs
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null) {
- mListeners.remove(i);
- }
- }
-
- // then add the new one
- mListeners.add(new WeakReference<>(listener));
- dispatchCurrentLoadState(listener);
- }
-
- /**
- * Remove a previously registered LoadStateListener.
- *
- * @param listener Previously registered listener.
- * @see #addWeakLoadStateListener(LoadStateListener)
- */
- public void removeWeakLoadStateListener(@NonNull LoadStateListener listener) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- final LoadStateListener currentListener = mListeners.get(i).get();
- if (currentListener == null || currentListener == listener) {
- // found Listener, or empty weak ref
- mListeners.remove(i);
- }
- }
- }
-
- abstract void dispatchCurrentLoadState(LoadStateListener listener);
-
- /**
- * Adds a callback, and issues updates since the previousSnapshot was created.
- * <p>
- * If previousSnapshot is passed, the callback will also immediately be dispatched any
- * differences between the previous snapshot, and the current state. For example, if the
- * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls,
- * 12 items, 3 nulls, the callback would immediately receive a call of
- * <code>onChanged(14, 2)</code>.
- * <p>
- * This allows an observer that's currently presenting a snapshot to catch up to the most recent
- * version, including any changes that may have been made.
- * <p>
- * The callback is internally held as weak reference, so PagedList doesn't hold a strong
- * reference to its observer, such as a {@link androidx.paging.PagedListAdapter}. If an adapter were held with a
- * strong reference, it would be necessary to clear its PagedList observer before it could be
- * GC'd.
- *
- * @param previousSnapshot Snapshot previously captured from this List, or null.
- * @param callback Callback to dispatch to.
- *
- * @see #removeWeakCallback(Callback)
- */
- @SuppressWarnings("WeakerAccess")
- public void addWeakCallback(@Nullable List<T> previousSnapshot, @NonNull Callback callback) {
- if (previousSnapshot != null && previousSnapshot != this) {
-
- if (previousSnapshot.isEmpty()) {
- if (!mStorage.isEmpty()) {
- // If snapshot is empty, diff is trivial - just notify number new items.
- // Note: occurs in async init, when snapshot taken before init page arrives
- callback.onInserted(0, mStorage.size());
- }
- } else {
- PagedList<T> storageSnapshot = (PagedList<T>) previousSnapshot;
-
- //noinspection unchecked
- dispatchUpdatesSinceSnapshot(storageSnapshot, callback);
- }
- }
-
- // first, clean up any empty weak refs
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- final Callback currentCallback = mCallbacks.get(i).get();
- if (currentCallback == null) {
- mCallbacks.remove(i);
- }
- }
-
- // then add the new one
- mCallbacks.add(new WeakReference<>(callback));
- }
- /**
- * Removes a previously added callback.
- *
- * @param callback Callback, previously added.
- * @see #addWeakCallback(List, Callback)
- */
- @SuppressWarnings("WeakerAccess")
- public void removeWeakCallback(@NonNull Callback callback) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- final Callback currentCallback = mCallbacks.get(i).get();
- if (currentCallback == null || currentCallback == callback) {
- // found callback, or empty weak ref
- mCallbacks.remove(i);
- }
- }
- }
-
- void notifyInserted(int position, int count) {
- if (count != 0) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- final Callback callback = mCallbacks.get(i).get();
- if (callback != null) {
- callback.onInserted(position, count);
- }
- }
- }
- }
-
- void notifyChanged(int position, int count) {
- if (count != 0) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- final Callback callback = mCallbacks.get(i).get();
-
- if (callback != null) {
- callback.onChanged(position, count);
- }
- }
- }
- }
-
- void notifyRemoved(int position, int count) {
- if (count != 0) {
- for (int i = mCallbacks.size() - 1; i >= 0; i--) {
- final Callback callback = mCallbacks.get(i).get();
-
- if (callback != null) {
- callback.onRemoved(position, count);
- }
- }
- }
- }
-
- /**
- * Dispatch updates since the non-empty snapshot was taken.
- *
- * @param snapshot Non-empty snapshot.
- * @param callback Callback for updates that have occurred since snapshot.
- */
- abstract void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> snapshot,
- @NonNull Callback callback);
-
- abstract void loadAroundInternal(int index);
-
- /**
- * Callback signaling when content is loaded into the list.
- * <p>
- * Can be used to listen to items being paged in and out. These calls will be dispatched on
- * the executor defined by {@link Builder#setNotifyExecutor(Executor)}, which is generally
- * the main/UI thread.
- */
- public abstract static class Callback {
- /**
- * Called when null padding items have been loaded to signal newly available data, or when
- * data that hasn't been used in a while has been dropped, and swapped back to null.
- *
- * @param position Position of first newly loaded items, out of total number of items
- * (including padded nulls).
- * @param count Number of items loaded.
- */
- public abstract void onChanged(int position, int count);
-
- /**
- * Called when new items have been loaded at the end or beginning of the list.
- *
- * @param position Position of the first newly loaded item (in practice, either
- * <code>0</code> or <code>size - 1</code>.
- * @param count Number of items loaded.
- */
- public abstract void onInserted(int position, int count);
-
- /**
- * Called when items have been removed at the end or beginning of the list, and have not
- * been replaced by padded nulls.
- *
- * @param position Position of the first newly loaded item (in practice, either
- * <code>0</code> or <code>size - 1</code>.
- * @param count Number of items loaded.
- */
- @SuppressWarnings("unused")
- public abstract void onRemoved(int position, int count);
- }
-
- /**
- * Configures how a PagedList loads content from its DataSource.
- * <p>
- * Use a Config {@link Builder} to construct and define custom loading behavior, such as
- * {@link Builder#setPageSize(int)}, which defines number of items loaded at a time}.
- */
- public static class Config {
- /**
- * When {@link #maxSize} is set to {@code MAX_SIZE_UNBOUNDED}, the maximum number of items
- * loaded is unbounded, and pages will never be dropped.
- */
- @SuppressWarnings("WeakerAccess")
- public static final int MAX_SIZE_UNBOUNDED = Integer.MAX_VALUE;
-
- /**
- * Size of each page loaded by the PagedList.
- */
- public final int pageSize;
-
- /**
- * Prefetch distance which defines how far ahead to load.
- * <p>
- * If this value is set to 50, the paged list will attempt to load 50 items in advance of
- * data that's already been accessed.
- *
- * @see PagedList#loadAround(int)
- */
- @SuppressWarnings("WeakerAccess")
- public final int prefetchDistance;
-
- /**
- * Defines whether the PagedList may display null placeholders, if the DataSource provides
- * them.
- */
- @SuppressWarnings("WeakerAccess")
- public final boolean enablePlaceholders;
-
- /**
- * Defines the maximum number of items that may be loaded into this pagedList before pages
- * should be dropped.
- * <p>
- * {@link PageKeyedDataSource} does not currently support dropping pages - when
- * loading from a {@code PageKeyedDataSource}, this value is ignored.
- *
- * @see #MAX_SIZE_UNBOUNDED
- * @see Builder#setMaxSize(int)
- */
- public final int maxSize;
-
- /**
- * Size hint for initial load of PagedList, often larger than a regular page.
- */
- @SuppressWarnings("WeakerAccess")
- public final int initialLoadSizeHint;
-
- Config(int pageSize, int prefetchDistance,
- boolean enablePlaceholders, int initialLoadSizeHint, int maxSize) {
- this.pageSize = pageSize;
- this.prefetchDistance = prefetchDistance;
- this.enablePlaceholders = enablePlaceholders;
- this.initialLoadSizeHint = initialLoadSizeHint;
- this.maxSize = maxSize;
- }
-
- /**
- * Builder class for {@link Config}.
- * <p>
- * You must at minimum specify page size with {@link #setPageSize(int)}.
- */
- public static final class Builder {
- static final int DEFAULT_INITIAL_PAGE_MULTIPLIER = 3;
-
- private int mPageSize = -1;
- private int mPrefetchDistance = -1;
- private int mInitialLoadSizeHint = -1;
- private boolean mEnablePlaceholders = true;
- private int mMaxSize = MAX_SIZE_UNBOUNDED;
-
- /**
- * Defines the number of items loaded at once from the DataSource.
- * <p>
- * Should be several times the number of visible items onscreen.
- * <p>
- * Configuring your page size depends on how your data is being loaded and used. Smaller
- * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
- * improve loading throughput, to a point
- * (avoid loading more than 2MB from SQLite at once, since it incurs extra cost).
- * <p>
- * If you're loading data for very large, social-media style cards that take up most of
- * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
- * displaying dozens of items in a tiled grid, which can present items during a scroll
- * much more quickly, consider closer to 100.
- *
- * @param pageSize Number of items loaded at once from the DataSource.
- * @return this
- */
- @NonNull
- public Builder setPageSize(@IntRange(from = 1) int pageSize) {
- if (pageSize < 1) {
- throw new IllegalArgumentException("Page size must be a positive number");
- }
- mPageSize = pageSize;
- return this;
- }
-
- /**
- * Defines how far from the edge of loaded content an access must be to trigger further
- * loading.
- * <p>
- * Should be several times the number of visible items onscreen.
- * <p>
- * If not set, defaults to page size.
- * <p>
- * A value of 0 indicates that no list items will be loaded until they are specifically
- * requested. This is generally not recommended, so that users don't observe a
- * placeholder item (with placeholders) or end of list (without) while scrolling.
- *
- * @param prefetchDistance Distance the PagedList should prefetch.
- * @return this
- */
- @NonNull
- public Builder setPrefetchDistance(@IntRange(from = 0) int prefetchDistance) {
- mPrefetchDistance = prefetchDistance;
- return this;
- }
-
- /**
- * Pass false to disable null placeholders in PagedLists using this Config.
- * <p>
- * If not set, defaults to true.
- * <p>
- * A PagedList will present null placeholders for not-yet-loaded content if two
- * conditions are met:
- * <p>
- * 1) Its DataSource can count all unloaded items (so that the number of nulls to
- * present is known).
- * <p>
- * 2) placeholders are not disabled on the Config.
- * <p>
- * Call {@code setEnablePlaceholders(false)} to ensure the receiver of the PagedList
- * (often a {@link androidx.paging.PagedListAdapter}) doesn't need to account for null items.
- * <p>
- * If placeholders are disabled, not-yet-loaded content will not be present in the list.
- * Paging will still occur, but as items are loaded or removed, they will be signaled
- * as inserts to the {@link PagedList.Callback}.
- * {@link PagedList.Callback#onChanged(int, int)} will not be issued as part of loading,
- * though a {@link androidx.paging.PagedListAdapter} may still receive change events as a result of
- * PagedList diffing.
- *
- * @param enablePlaceholders False if null placeholders should be disabled.
- * @return this
- */
- @SuppressWarnings("SameParameterValue")
- @NonNull
- public Builder setEnablePlaceholders(boolean enablePlaceholders) {
- mEnablePlaceholders = enablePlaceholders;
- return this;
- }
-
- /**
- * Defines how many items to load when first load occurs.
- * <p>
- * This value is typically larger than page size, so on first load data there's a large
- * enough range of content loaded to cover small scrolls.
- * <p>
- * When using a {@link PositionalDataSource}, the initial load size will be coerced to
- * an integer multiple of pageSize, to enable efficient tiling.
- * <p>
- * If not set, defaults to three times page size.
- *
- * @param initialLoadSizeHint Number of items to load while initializing the PagedList.
- * @return this
- */
- @SuppressWarnings("WeakerAccess")
- @NonNull
- public Builder setInitialLoadSizeHint(@IntRange(from = 1) int initialLoadSizeHint) {
- mInitialLoadSizeHint = initialLoadSizeHint;
- return this;
- }
-
- /**
- * Defines how many items to keep loaded at once.
- * <p>
- * This can be used to cap the number of items kept in memory by dropping pages. This
- * value is typically many pages so old pages are cached in case the user scrolls back.
- * <p>
- * This value must be at least two times the
- * {@link #setPrefetchDistance(int)} prefetch distance} plus the
- * {@link #setPageSize(int) page size}). This constraint prevent loads from being
- * continuously fetched and discarded due to prefetching.
- * <p>
- * The max size specified here best effort, not a guarantee. In practice, if maxSize
- * is many times the page size, the number of items held by the PagedList will not grow
- * above this number. Exceptions are made as necessary to guarantee:
- * <ul>
- * <li>Pages are never dropped until there are more than two pages loaded. Note that
- * a DataSource may not be held strictly to
- * {@link Config#pageSize requested pageSize}, so two pages may be larger than
- * expected.
- * <li>Pages are never dropped if they are within a prefetch window (defined to be
- * {@code pageSize + (2 * prefetchDistance)}) of the most recent load.
- * </ul>
- * <p>
- * {@link PageKeyedDataSource} does not currently support dropping pages - when
- * loading from a {@code PageKeyedDataSource}, this value is ignored.
- * <p>
- * If not set, defaults to {@code MAX_SIZE_UNBOUNDED}, which disables page dropping.
- *
- * @param maxSize Maximum number of items to keep in memory, or
- * {@code MAX_SIZE_UNBOUNDED} to disable page dropping.
- * @return this
- *
- * @see Config#MAX_SIZE_UNBOUNDED
- * @see Config#maxSize
- */
- @NonNull
- public Builder setMaxSize(@IntRange(from = 2) int maxSize) {
- mMaxSize = maxSize;
- return this;
- }
-
- /**
- * Creates a {@link Config} with the given parameters.
- *
- * @return A new Config.
- */
- @NonNull
- public Config build() {
- if (mPrefetchDistance < 0) {
- mPrefetchDistance = mPageSize;
- }
- if (mInitialLoadSizeHint < 0) {
- mInitialLoadSizeHint = mPageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER;
- }
- if (!mEnablePlaceholders && mPrefetchDistance == 0) {
- throw new IllegalArgumentException("Placeholders and prefetch are the only ways"
- + " to trigger loading of more data in the PagedList, so either"
- + " placeholders must be enabled, or prefetch distance must be > 0.");
- }
- if (mMaxSize != MAX_SIZE_UNBOUNDED) {
- if (mMaxSize < mPageSize + mPrefetchDistance * 2) {
- throw new IllegalArgumentException("Maximum size must be at least"
- + " pageSize + 2*prefetchDist, pageSize=" + mPageSize
- + ", prefetchDist=" + mPrefetchDistance + ", maxSize=" + mMaxSize);
- }
- }
-
- return new Config(mPageSize, mPrefetchDistance,
- mEnablePlaceholders, mInitialLoadSizeHint, mMaxSize);
- }
- }
- }
-
- /**
- * Signals when a PagedList has reached the end of available data.
- * <p>
- * When local storage is a cache of network data, it's common to set up a streaming pipeline:
- * Network data is paged into the database, database is paged into UI. Paging from the database
- * to UI can be done with a {@code LiveData<PagedList>}, but it's still necessary to know when
- * to trigger network loads.
- * <p>
- * BoundaryCallback does this signaling - when a DataSource runs out of data at the end of
- * the list, {@link #onItemAtEndLoaded(Object)} is called, and you can start an async network
- * load that will write the result directly to the database. Because the database is being
- * observed, the UI bound to the {@code LiveData<PagedList>} will update automatically to
- * account for the new items.
- * <p>
- * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
- * {@link androidx.paging.LivePagedListBuilder#setBoundaryCallback}, the callbacks may be issued multiple
- * times. If for example {@link #onItemAtEndLoaded(Object)} triggers a network load, it should
- * avoid triggering it again while the load is ongoing.
- * <p>
- * The database + network Repository in the
- * <a href="https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md">PagingWithNetworkSample</a>
- * shows how to implement a network BoundaryCallback using
- * <a href="https://square.github.io/retrofit/">Retrofit</a>, while
- * handling swipe-to-refresh, network errors, and retry.
- * <h4>Requesting Network Data</h4>
- * BoundaryCallback only passes the item at front or end of the list when out of data. This
- * makes it an easy fit for item-keyed network requests, where you can use the item passed to
- * the BoundaryCallback to request more data from the network. In these cases, the source of
- * truth for next page to load is coming from local storage, based on what's already loaded.
- * <p>
- * If you aren't using an item-keyed network API, you may be using page-keyed, or page-indexed.
- * If this is the case, the paging library doesn't know about the page key or index used in the
- * BoundaryCallback, so you need to track it yourself. You can do this in one of two ways:
- * <h5>Local storage Page key</h5>
- * If you want to perfectly resume your query, even if the app is killed and resumed, you can
- * store the key on disk. Note that with a positional/page index network API, there's a simple
- * way to do this, by using the {@code listSize} as an input to the next load (or
- * {@code listSize / NETWORK_PAGE_SIZE}, for page indexing).
- * <p>
- * The current list size isn't passed to the BoundaryCallback though. This is because the
- * PagedList doesn't necessarily know the number of items in local storage. Placeholders may be
- * disabled, or the DataSource may not count total number of items.
- * <p>
- * Instead, for these positional cases, you can query the database for the number of items, and
- * pass that to the network.
- * <h5>In-Memory Page key</h5>
- * Often it doesn't make sense to query the next page from network if the last page you fetched
- * was loaded many hours or days before. If you keep the key in memory, you can refresh any time
- * you start paging from a network source.
- * <p>
- * Store the next key in memory, inside your BoundaryCallback. When you create a new
- * BoundaryCallback when creating a new {@code LiveData}/{@code Observable} of
- * {@code PagedList}, refresh data. For example,
- * <a href="https://codelabs.developers.google.com/codelabs/android-paging/index.html#8">in the
- * Paging Codelab</a>, the GitHub network page index is stored in memory.
- *
- * @param <T> Type loaded by the PagedList.
- */
- @MainThread
- public abstract static class BoundaryCallback<T> {
- /**
- * Called when zero items are returned from an initial load of the PagedList's data source.
- */
- public void onZeroItemsLoaded() {}
-
- /**
- * Called when the item at the front of the PagedList has been loaded, and access has
- * occurred within {@link Config#prefetchDistance} of it.
- * <p>
- * No more data will be prepended to the PagedList before this item.
- *
- * @param itemAtFront The first item of PagedList
- */
- public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
-
- /**
- * Called when the item at the end of the PagedList has been loaded, and access has
- * occurred within {@link Config#prefetchDistance} of it.
- * <p>
- * No more data will be appended to the PagedList after this item.
- *
- * @param itemAtEnd The first item of PagedList
- */
- public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PagedStorage.java b/paging/common/src/main/java/androidx/paging/PagedStorage.java
deleted file mode 100644
index 232302f..0000000
--- a/paging/common/src/main/java/androidx/paging/PagedStorage.java
+++ /dev/null
@@ -1,663 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class holding the pages of data backing a PagedList, presenting sparse loaded data as a List.
- * <p>
- * It has two modes of operation: contiguous and non-contiguous (tiled). This class only holds
- * data, and does not have any notion of the ideas of async loads, or prefetching.
- */
-final class PagedStorage<T> extends AbstractList<T> implements Pager.AdjacentProvider<T> {
- /**
- * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item
- * in that position is already loading. We use a singleton placeholder list that is distinct
- * from Collections.emptyList() for safety.
- */
- @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
- private static final List PLACEHOLDER_LIST = new ArrayList();
-
- private int mLeadingNullCount;
- /**
- * List of pages in storage.
- *
- * Two storage modes:
- *
- * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled().
- * Safe to access any item in any page.
- *
- * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true.
- * mPages may have nulls, or placeholder (empty) pages while content is loading.
- */
- private final ArrayList<List<T>> mPages;
- private int mTrailingNullCount;
-
- private int mPositionOffset;
- /**
- * Number of loaded items held by {@link #mPages}. When tiling, doesn't count unloaded pages in
- * {@link #mPages}. If tiling is disabled, same as {@link #mStorageCount}.
- *
- * This count is the one used for trimming.
- */
- private int mLoadedCount;
-
- /**
- * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in
- * {@link #mPages} may be null, but this value still counts them.
- */
- private int mStorageCount;
-
- // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set
- private int mPageSize;
-
- private int mNumberPrepended;
- private int mNumberAppended;
-
- PagedStorage() {
- mLeadingNullCount = 0;
- mPages = new ArrayList<>();
- mTrailingNullCount = 0;
- mPositionOffset = 0;
- mLoadedCount = 0;
- mStorageCount = 0;
- mPageSize = 1;
- mNumberPrepended = 0;
- mNumberAppended = 0;
- }
-
- PagedStorage(int leadingNulls, List<T> page, int trailingNulls) {
- this();
- init(leadingNulls, page, trailingNulls, 0);
- }
-
- private PagedStorage(PagedStorage<T> other) {
- mLeadingNullCount = other.mLeadingNullCount;
- mPages = new ArrayList<>(other.mPages);
- mTrailingNullCount = other.mTrailingNullCount;
- mPositionOffset = other.mPositionOffset;
- mLoadedCount = other.mLoadedCount;
- mStorageCount = other.mStorageCount;
- mPageSize = other.mPageSize;
- mNumberPrepended = other.mNumberPrepended;
- mNumberAppended = other.mNumberAppended;
- }
-
- PagedStorage<T> snapshot() {
- return new PagedStorage<>(this);
- }
-
- private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) {
- mLeadingNullCount = leadingNulls;
- mPages.clear();
- mPages.add(page);
- mTrailingNullCount = trailingNulls;
-
- mPositionOffset = positionOffset;
- mLoadedCount = page.size();
- mStorageCount = mLoadedCount;
-
- // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
- // even if it will break if nulls convert.
- mPageSize = page.size();
-
- mNumberPrepended = 0;
- mNumberAppended = 0;
- }
-
- void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset,
- @NonNull Callback callback) {
- init(leadingNulls, page, trailingNulls, positionOffset);
- callback.onInitialized(size());
- }
-
- @Override
- public T get(int i) {
- if (i < 0 || i >= size()) {
- throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size());
- }
-
- // is it definitely outside 'mPages'?
- int localIndex = i - mLeadingNullCount;
- if (localIndex < 0 || localIndex >= mStorageCount) {
- return null;
- }
-
- int localPageIndex;
- int pageInternalIndex;
-
- if (isTiled()) {
- // it's inside mPages, and we're tiled. Jump to correct tile.
- localPageIndex = localIndex / mPageSize;
- pageInternalIndex = localIndex % mPageSize;
- } else {
- // it's inside mPages, but page sizes aren't regular. Walk to correct tile.
- // Pages can only be null while tiled, so accessing page count is safe.
- pageInternalIndex = localIndex;
- final int localPageCount = mPages.size();
- for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) {
- int pageSize = mPages.get(localPageIndex).size();
- if (pageSize > pageInternalIndex) {
- // stop, found the page
- break;
- }
- pageInternalIndex -= pageSize;
- }
- }
-
- List<T> page = mPages.get(localPageIndex);
- if (page == null || page.size() == 0) {
- // can only occur in tiled case, with untouched inner/placeholder pages
- return null;
- }
- return page.get(pageInternalIndex);
- }
-
- /**
- * Returns true if all pages are the same size, except for the last, which may be smaller
- */
- boolean isTiled() {
- return mPageSize > 0;
- }
-
- int getLeadingNullCount() {
- return mLeadingNullCount;
- }
-
- int getTrailingNullCount() {
- return mTrailingNullCount;
- }
-
- int getStorageCount() {
- return mStorageCount;
- }
-
- int getNumberAppended() {
- return mNumberAppended;
- }
-
- int getNumberPrepended() {
- return mNumberPrepended;
- }
-
- int getPageCount() {
- return mPages.size();
- }
-
- int getLoadedCount() {
- return mLoadedCount;
- }
-
- interface Callback {
- void onInitialized(int count);
- void onPagePrepended(int leadingNulls, int changed, int added);
- void onPageAppended(int endPosition, int changed, int added);
- void onPagePlaceholderInserted(int pageIndex);
- void onPageInserted(int start, int count);
- void onPagesRemoved(int startOfDrops, int count);
- void onPagesSwappedToPlaceholder(int startOfDrops, int count);
- }
-
- int getPositionOffset() {
- return mPositionOffset;
- }
-
- int getMiddleOfLoadedRange() {
- return mLeadingNullCount + mPositionOffset + mStorageCount / 2;
- }
-
- @Override
- public int size() {
- return mLeadingNullCount + mStorageCount + mTrailingNullCount;
- }
-
- int computeLeadingNulls() {
- int total = mLeadingNullCount;
- final int pageCount = mPages.size();
- for (int i = 0; i < pageCount; i++) {
- List page = mPages.get(i);
- if (page != null && page != PLACEHOLDER_LIST) {
- break;
- }
- total += mPageSize;
- }
- return total;
- }
-
- int computeTrailingNulls() {
- int total = mTrailingNullCount;
- for (int i = mPages.size() - 1; i >= 0; i--) {
- List page = mPages.get(i);
- if (page != null && page != PLACEHOLDER_LIST) {
- break;
- }
- total += mPageSize;
- }
- return total;
- }
-
- // ---------------- Trimming API -------------------
- // Trimming is always done at the beginning or end of the list, as content is loaded.
- // In addition to trimming pages in the storage, we also support pre-trimming pages (dropping
- // them just before they're added) to avoid dispatching an add followed immediately by a trim.
- //
- // Note - we avoid trimming down to a single page to reduce chances of dropping page in
- // viewport, since we don't strictly know the viewport. If trim is aggressively set to size of a
- // single page, trimming while the user can see a page boundary is dangerous. To be safe, we
- // just avoid trimming in these cases entirely.
-
- private boolean needsTrim(int maxSize, int requiredRemaining, int localPageIndex) {
- List<T> page = mPages.get(localPageIndex);
- return page == null || (mLoadedCount > maxSize
- && mPages.size() > 2
- && page != PLACEHOLDER_LIST
- && mLoadedCount - page.size() >= requiredRemaining);
- }
-
- boolean needsTrimFromFront(int maxSize, int requiredRemaining) {
- return needsTrim(maxSize, requiredRemaining, 0);
- }
-
- boolean needsTrimFromEnd(int maxSize, int requiredRemaining) {
- return needsTrim(maxSize, requiredRemaining, mPages.size() - 1);
- }
-
- boolean shouldPreTrimNewPage(int maxSize, int requiredRemaining, int countToBeAdded) {
- return mLoadedCount + countToBeAdded > maxSize
- && mPages.size() > 1
- && mLoadedCount >= requiredRemaining;
- }
-
- boolean trimFromFront(boolean insertNulls, int maxSize, int requiredRemaining,
- @NonNull Callback callback) {
- int totalRemoved = 0;
- while (needsTrimFromFront(maxSize, requiredRemaining)) {
- List page = mPages.remove(0);
- int removed = (page == null) ? mPageSize : page.size();
- totalRemoved += removed;
- mStorageCount -= removed;
- mLoadedCount -= (page == null) ? 0 : page.size();
- }
-
- if (totalRemoved > 0) {
- if (insertNulls) {
- // replace removed items with nulls
- int previousLeadingNulls = mLeadingNullCount;
- mLeadingNullCount += totalRemoved;
- callback.onPagesSwappedToPlaceholder(previousLeadingNulls, totalRemoved);
- } else {
- // simply remove, and handle offset
- mPositionOffset += totalRemoved;
- callback.onPagesRemoved(mLeadingNullCount, totalRemoved);
- }
- }
- return totalRemoved > 0;
- }
-
- boolean trimFromEnd(boolean insertNulls, int maxSize, int requiredRemaining,
- @NonNull Callback callback) {
- int totalRemoved = 0;
- while (needsTrimFromEnd(maxSize, requiredRemaining)) {
- List page = mPages.remove(mPages.size() - 1);
- int removed = (page == null) ? mPageSize : page.size();
- totalRemoved += removed;
- mStorageCount -= removed;
- mLoadedCount -= (page == null) ? 0 : page.size();
- }
-
- if (totalRemoved > 0) {
- int newEndPosition = mLeadingNullCount + mStorageCount;
- if (insertNulls) {
- // replace removed items with nulls
- mTrailingNullCount += totalRemoved;
- callback.onPagesSwappedToPlaceholder(newEndPosition, totalRemoved);
- } else {
- // items were just removed, signal
- callback.onPagesRemoved(newEndPosition, totalRemoved);
- }
- }
- return totalRemoved > 0;
- }
-
- // ---------------- Contiguous API -------------------
-
- void prependPage(@NonNull List<T> page, @NonNull Callback callback) {
- final int count = page.size();
- if (count == 0) {
- // Nothing returned from source, nothing to do
- return;
- }
- if (mPageSize > 0 && count != mPageSize) {
- if (mPages.size() == 1 && count > mPageSize) {
- // prepending to a single item - update current page size to that of 'inner' page
- mPageSize = count;
- } else {
- // no longer tiled
- mPageSize = -1;
- }
- }
-
- mPages.add(0, page);
- mLoadedCount += count;
- mStorageCount += count;
-
- final int changedCount = Math.min(mLeadingNullCount, count);
- final int addedCount = count - changedCount;
-
- if (changedCount != 0) {
- mLeadingNullCount -= changedCount;
- }
- mPositionOffset -= addedCount;
- mNumberPrepended += count;
-
- callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount);
- }
-
- void appendPage(@NonNull List<T> page, @NonNull Callback callback) {
- final int count = page.size();
- if (count == 0) {
- // Nothing returned from source, nothing to do
- return;
- }
-
- if (mPageSize > 0) {
- // if the previous page was smaller than mPageSize,
- // or if this page is larger than the previous, disable tiling
- if (mPages.get(mPages.size() - 1).size() != mPageSize
- || count > mPageSize) {
- mPageSize = -1;
- }
- }
-
- mPages.add(page);
- mLoadedCount += count;
- mStorageCount += count;
-
- final int changedCount = Math.min(mTrailingNullCount, count);
- final int addedCount = count - changedCount;
-
- if (changedCount != 0) {
- mTrailingNullCount -= changedCount;
- }
- mNumberAppended += count;
- callback.onPageAppended(mLeadingNullCount + mStorageCount - count,
- changedCount, addedCount);
- }
-
- // ------------- Adjacent Provider interface (contiguous-only) ------------------
-
- @Override
- public T getFirstLoadedItem() {
- // safe to access first page's first item here:
- // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
- return mPages.get(0).get(0);
- }
-
- @Override
- public T getLastLoadedItem() {
- // safe to access last page's last item here:
- // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty
- List<T> page = mPages.get(mPages.size() - 1);
- return page.get(page.size() - 1);
- }
-
- @Override
- public int getFirstLoadedItemIndex() {
- return getLeadingNullCount() + getPositionOffset();
- }
-
- @Override
- public int getLastLoadedItemIndex() {
- return getLeadingNullCount() + getStorageCount() - 1 + getPositionOffset();
- }
-
- @Override
- public void onPageResultResolution(@NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<T> pageResult) {
- // ignored
- }
-
- // ------------------ Non-Contiguous API (tiling required) ----------------------
-
- /**
- * Return true if the page at the passed position would be the first (if trimFromFront) or last
- * page that's currently loading.
- */
- boolean pageWouldBeBoundary(int positionOfPage, boolean trimFromFront) {
- if (mPageSize < 1 || mPages.size() < 2) {
- throw new IllegalStateException("Trimming attempt before sufficient load");
- }
-
- if (positionOfPage < mLeadingNullCount) {
- // position represent page in leading nulls
- return trimFromFront;
- }
-
- if (positionOfPage >= mLeadingNullCount + mStorageCount) {
- // position represent page in trailing nulls
- return !trimFromFront;
- }
-
- int localPageIndex = (positionOfPage - mLeadingNullCount) / mPageSize;
-
- // walk outside in, return false if we find non-placeholder page before localPageIndex
- if (trimFromFront) {
- for (int i = 0; i < localPageIndex; i++) {
- if (mPages.get(i) != null) {
- return false;
- }
- }
- } else {
- for (int i = mPages.size() - 1; i > localPageIndex; i--) {
- if (mPages.get(i) != null) {
- return false;
- }
- }
- }
-
- // didn't find another page, so this one would be a boundary
- return true;
- }
-
- void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
- int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
- int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize;
- for (int i = 0; i < pageCount; i++) {
- int beginInclusive = i * pageSize;
- int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize);
-
- List<T> sublist = multiPageList.subList(beginInclusive, endExclusive);
-
- if (i == 0) {
- // Trailing nulls for first page includes other pages in multiPageList
- int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size();
- init(leadingNulls, sublist, initialTrailingNulls, positionOffset);
- } else {
- int insertPosition = leadingNulls + beginInclusive;
- insertPage(insertPosition, sublist, null);
- }
- }
- callback.onInitialized(size());
- }
-
- void tryInsertPageAndTrim(
- int position,
- @NonNull List<T> page,
- int lastLoad,
- int maxSize,
- int requiredRemaining,
- @NonNull Callback callback) {
- boolean trim = maxSize != PagedList.Config.MAX_SIZE_UNBOUNDED;
- boolean trimFromFront = lastLoad > getMiddleOfLoadedRange();
-
- boolean pageInserted = !trim
- || !shouldPreTrimNewPage(maxSize, requiredRemaining, page.size())
- || !pageWouldBeBoundary(position, trimFromFront);
-
- if (pageInserted) {
- insertPage(position, page, callback);
- } else {
- // trim would have us drop the page we just loaded - swap it to null
- int localPageIndex = (position - mLeadingNullCount) / mPageSize;
- mPages.set(localPageIndex, null);
-
- // note: we also remove it, so we don't have to guess how large a 'null' page is later
- mStorageCount -= page.size();
- if (trimFromFront) {
- mPages.remove(0);
- mLeadingNullCount += page.size();
- } else {
- mPages.remove(mPages.size() - 1);
- mTrailingNullCount += page.size();
- }
- }
-
- if (trim) {
- if (trimFromFront) {
- trimFromFront(true, maxSize, requiredRemaining, callback);
- } else {
- trimFromEnd(true, maxSize, requiredRemaining, callback);
- }
- }
- }
-
- public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) {
- final int newPageSize = page.size();
- if (newPageSize != mPageSize) {
- // differing page size is OK in 2 cases, when the page is being added:
- // 1) to the end (in which case, ignore new smaller size)
- // 2) only the last page has been added so far (in which case, adopt new bigger size)
-
- int size = size();
- boolean addingLastPage = position == (size - size % mPageSize)
- && newPageSize < mPageSize;
- boolean == 0 && mPages.size() == 1
- && newPageSize > mPageSize;
-
- // OK only if existing single page, and it's the last one
- if (!onlyEndPagePresent && !addingLastPage) {
- throw new IllegalArgumentException("page introduces incorrect tiling");
- }
- if (onlyEndPagePresent) {
- mPageSize = newPageSize;
- }
- }
-
- int pageIndex = position / mPageSize;
-
- allocatePageRange(pageIndex, pageIndex);
-
- int localPageIndex = pageIndex - mLeadingNullCount / mPageSize;
-
- List<T> oldPage = mPages.get(localPageIndex);
- if (oldPage != null && oldPage != PLACEHOLDER_LIST) {
- throw new IllegalArgumentException(
- "Invalid position " + position + ": data already loaded");
- }
- mPages.set(localPageIndex, page);
- mLoadedCount += newPageSize;
- if (callback != null) {
- callback.onPageInserted(position, newPageSize);
- }
- }
-
- void allocatePageRange(final int minimumPage, final int maximumPage) {
- int leadingNullPages = mLeadingNullCount / mPageSize;
-
- if (minimumPage < leadingNullPages) {
- for (int i = 0; i < leadingNullPages - minimumPage; i++) {
- mPages.add(0, null);
- }
- int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize;
- mStorageCount += newStorageAllocated;
- mLeadingNullCount -= newStorageAllocated;
-
- leadingNullPages = minimumPage;
- }
- if (maximumPage >= leadingNullPages + mPages.size()) {
- int newStorageAllocated = Math.min(mTrailingNullCount,
- (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize);
- for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) {
- mPages.add(mPages.size(), null);
- }
- mStorageCount += newStorageAllocated;
- mTrailingNullCount -= newStorageAllocated;
- }
- }
-
- public void allocatePlaceholders(int index, int prefetchDistance,
- int pageSize, Callback callback) {
- if (pageSize != mPageSize) {
- if (pageSize < mPageSize) {
- throw new IllegalArgumentException("Page size cannot be reduced");
- }
- if (mPages.size() != 1 || mTrailingNullCount != 0) {
- // not in single, last page allocated case - can't change page size
- throw new IllegalArgumentException(
- "Page size can change only if last page is only one present");
- }
- mPageSize = pageSize;
- }
-
- final int maxPageCount = (size() + mPageSize - 1) / mPageSize;
- int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0);
- int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1);
-
- allocatePageRange(minimumPage, maximumPage);
- int leadingNullPages = mLeadingNullCount / mPageSize;
- for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) {
- int localPageIndex = pageIndex - leadingNullPages;
- if (mPages.get(localPageIndex) == null) {
- //noinspection unchecked
- mPages.set(localPageIndex, PLACEHOLDER_LIST);
- callback.onPagePlaceholderInserted(pageIndex);
- }
- }
- }
-
- public boolean hasPage(int pageSize, int index) {
- // NOTE: we pass pageSize here to avoid in case mPageSize
- // not fully initialized (when last page only one loaded)
- int leadingNullPages = mLeadingNullCount / pageSize;
-
- if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) {
- return false;
- }
-
- List<T> page = mPages.get(index - leadingNullPages);
-
- return page != null && page != PLACEHOLDER_LIST;
- }
-
- @Override
- public String toString() {
- StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount
- + ", storage " + mStorageCount
- + ", trailing " + getTrailingNullCount());
-
- for (int i = 0; i < mPages.size(); i++) {
- ret.append(" ").append(mPages.get(i));
- }
- return ret.toString();
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/Pager.java b/paging/common/src/main/java/androidx/paging/Pager.java
deleted file mode 100644
index 92ae848..0000000
--- a/paging/common/src/main/java/androidx/paging/Pager.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * 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.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.paging.futures.FutureCallback;
-import androidx.paging.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-class Pager<K, V> {
- @SuppressWarnings("unchecked")
- Pager(@NonNull PagedList.Config config,
- @NonNull DataSource<K, V> source,
- @NonNull Executor notifyExecutor,
- @NonNull Executor fetchExecutor,
- @NonNull PageConsumer<V> pageConsumer,
- @Nullable AdjacentProvider<V> adjacentProvider,
- @NonNull DataSource.BaseResult<V> result) {
- mConfig = config;
- mSource = source;
- mNotifyExecutor = notifyExecutor;
- mFetchExecutor = fetchExecutor;
- mPageConsumer = pageConsumer;
- if (adjacentProvider == null) {
- adjacentProvider = new SimpleAdjacentProvider<>();
- }
- mAdjacentProvider = adjacentProvider;
- mPrevKey = (K) result.prevKey;
- mNextKey = (K) result.nextKey;
- adjacentProvider.onPageResultResolution(PagedList.LoadType.REFRESH, result);
- mTotalCount = result.totalCount();
-
- // TODO: move this validation to tiled paging impl, once that's added back
- if (mSource.mType == DataSource.KeyType.POSITIONAL && mConfig.enablePlaceholders) {
- result.validateForInitialTiling(mConfig.pageSize);
- }
- }
-
- private final int mTotalCount;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final PagedList.Config mConfig;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final DataSource<K, V> mSource;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final Executor mNotifyExecutor;
-
- @NonNull
- private final Executor mFetchExecutor;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- @NonNull
- final PageConsumer<V> mPageConsumer;
-
- @NonNull
- private final AdjacentProvider<V> mAdjacentProvider;
-
- @Nullable
- private K mPrevKey;
-
- @Nullable
- private K mNextKey;
-
- private final AtomicBoolean mDetached = new AtomicBoolean(false);
-
- PagedList.LoadStateManager mLoadStateManager = new PagedList.LoadStateManager() {
- @Override
- protected void onStateChanged(@NonNull PagedList.LoadType type,
- @NonNull PagedList.LoadState state, @Nullable Throwable error) {
- mPageConsumer.onStateChanged(type, state, error);
- }
- };
-
- private void listenTo(@NonNull final PagedList.LoadType type,
- @NonNull final ListenableFuture<? extends DataSource.BaseResult<V>> future) {
- // First listen on the BG thread if the DataSource is invalid, since it can be expensive
- future.addListener(new Runnable() {
- @Override
- public void run() {
- // if invalid, drop result on the floor
- if (mSource.isInvalid()) {
- detach();
- return;
- }
-
- // Source has been verified to be valid after producing data, so sent data to UI
- Futures.addCallback(future, new FutureCallback<DataSource.BaseResult<V>>() {
- @Override
- public void onSuccess(DataSource.BaseResult<V> value) {
- onLoadSuccess(type, value);
- }
-
- @Override
- public void onError(@NonNull Throwable throwable) {
- onLoadError(type, throwable);
- }
- }, mNotifyExecutor);
- }
- }, mFetchExecutor);
- }
-
- interface PageConsumer<V> {
- // return true if we need to fetch more
- boolean onPageResult(
- @NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<V> pageResult);
-
- void onStateChanged(@NonNull PagedList.LoadType type,
- @NonNull PagedList.LoadState state, @Nullable Throwable error);
- }
-
- interface AdjacentProvider<V> {
- V getFirstLoadedItem();
-
- V getLastLoadedItem();
-
- int getFirstLoadedItemIndex();
-
- int getLastLoadedItemIndex();
-
- /**
- * Notify the AdjacentProvider of new loaded data, to update first/last item/index.
- *
- * NOTE: this data may not be committed (e.g. it may be dropped due to max size). Up to the
- * implementation of the AdjacentProvider to handle this (generally by ignoring this
- * call if dropping is supported).
- */
- void onPageResultResolution(
- @NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<V> result);
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- void onLoadSuccess(PagedList.LoadType type, DataSource.BaseResult<V> value) {
- if (isDetached()) {
- // abort!
- return;
- }
-
- mAdjacentProvider.onPageResultResolution(type, value);
-
- if (mPageConsumer.onPageResult(type, value)) {
- if (type.equals(PagedList.LoadType.START)) {
- //noinspection unchecked
- mPrevKey = (K) value.prevKey;
- schedulePrepend();
- } else if (type.equals(PagedList.LoadType.END)) {
- //noinspection unchecked
- mNextKey = (K) value.nextKey;
- scheduleAppend();
- } else {
- throw new IllegalStateException("Can only fetch more during append/prepend");
- }
- } else {
- PagedList.LoadState state =
- value.data.isEmpty() ? PagedList.LoadState.DONE : PagedList.LoadState.IDLE;
- mLoadStateManager.setState(type, state, null);
- }
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- void onLoadError(PagedList.LoadType type, Throwable throwable) {
- if (isDetached()) {
- // abort!
- return;
- }
- // TODO: handle nesting
- PagedList.LoadState state = mSource.isRetryableError(throwable)
- ? PagedList.LoadState.RETRYABLE_ERROR : PagedList.LoadState.ERROR;
- mLoadStateManager.setState(type, state, throwable);
- }
-
- public void trySchedulePrepend() {
- if (mLoadStateManager.getStart().equals(PagedList.LoadState.IDLE)) {
- schedulePrepend();
- }
- }
-
- public void tryScheduleAppend() {
- if (mLoadStateManager.getEnd().equals(PagedList.LoadState.IDLE)) {
- scheduleAppend();
- }
- }
-
- private boolean canPrepend() {
- if (mTotalCount == DataSource.BaseResult.TOTAL_COUNT_UNKNOWN) {
- // don't know count / position from initial load, so be conservative, return true
- return true;
- }
-
- // position is known, do we have space left?
- return mAdjacentProvider.getFirstLoadedItemIndex() > 0;
- }
-
- private boolean canAppend() {
- if (mTotalCount == DataSource.BaseResult.TOTAL_COUNT_UNKNOWN) {
- // don't know count / position from initial load, so be conservative, return true
- return true;
- }
-
- // count is known, do we have space left?
- return mAdjacentProvider.getLastLoadedItemIndex() < mTotalCount - 1;
- }
-
- private void schedulePrepend() {
- if (!canPrepend()) {
- onLoadSuccess(PagedList.LoadType.START, DataSource.BaseResult.<V>empty());
- return;
- }
- K key;
- switch(mSource.mType) {
- case POSITIONAL:
- //noinspection unchecked
- key = (K) ((Integer) (mAdjacentProvider.getFirstLoadedItemIndex() - 1));
- break;
- case PAGE_KEYED:
- key = mPrevKey;
- break;
- case ITEM_KEYED:
- key = mSource.getKey(mAdjacentProvider.getFirstLoadedItem());
- break;
- default:
- throw new IllegalArgumentException("unknown source type");
- }
- mLoadStateManager.setState(
- PagedList.LoadType.START, PagedList.LoadState.LOADING, null);
- listenTo(PagedList.LoadType.START, mSource.load(new DataSource.Params<>(
- DataSource.LoadType.START,
- key,
- mConfig.initialLoadSizeHint,
- mConfig.enablePlaceholders,
- mConfig.pageSize)));
- }
-
- private void scheduleAppend() {
- if (!canAppend()) {
- onLoadSuccess(PagedList.LoadType.END, DataSource.BaseResult.<V>empty());
- return;
- }
-
- K key;
- switch(mSource.mType) {
- case POSITIONAL:
- //noinspection unchecked
- key = (K) ((Integer) (mAdjacentProvider.getLastLoadedItemIndex() + 1));
- break;
- case PAGE_KEYED:
- key = mNextKey;
- break;
- case ITEM_KEYED:
- key = mSource.getKey(mAdjacentProvider.getLastLoadedItem());
- break;
- default:
- throw new IllegalArgumentException("unknown source type");
- }
-
- mLoadStateManager.setState(PagedList.LoadType.END, PagedList.LoadState.LOADING, null);
- listenTo(PagedList.LoadType.END, mSource.load(new DataSource.Params<>(
- DataSource.LoadType.END,
- key,
- mConfig.initialLoadSizeHint,
- mConfig.enablePlaceholders,
- mConfig.pageSize)));
- }
-
- void retry() {
- if (mLoadStateManager.getStart().equals(PagedList.LoadState.RETRYABLE_ERROR)) {
- schedulePrepend();
- }
- if (mLoadStateManager.getEnd().equals(PagedList.LoadState.RETRYABLE_ERROR)) {
- scheduleAppend();
- }
- }
-
- boolean isDetached() {
- return mDetached.get();
- }
-
- void detach() {
- mDetached.set(true);
- }
-
- static class SimpleAdjacentProvider<V> implements AdjacentProvider<V> {
- private int mFirstIndex;
- private int mLastIndex;
-
- private V mFirstItem;
- private V mLastItem;
-
- boolean mCounted;
- int mLeadingUnloadedCount;
- int mTrailingUnloadedCount;
-
- @Override
- public V getFirstLoadedItem() {
- return mFirstItem;
- }
-
- @Override
- public V getLastLoadedItem() {
- return mLastItem;
- }
-
- @Override
- public int getFirstLoadedItemIndex() {
- return mFirstIndex;
- }
-
- @Override
- public int getLastLoadedItemIndex() {
- return mLastIndex;
- }
-
- @Override
- public void onPageResultResolution(@NonNull PagedList.LoadType type,
- @NonNull DataSource.BaseResult<V> result) {
- if (result.data.isEmpty()) {
- return;
- }
- if (type == PagedList.LoadType.START) {
- mFirstIndex -= result.data.size();
- mFirstItem = result.data.get(0);
- if (mCounted) {
- mLeadingUnloadedCount -= result.data.size();
- }
- } else if (type == PagedList.LoadType.END) {
- mLastIndex += result.data.size();
- mLastItem = result.data.get(result.data.size() - 1);
- if (mCounted) {
- mTrailingUnloadedCount -= result.data.size();
- }
- } else {
- mFirstIndex = result.leadingNulls + result.offset;
- mLastIndex = mFirstIndex + result.data.size() - 1;
- mFirstItem = result.data.get(0);
- mLastItem = result.data.get(result.data.size() - 1);
-
- if (result.counted) {
- mCounted = true;
- mLeadingUnloadedCount = result.leadingNulls;
- mTrailingUnloadedCount = result.trailingNulls;
- }
- }
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java b/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
deleted file mode 100644
index 3cb0217..0000000
--- a/paging/common/src/main/java/androidx/paging/PositionalDataSource.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.WorkerThread;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
- * arbitrary page positions.
- * <p>
- * Extend PositionalDataSource if you can load pages of a requested size at arbitrary
- * positions, and provide a fixed item count. If your data source can't support loading arbitrary
- * requested page sizes (e.g. when network page size constraints are only known at runtime), either
- * use {@link PageKeyedDataSource} or {@link ItemKeyedDataSource}, or pass the initial result with
- * the two parameter {@link LoadInitialCallback#onResult(List, int)}.
- * <p>
- * Room can generate a Factory of PositionalDataSources for you:
- * <pre>
- * {@literal @}Dao
- * interface UserDao {
- * {@literal @}Query("SELECT * FROM user ORDER BY mAge DESC")
- * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
- * }</pre>
- *
- * @see ListenablePositionalDataSource
- *
- * @param <T> Type of items being loaded by the PositionalDataSource.
- */
-public abstract class PositionalDataSource<T> extends ListenablePositionalDataSource<T> {
-
- /**
- * Holder object for inputs to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- */
- public static class LoadInitialParams extends ListenablePositionalDataSource.LoadInitialParams {
- public LoadInitialParams(
- int requestedStartPosition,
- int requestedLoadSize,
- int pageSize,
- boolean placeholdersEnabled) {
- super(requestedStartPosition, requestedLoadSize, pageSize, placeholdersEnabled);
- }
- }
-
- /**
- * Holder object for inputs to {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
- */
- public static class LoadRangeParams extends ListenablePositionalDataSource.LoadRangeParams {
- public LoadRangeParams(int startPosition, int loadSize) {
- super(startPosition, loadSize);
- }
- }
-
- /**
- * Callback for {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}
- * to return data, position, and count.
- * <p>
- * A callback should be called only once, and may throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <T> Type of items being loaded.
- */
- public abstract static class LoadInitialCallback<T> {
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass the total size to the totalCount parameter. If placeholders are not
- * requested (when {@link LoadInitialParams#placeholdersEnabled} is false), you can instead
- * call {@link #onResult(List, int)}.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be loaded from this DataSource,
- * pass {@code N}.
- * @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial {@code data} parameter
- * as well as any items that can be loaded in front or behind of
- * {@code data}.
- */
- public abstract void onResult(@NonNull List<T> data, int position, int totalCount);
-
- /**
- * Called to pass initial load state from a DataSource without total count,
- * when placeholders aren't requested.
- * <p class="note"><strong>Note:</strong> This method can only be called when placeholders
- * are disabled ({@link LoadInitialParams#placeholdersEnabled} is false).
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * if position is known but total size is not. If placeholders are requested, call the three
- * parameter variant: {@link #onResult(List, int, int)}.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be provided by this DataSource,
- * pass {@code N}.
- */
- public abstract void onResult(@NonNull List<T> data, int position);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- /**
- * Callback for PositionalDataSource {@link #loadRange(LoadRangeParams, LoadRangeCallback)}
- * to return data.
- * <p>
- * A callback should be called only once, and may throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <T> Type of items being loaded.
- */
- public abstract static class LoadRangeCallback<T> {
- /**
- * Called to pass loaded data from {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
- *
- * @param data List of items loaded from the DataSource. Must be same size as requested,
- * unless at end of list.
- */
- public abstract void onResult(@NonNull List<T> data);
-
- /**
- * Called to report an error from a DataSource.
- * <p>
- * Call this method to report an error from
- * {@link #loadRange(LoadRangeParams, LoadRangeCallback)}.
- *
- * @param error The error that occurred during loading.
- */
- public void onError(@NonNull Throwable error) {
- // TODO: remove default implementation in 3.0
- throw new IllegalStateException(
- "You must implement onError if implementing your own load callback");
- }
- }
-
- @NonNull
- @Override
- public final ListenableFuture<InitialResult<T>> loadInitial(
- @NonNull final ListenablePositionalDataSource.LoadInitialParams params) {
- final ResolvableFuture<InitialResult<T>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- final LoadInitialParams newParams = new LoadInitialParams(
- params.requestedStartPosition,
- params.requestedLoadSize,
- params.pageSize,
- params.placeholdersEnabled);
- LoadInitialCallback<T> callback = new LoadInitialCallback<T>() {
- @Override
- public void onResult(@NonNull List<T> data, int position, int totalCount) {
- if (isInvalid()) {
- // NOTE: this isInvalid() check works around
- // https://issuetracker.google.com/issues/124511903
- future.set(new InitialResult<>(Collections.<T>emptyList(), 0, 0));
- } else {
- setFuture(newParams, new InitialResult<>(data, position, totalCount));
- }
- }
-
- @Override
- public void onResult(@NonNull List<T> data, int position) {
- if (isInvalid()) {
- // NOTE: this isInvalid() check works around
- // https://issuetracker.google.com/issues/124511903
- future.set(new InitialResult<>(Collections.<T>emptyList(), 0));
- } else {
- setFuture(newParams, new InitialResult<>(data, position));
- }
- }
-
- private void setFuture(
- @NonNull ListenablePositionalDataSource.LoadInitialParams params,
- @NonNull InitialResult<T> result) {
- if (params.placeholdersEnabled) {
- result.validateForInitialTiling(params.pageSize);
- }
- future.set(result);
- }
-
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadInitial(newParams,
- callback);
- }
- });
- return future;
- }
-
- @NonNull
- @Override
- public final ListenableFuture<RangeResult<T>> loadRange(
- final @NonNull ListenablePositionalDataSource.LoadRangeParams params) {
- final ResolvableFuture<RangeResult<T>> future = ResolvableFuture.create();
- getExecutor().execute(new Runnable() {
- @Override
- public void run() {
- LoadRangeCallback<T> callback = new LoadRangeCallback<T>() {
- @Override
- public void onResult(@NonNull List<T> data) {
- if (isInvalid()) {
- future.set(new RangeResult<>(Collections.<T>emptyList()));
- } else {
- future.set(new RangeResult<>(data));
- }
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- future.setException(error);
- }
- };
- loadRange(new LoadRangeParams(
- params.startPosition,
- params.loadSize),
- callback);
- }
- });
- return future;
- }
-
- /**
- * Load initial list data.
- * <p>
- * This method is called to load the initial page(s) from the DataSource.
- * <p>
- * Result list must be a multiple of pageSize to enable efficient tiling.
- *
- * @param params Parameters for initial load, including requested start position, load size, and
- * page size.
- * @param callback Callback that receives initial load data, including
- * position and total data set size.
- */
- @WorkerThread
- public abstract void loadInitial(
- @NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback<T> callback);
-
- /**
- * Called to load a range of data from the DataSource.
- * <p>
- * This method is called to load additional pages from the DataSource after the
- * LoadInitialCallback passed to dispatchLoadInitial has initialized a PagedList.
- * <p>
- * Unlike {@link #loadInitial(LoadInitialParams, LoadInitialCallback)}, this method must return
- * the number of items requested, at the position requested.
- *
- * @param params Parameters for load, including start position and load size.
- * @param callback Callback that receives loaded data.
- */
- @WorkerThread
- public abstract void loadRange(@NonNull LoadRangeParams params,
- @NonNull LoadRangeCallback<T> callback);
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- /**
- * Helper for computing an initial position in
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * The value computed by this function will do bounds checking, page alignment, and positioning
- * based on initial load size requested.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * class ItemDataSource extends PositionalDataSource<Item> {
- * private int computeCount() {
- * // actual count code here
- * }
- *
- * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
- * // actual load code here
- * }
- *
- * {@literal @}Override
- * public void loadInitial({@literal @}NonNull LoadInitialParams params,
- * {@literal @}NonNull LoadInitialCallback<Item> callback) {
- * int totalCount = computeCount();
- * int position = computeInitialLoadPosition(params, totalCount);
- * int loadSize = computeInitialLoadSize(params, position, totalCount);
- * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
- * }
- *
- * {@literal @}Override
- * public void loadRange({@literal @}NonNull LoadRangeParams params,
- * {@literal @}NonNull LoadRangeCallback<Item> callback) {
- * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
- * }
- * }</pre>
- *
- * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
- * including page size, and requested start/loadSize.
- * @param totalCount Total size of the data set.
- * @return Position to start loading at.
- *
- *
- * @see #computeInitialLoadSize(LoadInitialParams, int, int)
- */
- public static int computeInitialLoadPosition(
- @NonNull LoadInitialParams params, int totalCount) {
- return ListenablePositionalDataSource.computeInitialLoadPosition(params, totalCount);
- }
-
- /**
- * Helper for computing an initial load size in
- * {@link #loadInitial(LoadInitialParams, LoadInitialCallback)} when total data set size can be
- * computed ahead of loading.
- * <p>
- * This function takes the requested load size, and bounds checks it against the value returned
- * by
- * {@link #computeInitialLoadPosition(LoadInitialParams, int)}.
- * <p>
- * Example usage in a PositionalDataSource subclass:
- * <pre>
- * class ItemDataSource extends PositionalDataSource<Item> {
- * private int computeCount() {
- * // actual count code here
- * }
- *
- * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
- * // actual load code here
- * }
- *
- * {@literal @}Override
- * public void loadInitial({@literal @}NonNull LoadInitialParams params,
- * {@literal @}NonNull LoadInitialCallback<Item> callback) {
- * int totalCount = computeCount();
- * int position = computeInitialLoadPosition(params, totalCount);
- * int loadSize = computeInitialLoadSize(params, position, totalCount);
- * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
- * }
- *
- * {@literal @}Override
- * public void loadRange({@literal @}NonNull LoadRangeParams params,
- * {@literal @}NonNull LoadRangeCallback<Item> callback) {
- * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
- * }
- * }</pre>
- *
- * @param params Params passed to {@link #loadInitial(LoadInitialParams, LoadInitialCallback)},
- * including page size, and requested start/loadSize.
- * @param initialLoadPosition Value returned by
- * {@link #computeInitialLoadPosition(LoadInitialParams, int)}
- * @param totalCount Total size of the data set.
- * @return Number of items to load.
- *
- * @see #computeInitialLoadPosition(LoadInitialParams, int)
- */
- public static int computeInitialLoadSize(
- @NonNull LoadInitialParams params, int initialLoadPosition, int totalCount) {
- return ListenablePositionalDataSource.computeInitialLoadSize(params, initialLoadPosition,
- totalCount);
- }
-
- @NonNull
- @Override
- public final <V> PositionalDataSource<V> mapByPage(
- @NonNull Function<List<T>, List<V>> function) {
- return new WrapperPositionalDataSource<>(this, function);
- }
-
- @NonNull
- @Override
- public final <V> PositionalDataSource<V> map(@NonNull Function<T, V> function) {
- return mapByPage(createListFunction(function));
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java b/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
deleted file mode 100644
index a3e5a12..0000000
--- a/paging/common/src/main/java/androidx/paging/SnapshotPagedList.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-class SnapshotPagedList<T> extends PagedList<T> {
- private final boolean mContiguous;
- private final Object mLastKey;
- private final DataSource<?, T> mDataSource;
-
- SnapshotPagedList(@NonNull PagedList<T> pagedList) {
- super(pagedList.mStorage.snapshot(),
- pagedList.mMainThreadExecutor,
- pagedList.mBackgroundThreadExecutor,
- null,
- pagedList.mConfig);
- mDataSource = pagedList.getDataSource();
- mContiguous = pagedList.isContiguous();
- mLastLoad = pagedList.mLastLoad;
- mLastKey = pagedList.getLastKey();
- }
-
- @Override
- public boolean isImmutable() {
- return true;
- }
-
- @Override
- public void detach() {}
-
- @Override
- public boolean isDetached() {
- return true;
- }
-
- @Override
- boolean isContiguous() {
- return mContiguous;
- }
-
- @Nullable
- @Override
- public Object getLastKey() {
- return mLastKey;
- }
-
- @NonNull
- @Override
- public DataSource<?, T> getDataSource() {
- return mDataSource;
- }
-
- @Override
- void dispatchUpdatesSinceSnapshot(@NonNull PagedList<T> storageSnapshot,
- @NonNull Callback callback) {
- }
-
- @Override
- void dispatchCurrentLoadState(LoadStateListener listener) {
- }
-
- @Override
- void loadAroundInternal(int index) {
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/TiledDataSource.java b/paging/common/src/main/java/androidx/paging/TiledDataSource.java
deleted file mode 100644
index 7c45879..0000000
--- a/paging/common/src/main/java/androidx/paging/TiledDataSource.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.WorkerThread;
-
-import java.util.Collections;
-import java.util.List;
-
-// NOTE: Room 1.0 depends on this class, so it should not be removed until
-// we can require a version of Room that uses PositionalDataSource directly
-/**
- * @param <T> Type loaded by the TiledDataSource.
- *
- * @deprecated Use {@link PositionalDataSource}
- * @hide
- */
-@SuppressWarnings("DeprecatedIsStillUsed")
-@Deprecated
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
-public abstract class TiledDataSource<T> extends PositionalDataSource<T> {
-
- @WorkerThread
- public abstract int countItems();
-
- @Override
- boolean isContiguous() {
- return false;
- }
-
- @Nullable
- @WorkerThread
- public abstract List<T> loadRange(int startPosition, int count);
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- @NonNull LoadInitialCallback<T> callback) {
- int totalCount = countItems();
- if (totalCount == 0) {
- callback.onResult(Collections.<T>emptyList(), 0, 0);
- return;
- }
-
- // bound the size requested, based on known count
- final int firstLoadPosition = computeInitialLoadPosition(params, totalCount);
- final int firstLoadSize = computeInitialLoadSize(params, firstLoadPosition, totalCount);
-
- // convert from legacy behavior
- List<T> list = loadRange(firstLoadPosition, firstLoadSize);
- if (list != null && list.size() == firstLoadSize) {
- callback.onResult(list, firstLoadPosition, totalCount);
- } else {
- // null list, or size doesn't match request
- // The size check is a WAR for Room 1.0, subsequent versions do the check in Room
- invalidate();
- }
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- @NonNull LoadRangeCallback<T> callback) {
- List<T> list = loadRange(params.startPosition, params.loadSize);
- if (list != null) {
- callback.onResult(list);
- } else {
- invalidate();
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperDataSource.java
deleted file mode 100644
index d8c4df4..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperDataSource.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.util.Function;
-import androidx.paging.futures.DirectExecutor;
-import androidx.paging.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.IdentityHashMap;
-import java.util.List;
-
-/**
- * @param <Key> DataSource key type, same for original and wrapped.
- * @param <ValueFrom> Value type of original DataSource.
- * @param <ValueTo> Value type of new DataSource.
- */
-class WrapperDataSource<Key, ValueFrom, ValueTo> extends DataSource<Key, ValueTo> {
- private final DataSource<Key, ValueFrom> mSource;
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<ValueFrom>, List<ValueTo>> mListFunction;
-
-
- private final IdentityHashMap<ValueTo, Key> mKeyMap;
-
- WrapperDataSource(@NonNull DataSource<Key, ValueFrom> source,
- @NonNull Function<List<ValueFrom>, List<ValueTo>> listFunction) {
- super(source.mType);
- mSource = source;
- mListFunction = listFunction;
- mKeyMap = source.mType == KeyType.ITEM_KEYED ? new IdentityHashMap<ValueTo, Key>() : null;
- }
-
- @Override
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.addInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.removeInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void invalidate() {
- mSource.invalidate();
- }
-
- @Override
- public boolean isInvalid() {
- return mSource.isInvalid();
- }
-
- @Nullable
- @Override
- Key getKey(@NonNull ValueTo item) {
- if (mKeyMap != null) {
- synchronized (mKeyMap) {
- return mKeyMap.get(item);
- }
- }
- // positional / page-keyed
- return null;
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- void stashKeysIfNeeded(@NonNull List<ValueFrom> source, @NonNull List<ValueTo> dest) {
- if (mKeyMap != null) {
- synchronized (mKeyMap) {
- for (int i = 0; i < dest.size(); i++) {
- mKeyMap.put(dest.get(i), mSource.getKey(source.get(i)));
- }
- }
- }
- }
-
- @Override
- final ListenableFuture<? extends BaseResult> load(@NonNull Params params) {
- //noinspection unchecked
- return Futures.transform(
- mSource.load(params),
- new Function<BaseResult<ValueFrom>, BaseResult<ValueTo>>() {
- @Override
- public BaseResult<ValueTo> apply(BaseResult<ValueFrom> input) {
- BaseResult<ValueTo> result = new BaseResult<>(input, mListFunction);
- stashKeysIfNeeded(input.data, result.data);
- return result;
- }
- },
- DirectExecutor.INSTANCE);
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
deleted file mode 100644
index 3a1b5c9..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperItemKeyedDataSource.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.arch.core.util.Function;
-
-import java.util.IdentityHashMap;
-import java.util.List;
-
-class WrapperItemKeyedDataSource<K, A, B> extends ItemKeyedDataSource<K, B> {
- private final ItemKeyedDataSource<K, A> mSource;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<A>, List<B>> mListFunction;
-
- private final IdentityHashMap<B, K> mKeyMap = new IdentityHashMap<>();
-
- WrapperItemKeyedDataSource(ItemKeyedDataSource<K, A> source,
- Function<List<A>, List<B>> listFunction) {
- mSource = source;
- mListFunction = listFunction;
- }
-
- @Override
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.addInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.removeInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void invalidate() {
- mSource.invalidate();
- }
-
- @Override
- public boolean isInvalid() {
- return mSource.isInvalid();
- }
-
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- List<B> convertWithStashedKeys(List<A> source) {
- List<B> dest = convert(mListFunction, source);
- synchronized (mKeyMap) {
- // synchronize on mKeyMap, since multiple loads may occur simultaneously.
- // Note: manually sync avoids locking per-item (e.g. Collections.synchronizedMap)
- for (int i = 0; i < dest.size(); i++) {
- mKeyMap.put(dest.get(i), mSource.getKey(source.get(i)));
- }
- }
- return dest;
- }
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams<K> params,
- final @NonNull LoadInitialCallback<B> callback) {
- mSource.loadInitial(params, new LoadInitialCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data, int position, int totalCount) {
- callback.onResult(convertWithStashedKeys(data), position, totalCount);
- }
-
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convertWithStashedKeys(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadAfter(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<B> callback) {
- mSource.loadAfter(params, new LoadCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convertWithStashedKeys(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadBefore(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<B> callback) {
- mSource.loadBefore(params, new LoadCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convertWithStashedKeys(data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @NonNull
- @Override
- public K getKey(@NonNull B item) {
- synchronized (mKeyMap) {
- return mKeyMap.get(item);
- }
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
deleted file mode 100644
index 3ffc79d..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperPageKeyedDataSource.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.arch.core.util.Function;
-
-import java.util.List;
-
-class WrapperPageKeyedDataSource<K, A, B> extends PageKeyedDataSource<K, B> {
- private final PageKeyedDataSource<K, A> mSource;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<A>, List<B>> mListFunction;
-
- WrapperPageKeyedDataSource(PageKeyedDataSource<K, A> source,
- Function<List<A>, List<B>> listFunction) {
- mSource = source;
- mListFunction = listFunction;
- }
-
- @Override
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.addInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.removeInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void invalidate() {
- mSource.invalidate();
- }
-
- @Override
- public boolean isInvalid() {
- return mSource.isInvalid();
- }
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams<K> params,
- final @NonNull LoadInitialCallback<K, B> callback) {
- mSource.loadInitial(params, new LoadInitialCallback<K, A>() {
- @Override
- public void onResult(@NonNull List<A> data, int position, int totalCount,
- @Nullable K previousPageKey, @Nullable K nextPageKey) {
- callback.onResult(convert(mListFunction, data), position, totalCount,
- previousPageKey, nextPageKey);
- }
-
- @Override
- public void onResult(@NonNull List<A> data, @Nullable K previousPageKey,
- @Nullable K nextPageKey) {
- callback.onResult(convert(mListFunction, data), previousPageKey, nextPageKey);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadBefore(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<K, B> callback) {
- mSource.loadBefore(params, new LoadCallback<K, A>() {
- @Override
- public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
- callback.onResult(convert(mListFunction, data), adjacentPageKey);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadAfter(@NonNull LoadParams<K> params,
- final @NonNull LoadCallback<K, B> callback) {
- mSource.loadAfter(params, new LoadCallback<K, A>() {
- @Override
- public void onResult(@NonNull List<A> data, @Nullable K adjacentPageKey) {
- callback.onResult(convert(mListFunction, data), adjacentPageKey);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java b/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
deleted file mode 100644
index fdc2b9a..0000000
--- a/paging/common/src/main/java/androidx/paging/WrapperPositionalDataSource.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package androidx.paging;
-
-import androidx.annotation.NonNull;
-import androidx.arch.core.util.Function;
-
-import java.util.List;
-
-class WrapperPositionalDataSource<A, B> extends PositionalDataSource<B> {
- private final PositionalDataSource<A> mSource;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
- final Function<List<A>, List<B>> mListFunction;
-
- WrapperPositionalDataSource(PositionalDataSource<A> source,
- Function<List<A>, List<B>> listFunction) {
- mSource = source;
- mListFunction = listFunction;
- }
-
- @Override
- public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.addInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void removeInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
- mSource.removeInvalidatedCallback(onInvalidatedCallback);
- }
-
- @Override
- public void invalidate() {
- mSource.invalidate();
- }
-
- @Override
- public boolean isInvalid() {
- return mSource.isInvalid();
- }
-
- @Override
- public void loadInitial(@NonNull LoadInitialParams params,
- final @NonNull LoadInitialCallback<B> callback) {
- mSource.loadInitial(params, new LoadInitialCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data, int position, int totalCount) {
- callback.onResult(convert(mListFunction, data), position, totalCount);
- }
-
- @Override
- public void onResult(@NonNull List<A> data, int position) {
- callback.onResult(convert(mListFunction, data), position);
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-
- @Override
- public void loadRange(@NonNull LoadRangeParams params,
- final @NonNull LoadRangeCallback<B> callback) {
- mSource.loadRange(params, new LoadRangeCallback<A>() {
- @Override
- public void onResult(@NonNull List<A> data) {
- callback.onResult(convert(mListFunction, data));
- }
-
- @Override
- public void onError(@NonNull Throwable error) {
- callback.onError(error);
- }
- });
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/futures/DirectExecutor.java b/paging/common/src/main/java/androidx/paging/futures/DirectExecutor.java
deleted file mode 100644
index 46eeb29..0000000
--- a/paging/common/src/main/java/androidx/paging/futures/DirectExecutor.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import java.util.concurrent.Executor;
-
-/**
- * Executor that runs each task in the thread that invokes {@link Executor#execute execute}
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class DirectExecutor implements Executor {
- /**
- * Returns an {@link Executor} that runs each task in the thread that invokes {@link
- * Executor#execute execute}.
- *
- * <p>This instance is equivalent to:
- *
- * <pre>{@code
- * final class DirectExecutor implements Executor {
- * public void execute(Runnable r) {
- * r.run();
- * }
- * }
- * }</pre>
- */
- @NonNull
- public static DirectExecutor INSTANCE = new DirectExecutor();
-
- private DirectExecutor() {}
- @Override
- public void execute(@NonNull Runnable runnable) {
- runnable.run();
- }
-}
diff --git a/paging/common/src/main/java/androidx/paging/futures/FutureCallback.java b/paging/common/src/main/java/androidx/paging/futures/FutureCallback.java
deleted file mode 100644
index 7d4aa0d..0000000
--- a/paging/common/src/main/java/androidx/paging/futures/FutureCallback.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-/**
- * A callback for accepting the results of a {@link Future} computation asynchronously.
- *
- * <p>To attach to a {@link ListenableFuture} use {@link Futures#addCallback}.
- * @param <V> Type of the Future result.
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public interface FutureCallback<V> {
- /** Invoked with the result of the {@code Future} computation when it is successful. */
- @SuppressWarnings("UnknownNullness")
- void onSuccess(V value);
-
- /**
- * Invoked when a {@code Future} computation fails or is canceled.
- *
- * <p>If the future's {@link Future#get() get} method throws an {@link ExecutionException}, then
- * the cause is passed to this method. Any other thrown object is passed unaltered.
- */
- void onError(@NonNull Throwable throwable);
-}
diff --git a/paging/common/src/main/java/androidx/paging/futures/Futures.java b/paging/common/src/main/java/androidx/paging/futures/Futures.java
deleted file mode 100644
index 4ad52f8..0000000
--- a/paging/common/src/main/java/androidx/paging/futures/Futures.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * 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.futures;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.arch.core.util.Function;
-import androidx.concurrent.futures.ResolvableFuture;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class Futures {
- private Futures() {}
- /**
- * Registers separate success and failure callbacks to be run when the {@code Future}'s
- * computation is complete or, if the computation is already complete, immediately.
- *
- * <p>The callback is run on {@code executor}. There is no guaranteed ordering of execution of
- * callbacks, but any callback added through this method is guaranteed to be called once the
- * computation is complete.
- *
- * <p>Example:
- *
- * <pre>{@code
- * ListenableFuture<QueryResult> future = ...;
- * Executor e = ...
- * addCallback(future,
- * new FutureCallback<QueryResult>() {
- * public void onSuccess(QueryResult result) {
- * storeInCache(result);
- * }
- * public void onFailure(Throwable t) {
- * reportError(t);
- * }
- * }, e);
- * }</pre>
- *
- * <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases. See
- * the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
- * documentation. All its warnings about heavyweight listeners are also applicable to heavyweight
- * callbacks passed to this method.
- *
- * <p>For a more general interface to attach a completion listener to a {@code Future}, see {@link
- * ListenableFuture#addListener addListener}.
- *
- * @param future The future attach the callback to.
- * @param callback The callback to invoke when {@code future} is completed.
- * @param executor The executor to run {@code callback} when the future completes.
- */
- public static <V> void addCallback(@NonNull final ListenableFuture<V> future,
- @NonNull final FutureCallback<? super V> callback, @NonNull Executor executor) {
- future.addListener(new Runnable() {
- @Override
- public void run() {
- final V value;
- try {
- value = future.get();
- } catch (ExecutionException e) {
- callback.onError(e.getCause());
- return;
- } catch (Throwable e) {
- callback.onError(e);
- return;
- }
- callback.onSuccess(value);
- }
- }, executor);
- }
-
- /**
- * Returns a new {@code Future} whose result is derived from the result of the given {@code
- * Future}. If {@code input} fails, the returned {@code Future} fails with the same exception
- * (and the function is not invoked). Example usage:
- *
- * <pre>{@code
- * ListenableFuture<QueryResult> queryFuture = ...;
- * ListenableFuture<List<Row>> rowsFuture =
- * transform(queryFuture, QueryResult::getRows, executor);
- * }</pre>
- *
- * <p>When selecting an executor, note that {@code directExecutor} is dangerous in some cases.
- * See the discussion in the {@link ListenableFuture#addListener ListenableFuture.addListener}
- * documentation. All its warnings about heavyweight listeners are also applicable to
- * heavyweight functions passed to this method.
- *
- * <p>The returned {@code Future} attempts to keep its cancellation state in sync with that of
- * the input future. That is, if the returned {@code Future} is cancelled, it will attempt to
- * cancel the input, and if the input is cancelled, the returned {@code Future} will receive a
- * callback in which it will attempt to cancel itself.
- *
- * <p>An example use of this method is to convert a serializable object returned from an RPC
- * into a POJO.
- *
- * @param input The future to transform
- * @param function A Function to transform the results of the provided future to the results of
- * the returned future.
- * @param executor Executor to run the function in.
- * @return A future that holds result of the transformation.
- */
- @NonNull
- public static <I, O> ListenableFuture<O> transform(
- @NonNull final ListenableFuture<I> input,
- @NonNull final Function<? super I, ? extends O> function,
- @NonNull final Executor executor) {
- final ResolvableFuture<O> out = ResolvableFuture.create();
-
- // add success/error callback
- addCallback(input, new FutureCallback<I>() {
- @Override
- public void onSuccess(I value) {
- out.set(function.apply(value));
- }
-
- @Override
- public void onError(@NonNull Throwable throwable) {
- out.setException(throwable);
- }
- }, executor);
-
- // propagate output future's cancellation to input future
- addCallback(out, new FutureCallback<O>() {
- @Override
- public void onSuccess(O value) {}
-
- @Override
- public void onError(@NonNull Throwable throwable) {
- if (throwable instanceof CancellationException) {
- input.cancel(false);
- }
- }
- }, executor);
- return out;
- }
-}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
new file mode 100644
index 0000000..1574a84
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ContiguousPagedList.kt
@@ -0,0 +1,351 @@
+/*
+ * 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.annotation.MainThread
+import androidx.annotation.RestrictTo
+import java.util.concurrent.Executor
+
+/**
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+open class ContiguousPagedList<K : Any, V : Any>(
+ override val dataSource: DataSource<K, V>,
+ mainThreadExecutor: Executor,
+ backgroundThreadExecutor: Executor,
+ boundaryCallback: BoundaryCallback<V>?,
+ config: Config,
+ initialResult: DataSource.BaseResult<V>,
+ lastLoad: Int
+) : PagedList<V>(
+ PagedStorage<V>(),
+ mainThreadExecutor,
+ backgroundThreadExecutor,
+ boundaryCallback,
+ config
+), PagedStorage.Callback, Pager.PageConsumer<V> {
+ internal companion object {
+ internal const val LAST_LOAD_UNSPECIFIED = -1
+
+ internal fun getPrependItemsRequested(
+ prefetchDistance: Int,
+ index: Int,
+ leadingNulls: Int
+ ) = prefetchDistance - (index - leadingNulls)
+
+ internal fun getAppendItemsRequested(
+ prefetchDistance: Int,
+ index: Int,
+ itemsBeforeTrailingNulls: Int
+ ) = index + prefetchDistance + 1 - itemsBeforeTrailingNulls
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ var prependItemsRequested = 0
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ var appendItemsRequested = 0
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ var replacePagesWithNulls = false
+
+ @Suppress("MemberVisibilityCanBePrivate") /* synthetic access */
+ val shouldTrim: Boolean
+
+ private val pager: Pager<K, V>
+
+ override val isDetached
+ get() = pager.isDetached
+
+ override val isContiguous = true
+
+ override val lastKey
+ get() = when (dataSource.type) {
+ DataSource.KeyType.POSITIONAL -> {
+ @Suppress("UNCHECKED_CAST")
+ lastLoad as K
+ }
+ else -> lastItem?.let { dataSource.getKeyInternal(it) }
+ }
+
+ /**
+ * Given a page result, apply or drop it, and return whether more loading is needed.
+ */
+ override fun onPageResult(type: LoadType, pageResult: DataSource.BaseResult<V>): Boolean {
+ var continueLoading = false
+ val page = pageResult.data
+
+ // if we end up trimming, we trim from side that's furthest from most recent access
+ val trimFromFront = lastLoad > storage.middleOfLoadedRange
+
+ // is the new page big enough to warrant pre-trimming (i.e. dropping) it?
+ val skipNewPage = shouldTrim && storage.shouldPreTrimNewPage(
+ config.maxSize,
+ requiredRemainder,
+ page.size
+ )
+
+ if (type == LoadType.END) {
+ if (skipNewPage && !trimFromFront) {
+ // don't append this data, drop it
+ appendItemsRequested = 0
+ } else {
+ storage.appendPage(page, this@ContiguousPagedList)
+ appendItemsRequested -= page.size
+ if (appendItemsRequested > 0 && page.size != 0) {
+ continueLoading = true
+ }
+ }
+ } else if (type == LoadType.START) {
+ if (skipNewPage && trimFromFront) {
+ // don't append this data, drop it
+ prependItemsRequested = 0
+ } else {
+ storage.prependPage(page, this@ContiguousPagedList)
+ prependItemsRequested -= page.size
+ if (prependItemsRequested > 0 && page.size != 0) {
+ continueLoading = true
+ }
+ }
+ } else {
+ throw IllegalArgumentException("unexpected result type $type")
+ }
+
+ if (shouldTrim) {
+ // Try and trim, but only if the side being trimmed isn't actually fetching.
+ // For simplicity (both of impl here, and contract w/ DataSource) we don't
+ // allow fetches in same direction - this means reading the load state is safe.
+ if (trimFromFront) {
+ if (pager.loadStateManager.start != LoadState.LOADING) {
+ if (storage.trimFromFront(
+ replacePagesWithNulls,
+ config.maxSize,
+ requiredRemainder,
+ this@ContiguousPagedList
+ )
+ ) {
+ // trimmed from front, ensure we can fetch in that dir
+ pager.loadStateManager.setState(
+ LoadType.START,
+ LoadState.IDLE,
+ null
+ )
+ }
+ }
+ } else {
+ if (pager.loadStateManager.end != LoadState.LOADING) {
+ if (storage.trimFromEnd(
+ replacePagesWithNulls,
+ config.maxSize,
+ requiredRemainder,
+ this@ContiguousPagedList
+ )
+ ) {
+ pager.loadStateManager.setState(LoadType.END, LoadState.IDLE, null)
+ }
+ }
+ }
+ }
+
+ triggerBoundaryCallback(type, page)
+ return continueLoading
+ }
+
+ override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) =
+ dispatchStateChange(type, state, error)
+
+ private fun triggerBoundaryCallback(type: LoadType, page: List<V>) {
+ if (boundaryCallback != null) {
+ val deferEmpty = storage.size == 0
+ val deferBegin = (!deferEmpty && type == LoadType.START && page.isEmpty())
+ val deferEnd = (!deferEmpty && type == LoadType.END && page.isEmpty())
+ deferBoundaryCallbacks(deferEmpty, deferBegin, deferEnd)
+ }
+ }
+
+ override fun retry() {
+ super.retry()
+ pager.retry()
+
+ if (pager.loadStateManager.refresh == LoadState.RETRYABLE_ERROR) {
+ // Loading the next PagedList failed, signal the retry callback.
+ refreshRetryCallback?.run()
+ }
+ }
+
+ init {
+ this.lastLoad = lastLoad
+ pager = Pager(
+ config,
+ dataSource,
+ mainThreadExecutor,
+ backgroundThreadExecutor,
+ this,
+ storage,
+ initialResult
+ )
+
+ if (config.enablePlaceholders) {
+ // Placeholders enabled, pass raw data to storage init
+ storage.init(
+ initialResult.leadingNulls,
+ initialResult.data,
+ initialResult.trailingNulls,
+ initialResult.offset,
+ this
+ )
+ } else {
+ // If placeholder are disabled, avoid passing leading/trailing nulls,
+ // since DataSource may have passed them anyway
+ storage.init(
+ 0,
+ initialResult.data,
+ 0,
+ initialResult.offset + initialResult.leadingNulls,
+ this
+ )
+ }
+
+ shouldTrim =
+ dataSource.supportsPageDropping && config.maxSize != Config.MAX_SIZE_UNBOUNDED
+
+ if (this.lastLoad == LAST_LOAD_UNSPECIFIED) {
+ // Because the ContiguousPagedList wasn't initialized with a last load position,
+ // initialize it to the middle of the initial load
+ this.lastLoad = (initialResult.leadingNulls + initialResult.offset +
+ initialResult.data.size / 2)
+ }
+ triggerBoundaryCallback(LoadType.REFRESH, initialResult.data)
+ }
+
+ override fun dispatchCurrentLoadState(listener: LoadStateListener) =
+ pager.loadStateManager.dispatchCurrentLoadState(listener)
+
+ override fun setInitialLoadState(loadState: LoadState, error: Throwable?) =
+ pager.loadStateManager.setState(LoadType.REFRESH, loadState, error)
+
+ @MainThread
+ override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<V>, callback: Callback) {
+ val snapshotStorage = snapshot.storage
+
+ val newlyAppended = storage.numberAppended - snapshotStorage.numberAppended
+ val newlyPrepended = storage.numberPrepended - snapshotStorage.numberPrepended
+
+ val previousTrailing = snapshotStorage.trailingNullCount
+ val previousLeading = snapshotStorage.leadingNullCount
+
+ // Validate that the snapshot looks like a previous version of this list - if it's not,
+ // we can't be sure we'll dispatch callbacks safely
+ if (snapshotStorage.isEmpty() ||
+ newlyAppended < 0 ||
+ newlyPrepended < 0 ||
+ storage.trailingNullCount != maxOf(previousTrailing - newlyAppended, 0) ||
+ storage.leadingNullCount != maxOf(previousLeading - newlyPrepended, 0) ||
+ storage.storageCount != snapshotStorage.storageCount + newlyAppended + newlyPrepended
+ ) {
+ throw IllegalArgumentException(
+ "Invalid snapshot provided - doesn't appear to be a snapshot of this PagedList"
+ )
+ }
+
+ if (newlyAppended != 0) {
+ val changedCount = minOf(previousTrailing, newlyAppended)
+ val addedCount = newlyAppended - changedCount
+
+ val endPosition = snapshotStorage.leadingNullCount + snapshotStorage.storageCount
+ if (changedCount != 0) {
+ callback.onChanged(endPosition, changedCount)
+ }
+ if (addedCount != 0) {
+ callback.onInserted(endPosition + changedCount, addedCount)
+ }
+ }
+ if (newlyPrepended != 0) {
+ val changedCount = minOf(previousLeading, newlyPrepended)
+ val addedCount = newlyPrepended - changedCount
+
+ if (changedCount != 0) {
+ callback.onChanged(previousLeading, changedCount)
+ }
+ if (addedCount != 0) {
+ callback.onInserted(0, addedCount)
+ }
+ }
+ }
+
+ @MainThread
+ override fun loadAroundInternal(index: Int) {
+ val prependItems =
+ getPrependItemsRequested(config.prefetchDistance, index, storage.leadingNullCount)
+ val appendItems = getAppendItemsRequested(
+ config.prefetchDistance,
+ index,
+ storage.leadingNullCount + storage.storageCount
+ )
+
+ prependItemsRequested = maxOf(prependItems, prependItemsRequested)
+ if (prependItemsRequested > 0) {
+ pager.trySchedulePrepend()
+ }
+
+ appendItemsRequested = maxOf(appendItems, appendItemsRequested)
+ if (appendItemsRequested > 0) {
+ pager.tryScheduleAppend()
+ }
+ }
+
+ override fun detach() = pager.detach()
+
+ @MainThread
+ override fun onInitialized(count: Int) {
+ notifyInserted(0, count)
+ // simple heuristic to decide if, when dropping pages, we should replace with placeholders
+ replacePagesWithNulls = storage.leadingNullCount > 0 || storage.trailingNullCount > 0
+ }
+
+ @MainThread
+ override fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int) {
+ // finally dispatch callbacks, after prepend may have already been scheduled
+ notifyChanged(leadingNulls, changed)
+ notifyInserted(0, added)
+
+ offsetAccessIndices(added)
+ }
+
+ @MainThread
+ override fun onPageAppended(endPosition: Int, changed: Int, added: Int) {
+ // finally dispatch callbacks, after append may have already been scheduled
+ notifyChanged(endPosition, changed)
+ notifyInserted(endPosition + changed, added)
+ }
+
+ @MainThread
+ override fun onPagePlaceholderInserted(pageIndex: Int) {
+ throw IllegalStateException("Tiled callback on ContiguousPagedList")
+ }
+
+ @MainThread
+ override fun onPageInserted(start: Int, count: Int) {
+ throw IllegalStateException("Tiled callback on ContiguousPagedList")
+ }
+
+ override fun onPagesRemoved(startOfDrops: Int, count: Int) = notifyRemoved(startOfDrops, count)
+
+ override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) =
+ notifyChanged(startOfDrops, count)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/DataSource.kt b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
new file mode 100644
index 0000000..eec73ae
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/DataSource.kt
@@ -0,0 +1,472 @@
+/*
+ * 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.annotation.AnyThread
+import androidx.annotation.RestrictTo
+import androidx.annotation.WorkerThread
+import androidx.arch.core.util.Function
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * Base class for loading pages of snapshot data into a [PagedList].
+ *
+ * DataSource is queried to load pages of content into a [PagedList]. A PagedList can grow as
+ * it loads more data, but the data loaded cannot be updated. If the underlying data set is
+ * modified, a new PagedList / DataSource pair must be created to represent the new data.
+ *
+ * <h4>Loading Pages</h4>
+ *
+ * PagedList queries data from its DataSource in response to loading hints. PagedListAdapter
+ * calls [PagedList.loadAround] to load content as the user scrolls in a RecyclerView.
+ *
+ * To control how and when a PagedList queries data from its DataSource, see
+ * [PagedList.Config]. The Config object defines things like load sizes and prefetch distance.
+ *
+ * <h4>Updating Paged Data</h4>
+ *
+ * A PagedList / DataSource pair are a snapshot of the data set. A new pair of
+ * PagedList / DataSource must be created if an update occurs, such as a reorder, insert, delete, or
+ * content update occurs. A DataSource must detect that it cannot continue loading its
+ * snapshot (for instance, when Database query notices a table being invalidated), and call
+ * [invalidate]. Then a new PagedList / DataSource pair would be created to load data from the new
+ * state of the Database query.
+ *
+ * To page in data that doesn't update, you can create a single DataSource, and pass it to a single
+ * PagedList. For example, loading from network when the network's paging API doesn't provide
+ * updates.
+ *
+ * To page in data from a source that does provide updates, you can create a [DataSource.Factory],
+ * where each DataSource created is invalidated when an update to the data set occurs that makes the
+ * current snapshot invalid. For example, when paging a query from the Database, and the table being
+ * queried inserts or removes items. You can also use a DataSource.Factory to provide multiple
+ * versions of network-paged lists. If reloading all content (e.g. in response to an action like
+ * swipe-to-refresh) is required to get a new version of data, you can connect an explicit refresh
+ * signal to call [invalidate] on the current [DataSource].
+ *
+ * If you have more granular update signals, such as a network API signaling an update to a single
+ * item in the list, it's recommended to load data from network into memory. Then present that
+ * data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory
+ * copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the
+ * snapshot can be created.
+ *
+ * <h4>Implementing a DataSource</h4>
+ *
+ * To implement, extend one of the subclasses: [PageKeyedDataSource], [ItemKeyedDataSource], or
+ * [PositionalDataSource].
+ *
+ * Use [PageKeyedDataSource] if pages you load embed keys for loading adjacent pages. For example a
+ * network response that returns some items, and a next/previous page links.
+ *
+ * Use [ItemKeyedDataSource] if you need to use data from item `N-1` to load item
+ * `N`. For example, if requesting the backend for the next comments in the list
+ * requires the ID or timestamp of the most recent loaded comment, or if querying the next users
+ * from a name-sorted database query requires the name and unique ID of the previous.
+ *
+ * Use [PositionalDataSource] if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. PositionalDataSource supports querying pages at
+ * arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that
+ * PositionalDataSource is required to respect page size for efficient tiling. If you want to
+ * override page size (e.g. when network page size constraints are only known at runtime), use one
+ * of the other DataSource classes.
+ *
+ * Because a `null` item indicates a placeholder in [PagedList], DataSource may not
+ * return `null` items in lists that it loads. This is so that users of the PagedList
+ * can differentiate unloaded placeholder items from content that has been paged in.
+ *
+ * @param Key Unique identifier for item loaded from DataSource. Often an integer to represent
+ * position in data set. Note - this is distinct from e.g. Room's `<Value> Value type
+ * loaded by the DataSource.
+ */
+abstract class DataSource<Key : Any, Value : Any>
+// Since we currently rely on implementation details of two implementations, prevent external
+// subclassing, except through exposed subclasses.
+internal constructor(internal val type: KeyType) {
+ private val >
+
+ private val _invalid = AtomicBoolean(false)
+ /**
+ * @return `true` if the data source is invalid, and can no longer be queried for data.
+ */
+ open val isInvalid
+ @WorkerThread
+ get() = _invalid.get()
+
+ private var _executor: Executor? = null
+ /**
+ * `null` until `loadInitial` is called by [PagedList] construction.
+ *
+ * This backing variable is necessary for back-compatibility with paging-common:2.1.0 Java API,
+ * while still providing synthetic accessors for Kotlin API.
+ */
+ protected val executor: Executor
+ get() = _executor ?: throw IllegalStateException(
+ "This DataSource has not been passed to a PagedList, has no executor yet."
+ )
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ fun initExecutor(executor: Executor) {
+ _executor = executor
+ }
+
+ /**
+ * Factory for DataSources.
+ *
+ * Data-loading systems of an application or library can implement this interface to allow
+ * `LiveData<PagedList>`s to be created. For example, Room can provide a
+ * [DataSource.Factory] for a given SQL query:
+ *
+ * ```
+ * @Dao
+ * interface UserDao {
+ * @Query("SELECT * FROM user ORDER BY lastName ASC")
+ * public abstract DataSource.Factory<Integer, User> usersByLastName();
+ * }
+ * ```
+ *
+ * In the above sample, `Integer` is used because it is the `Key` type of
+ * PositionalDataSource. Currently, Room uses the `LIMIT`/`OFFSET` SQL keywords to
+ * page a large query with a PositionalDataSource.
+ *
+ * @param Key Key identifying items in DataSource.
+ * @param Value Type of items in the list loaded by the DataSources.
+ */
+ abstract class Factory<Key : Any, Value : Any> {
+ /**
+ * Create a [DataSource].
+ *
+ * The [DataSource] should invalidate itself if the snapshot is no longer valid. If a
+ * [DataSource] becomes invalid, the only way to query more data is to create a new
+ * [DataSource] from the Factory.
+ *
+ * [androidx.paging.LivePagedListBuilder] for example will construct a new PagedList and
+ * DataSource when the current DataSource is invalidated, and pass the new PagedList through
+ * the `LiveData<PagedList>` to observers.
+ *
+ * @return the new DataSource.
+ */
+ abstract fun create(): DataSource<Key, Value>
+
+ /**
+ * Applies the given function to each value emitted by DataSources produced by this Factory.
+ *
+ * Same as [mapByPage], but operates on individual items.
+ *
+ * @param function Function that runs on each loaded item, returning items of a potentially
+ * new type.
+ * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @return A new [DataSource.Factory], which transforms items using the given function.
+ *
+ * @see mapByPage
+ * @see DataSource.map
+ * @see DataSource.mapByPage
+ */
+ open fun <ToValue : Any> map(function: Function<Value, ToValue>): Factory<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+
+ /**
+ * Applies the given function to each value emitted by DataSources produced by this Factory.
+ *
+ * Same as [map], but allows for batch conversions.
+ *
+ * @param function Function that runs on each loaded page, returning items of a potentially
+ * new type.
+ * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @return A new [DataSource.Factory], which transforms items using the given function.
+ *
+ * @see map
+ * @see DataSource.map
+ * @see DataSource.mapByPage
+ */
+ open fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): Factory<Key, ToValue> = object : Factory<Key, ToValue>() {
+ override fun create(): DataSource<Key, ToValue> =
+ this@Factory.create().mapByPage(function)
+ }
+ }
+
+ /**
+ * Applies the given function to each value emitted by the DataSource.
+ *
+ * Same as [map], but allows for batch conversions.
+ *
+ * @param function Function that runs on each loaded page, returning items of a potentially
+ * new type.
+ * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @return A new DataSource, which transforms items using the given function.
+ *
+ * @see map
+ * @see DataSource.Factory.map
+ * @see DataSource.Factory.mapByPage
+ */
+ open fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): DataSource<Key, ToValue> = WrapperDataSource(this, function)
+
+ /**
+ * Applies the given function to each value emitted by the DataSource.
+ *
+ * Same as [mapByPage], but operates on individual items.
+ *
+ * @param function Function that runs on each loaded item, returning items of a potentially
+ * new type.
+ * @param ToValue Type of items produced by the new DataSource, from the passed function.
+ * @return A new DataSource, which transforms items using the given function.
+ *
+ * @see mapByPage
+ * @see DataSource.Factory.map
+ * @see DataSource.Factory.mapByPage
+ */
+ open fun <ToValue : Any> map(function: Function<Value, ToValue>): DataSource<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+
+ /**
+ * Returns true if the data source guaranteed to produce a contiguous set of items, never
+ * producing gaps.
+ */
+ internal open val isContiguous = true
+
+ internal open val supportsPageDropping = true
+
+ /**
+ * Invalidation callback for [DataSource].
+ *
+ * Used to signal when a [DataSource] a data source has become invalid, and that a new data
+ * source is needed to continue loading data.
+ */
+ interface InvalidatedCallback {
+ /**
+ * Called when the data backing the list has become invalid. This callback is typically used
+ * to signal that a new data source is needed.
+ *
+ * This callback will be invoked on the thread that calls [invalidate]. It is valid for the
+ * data source to invalidate itself during its load methods, or for an outside source to
+ * invalidate it.
+ */
+ @AnyThread
+ fun onInvalidated()
+ }
+
+ /**
+ * Add a callback to invoke when the DataSource is first invalidated.
+ *
+ * Once invalidated, a data source will not become valid again.
+ *
+ * A data source will only invoke its callbacks once - the first time [invalidate] is called, on
+ * that thread.
+ *
+ * @param onInvalidatedCallback The callback, will be invoked on thread that invalidates the
+ * [DataSource].
+ */
+ @AnyThread
+ open fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ onInvalidatedCallbacks.add(onInvalidatedCallback)
+ }
+
+ /**
+ * Remove a previously added invalidate callback.
+ *
+ * @param onInvalidatedCallback The previously added callback.
+ */
+ @AnyThread
+ open fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ onInvalidatedCallbacks.remove(onInvalidatedCallback)
+ }
+
+ /**
+ * Signal the data source to stop loading, and notify its callback.
+ *
+ * If invalidate has already been called, this method does nothing.
+ */
+ @AnyThread
+ open fun invalidate() {
+ if (_invalid.compareAndSet(false, true)) {
+ onInvalidatedCallbacks.forEach { it.onInvalidated() }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ enum class LoadType {
+ INITIAL,
+ START,
+ END
+ }
+
+ /**
+ * @param K Type of the key used to query the [DataSource].
+ * @property key Can be `null` for init, otherwise non-null
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ class Params<K : Any> internal constructor(
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ val type: LoadType,
+ val key: K?,
+ val initialLoadSize: Int,
+ val placeholdersEnabled: Boolean,
+ val pageSize: Int
+ )
+
+ /**
+ * @param Value Type of the data produced by a [DataSource].
+ * @property counted Set to true if the result is an initial load that is passed totalCount
+ */
+ open class BaseResult<Value : Any> protected constructor(
+ @JvmField
+ val data: List<Value>,
+ val prevKey: Any?,
+ val nextKey: Any?,
+ val leadingNulls: Int,
+ val trailingNulls: Int,
+ val offset: Int,
+ val counted: Boolean
+ ) {
+ init {
+ validate()
+ }
+
+ // only one of leadingNulls / offset may be used
+ private fun position() = leadingNulls + offset
+
+ internal fun totalCount() = when {
+ // only one of leadingNulls / offset may be used
+ counted -> position() + data.size + trailingNulls
+ else -> TOTAL_COUNT_UNKNOWN
+ }
+
+ private fun validate() {
+ if (leadingNulls < 0 || offset < 0) {
+ throw IllegalArgumentException("Position must be non-negative")
+ }
+ if (data.isEmpty() && (leadingNulls != 0 || trailingNulls != 0)) {
+ throw IllegalArgumentException(
+ "Initial result cannot be empty if items are" + " present in data set."
+ )
+ }
+ if (trailingNulls < 0) {
+ throw IllegalArgumentException(
+ "List size + position too large, last item in list beyond totalCount."
+ )
+ }
+ }
+
+ internal fun validateForInitialTiling(pageSize: Int) {
+ if (!counted) {
+ throw IllegalStateException(
+ "Placeholders requested, but totalCount not provided. Please call the" +
+ " three-parameter onResult method, or disable placeholders in the" +
+ " PagedList.Config"
+ )
+ }
+ if (trailingNulls != 0 && data.size % pageSize != 0) {
+ val totalCount = leadingNulls + data.size + trailingNulls
+ throw IllegalArgumentException(
+ "PositionalDataSource requires initial load size to be a multiple of page" +
+ " size to support internal tiling. loadSize ${data.size}, position" +
+ " $leadingNulls, totalCount $totalCount, pageSize $pageSize"
+ )
+ }
+ if (position() % pageSize != 0) {
+ throw IllegalArgumentException(
+ "Initial load must be pageSize aligned.Position = ${position()}, pageSize =" +
+ " $pageSize"
+ )
+ }
+ }
+
+ override fun equals(other: Any?) = when (other) {
+ is BaseResult<*> -> data == other.data &&
+ prevKey == other.prevKey &&
+ nextKey == other.nextKey &&
+ leadingNulls == other.leadingNulls &&
+ trailingNulls == other.trailingNulls &&
+ offset == other.offset &&
+ counted == other.counted
+ else -> false
+ }
+
+ internal companion object {
+ internal fun <T : Any> empty() = BaseResult(emptyList<T>(), null, null, 0, 0, 0, true)
+
+ internal const val TOTAL_COUNT_UNKNOWN = -1
+
+ internal fun <ToValue : Any, Value : Any> convert(
+ result: BaseResult<ToValue>,
+ function: Function<List<ToValue>, List<Value>>
+ ) = BaseResult(
+ data = convert(function, result.data),
+ prevKey = result.prevKey,
+ nextKey = result.nextKey,
+ leadingNulls = result.leadingNulls,
+ trailingNulls = result.trailingNulls,
+ offset = result.offset,
+ counted = result.counted
+ )
+ }
+ }
+
+ internal enum class KeyType {
+ POSITIONAL,
+ PAGE_KEYED,
+ ITEM_KEYED
+ }
+
+ internal abstract fun load(params: Params<Key>): ListenableFuture<out BaseResult<Value>>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ internal abstract fun getKeyInternal(item: Value): Key
+
+ /**
+ * Determine whether an error passed to a loading method is retryable.
+ *
+ * @param error Throwable returned from an attempted load from this DataSource.
+ * @return `true` if the error is retryable, otherwise false.
+ */
+ open fun isRetryableError(error: Throwable) = false
+
+ internal companion object {
+ internal fun <A, B> convert(
+ function: Function<List<A>, List<B>>,
+ source: List<A>
+ ): List<B> {
+ val dest = function.apply(source)
+ if (dest.size != source.size) {
+ throw IllegalStateException(
+ "Invalid Function $function changed return size. This is not supported."
+ )
+ }
+ return dest
+ }
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
new file mode 100644
index 0000000..ef09669
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/InitialPagedList.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.annotation.RestrictTo
+import androidx.paging.futures.DirectExecutor
+
+/**
+ * InitialPagedList is an empty placeholder that's sent at the front of a stream of PagedLists.
+ *
+ * It's used solely for listening to [PagedList.LoadType.REFRESH] loading events, and retrying
+ * any errors that occur during initial load.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class InitialPagedList<K : Any, V : Any>(
+ dataSource: DataSource<K, V>,
+ config: Config,
+ initialKey: K?
+) :
+ ContiguousPagedList<K, V>(
+ dataSource,
+ DirectExecutor,
+ DirectExecutor,
+ null,
+ config,
+ DataSource.BaseResult.empty<V>(),
+ 0 // no previous load, so pass 0
+ ) {
+ override val lastKey = initialKey
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
new file mode 100644
index 0000000..1924dca
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ItemKeyedDataSource.kt
@@ -0,0 +1,306 @@
+/*
+ * 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.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Incremental data loader for paging keyed content, where loaded content uses previously loaded
+ * items as input to future loads.
+ *
+ * Implement a DataSource using ItemKeyedDataSource if you need to use data from item `N - 1`
+ * to load item `N`. This is common, for example, in uniquely sorted database queries where
+ * attributes of the item such just before the next query define how to execute it.
+ *
+ * The `InMemoryByItemRepository` in the
+ * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
+ * shows how to implement a network ItemKeyedDataSource using
+ * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh, network errors,
+ * and retry.
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ *
+ * @see ListenableItemKeyedDataSource
+ */
+abstract class ItemKeyedDataSource<Key : Any, Value : Any> :
+ ListenableItemKeyedDataSource<Key, Value>() {
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ */
+ open class LoadInitialParams<Key : Any>(
+ requestedInitialKey: Key?,
+ requestedLoadSize: Int,
+ placeholdersEnabled: Boolean
+ ) : ListenableItemKeyedDataSource.LoadInitialParams<Key>(
+ requestedInitialKey,
+ requestedLoadSize,
+ placeholdersEnabled
+ )
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query Value types out of the [DataSource].
+ */
+ open class LoadParams<Key : Any>(key: Key, requestedLoadSize: Int) :
+ ListenableItemKeyedDataSource.LoadParams<Key>(key, requestedLoadSize)
+
+ /**
+ * Callback for [loadInitial]
+ * to return data and, optionally, position/count information.
+ *
+ * A callback can be called only once, and will throw if called again.
+ *
+ * If you can compute the number of items in the data set before and after the loaded range,
+ * call the three parameter [onResult] to pass that information. You can skip passing this
+ * information by calling the single parameter [onResult], either if it's difficult to compute,
+ * or if [LoadInitialParams.placeholdersEnabled] is `false`, so the positioning information will
+ * be ignored.
+ *
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param Value Type of items being loaded.
+ */
+ abstract class LoadInitialCallback<Value> : LoadCallback<Value>() {
+ /**
+ * Called to pass initial load state from a DataSource.
+ *
+ * Call this method from your DataSource's `loadInitial` function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ *
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are `N`
+ * items before the items in data that can be loaded from this DataSource,
+ * pass `N`.
+ * @param totalCount Total number of items that may be returned from this [DataSource].
+ * Includes the number in the initial `data` parameter as well as any
+ * items that can be loaded in front or behind of `data`.
+ */
+ abstract fun onResult(data: List<Value>, position: Int, totalCount: Int)
+ }
+
+ /**
+ * Callback for ItemKeyedDataSource [loadBefore] and [loadAfter] to return data.
+ *
+ * A callback can be called only once, and will throw if called again.
+ *
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param Value Type of items being loaded.
+ */
+ abstract class LoadCallback<Value> {
+ /**
+ * Called to pass loaded data from a DataSource.
+ *
+ * Call this method from your ItemKeyedDataSource's [loadBefore] and [loadAfter] methods to
+ * return data.
+ *
+ * Call this from [loadInitial] to initialize without counting available data, or supporting
+ * placeholders.
+ *
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the [ItemKeyedDataSource].
+ */
+ abstract fun onResult(data: List<Value>)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from [loadInitial], [loadBefore], or [loadAfter]
+ * methods.
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ final override fun loadInitial(
+ params: ListenableItemKeyedDataSource.LoadInitialParams<Key>
+ ): ListenableFuture<InitialResult<Value>> {
+ val future = ResolvableFuture.create<InitialResult<Value>>()
+ executor.execute {
+ val callback = object : LoadInitialCallback<Value>() {
+ override fun onResult(data: List<Value>, position: Int, totalCount: Int) {
+ future.set(InitialResult(data, position, totalCount))
+ }
+
+ override fun onResult(data: List<Value>) {
+ future.set(InitialResult(data))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadInitial(
+ LoadInitialParams(
+ params.requestedInitialKey,
+ params.requestedLoadSize,
+ params.placeholdersEnabled
+ ),
+ callback
+ )
+ }
+ return future
+ }
+
+ private fun getFutureAsCallback(future: ResolvableFuture<Result<Value>>): LoadCallback<Value> {
+ return object : LoadCallback<Value>() {
+ override fun onResult(data: List<Value>) {
+ future.set(Result(data))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ }
+
+ final override fun loadBefore(
+ params: ListenableItemKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Value>> {
+ val future = ResolvableFuture.create<Result<Value>>()
+ executor.execute {
+ val loadParams = LoadParams(params.key, params.requestedLoadSize)
+ loadBefore(loadParams, getFutureAsCallback(future))
+ }
+ return future
+ }
+
+ final override fun loadAfter(
+ params: ListenableItemKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Value>> {
+ val future = ResolvableFuture.create<Result<Value>>()
+ executor.execute {
+ val loadParams = LoadParams(params.key, params.requestedLoadSize)
+ loadAfter(loadParams, getFutureAsCallback(future))
+ }
+ return future
+ }
+
+ /**
+ * Load initial data.
+ *
+ * This method is called first to initialize a [PagedList] with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+ * the callback via the three-parameter [LoadInitialCallback.onResult]. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ *
+ * [LoadInitialParams.requestedInitialKey] and [LoadInitialParams.requestedLoadSize]
+ * are hints, not requirements, so they may be altered or ignored. Note that ignoring the
+ * `requestedInitialKey` can prevent subsequent PagedList/DataSource pairs from
+ * initializing at the same location. If your DataSource never invalidates (for example,
+ * loading from the network without the network ever signalling that old data must be reloaded),
+ * it's fine to ignore the `initialLoadKey` and always start from the beginning of the
+ * data set.
+ *
+ * @param params Parameters for initial load, including initial key and requested size.
+ * @param callback Callback that receives initial load data.
+ */
+ abstract fun loadInitial(params: LoadInitialParams<Key>, callback: LoadInitialCallback<Value>)
+
+ /**
+ * Load list data after the key specified in [LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * Data may be passed synchronously during the loadAfter method, or deferred and called at a
+ * later time. Further loads going down will be blocked until the callback is called.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key to load after, and requested size.
+ * @param callback Callback that receives loaded data.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Value>)
+
+ /**
+ * Load list data before the key specified in [LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * **Note:** Data returned will be prepended just before the key
+ * passed, so if you vary size, ensure that the last item is adjacent to the passed key.
+ *
+ * Data may be passed synchronously during the loadBefore method, or deferred and called at a
+ * later time. Further loads going up will be blocked until the callback is called.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key to load before, and requested size.
+ * @param callback Callback that receives loaded data.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Value>)
+
+ /**
+ * Return a key associated with the given item.
+ *
+ * If your ItemKeyedDataSource is loading from a source that is sorted and loaded by a unique
+ * integer ID, you would return `item.getID()` here. This key can then be passed to
+ * [loadBefore] or [loadAfter] to load additional items adjacent to the item passed to this
+ * function.
+ *
+ * If your key is more complex, such as when you're sorting by name, then resolving collisions
+ * with integer ID, you'll need to return both. In such a case you would use a wrapper class,
+ * such as `Pair<String, Integer>` or, in Kotlin,
+ * `data class Key(val name: String, val id: Int)`
+ *
+ * @param item Item to get the key from.
+ * @return Key associated with given item.
+ */
+ abstract override fun getKey(item: Value): Key
+
+ final override fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): ItemKeyedDataSource<Key, ToValue> = WrapperItemKeyedDataSource(this, function)
+
+ final override fun <ToValue : Any> map(
+ function: Function<Value, ToValue>
+ ): ItemKeyedDataSource<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListDataSource.kt
new file mode 100644
index 0000000..161a583
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListDataSource.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.annotation.VisibleForTesting
+import java.util.ArrayList
+
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+class ListDataSource<T : Any>(list: List<T>) : PositionalDataSource<T>() {
+ private val list: List<T> = ArrayList(list)
+
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
+ val totalCount = list.size
+ val position = computeInitialLoadPosition(params, totalCount)
+ val loadSize = computeInitialLoadSize(params, position, totalCount)
+
+ // for simplicity, we could return everything immediately,
+ // but we tile here since it's expected behavior
+ val sublist = list.subList(position, position + loadSize)
+ callback.onResult(sublist, position, totalCount)
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
+ val end = minOf(list.size, params.startPosition + params.loadSize)
+ callback.onResult(list.subList(params.startPosition, end))
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListenableItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListenableItemKeyedDataSource.kt
new file mode 100644
index 0000000..2289b23
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListenableItemKeyedDataSource.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright 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.
+ */
+
+package androidx.paging
+
+import androidx.annotation.RestrictTo
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Incremental data loader for paging keyed content, where loaded content uses previously loaded
+ * items as input to future loads.
+ *
+ * Implement a DataSource using ListenableItemKeyedDataSource if you need to use data from item
+ * `N - 1` to load item `N`. This is common, for example, in uniquely sorted database
+ * queries where attributes of the item such just before the next query define how to execute it.
+ *
+ * @see ItemKeyedDataSource
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+abstract class ListenableItemKeyedDataSource<Key : Any, Value : Any> :
+ DataSource<Key, Value>(KeyType.ITEM_KEYED) {
+
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal final override fun load(params: Params<Key>): ListenableFuture<out BaseResult<Value>> {
+ when (params.type) {
+ LoadType.INITIAL -> {
+ val initParams = ItemKeyedDataSource.LoadInitialParams(
+ params.key, params.initialLoadSize, params.placeholdersEnabled
+ )
+ return loadInitial(initParams)
+ }
+ LoadType.START -> {
+ val loadParams = ItemKeyedDataSource.LoadParams(params.key!!, params.pageSize)
+ return loadBefore(loadParams)
+ }
+ LoadType.END -> {
+ val loadParams = ItemKeyedDataSource.LoadParams(params.key!!, params.pageSize)
+ return loadAfter(loadParams)
+ }
+ }
+ }
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @property requestedInitialKey Load items around this key, or at the beginning of the data set
+ * if `null` is passed.
+ *
+ * Note that this key is generally a hint, and may be ignored if
+ * you want to always load from the beginning.
+ * @property requestedLoadSize Requested number of items to load.
+ *
+ * Note that this may be larger than available data.
+ * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
+ * loaded total count will be ignored.
+ */
+ open class LoadInitialParams<Key : Any>(
+ @JvmField val requestedInitialKey: Key?,
+ @JvmField val requestedLoadSize: Int,
+ @JvmField val placeholdersEnabled: Boolean
+ )
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @property key Load items before/after this key.
+ *
+ * Returned data must begin directly adjacent to this position.
+ * @property requestedLoadSize Requested number of items to load.
+ *
+ * Returned page can be of this size, but it may be altered if that
+ * is easier, e.g. a network data source where the backend defines
+ * page size.
+ */
+ open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+
+ /**
+ * Load initial data.
+ *
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass `totalCount`
+ * to the [InitialResult] constructor. This enables PagedLists presenting data from this
+ * source to display placeholders to represent unloaded items.
+ *
+ * [ItemKeyedDataSource.LoadInitialParams.requestedInitialKey] and
+ * [ItemKeyedDataSource.LoadInitialParams.requestedLoadSize] are hints, not requirements,
+ * so they may be altered or ignored. Note that ignoring the `requestedInitialKey` can
+ * prevent subsequent PagedList/DataSource pairs from initializing at the same location. If your
+ * DataSource never invalidates (for example, loading from the network without the network ever
+ * signalling that old data must be reloaded), it's fine to ignore the `initialLoadKey`
+ * and always start from the beginning of the data set.
+ *
+ * @param params Parameters for initial load, including initial key and requested size.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadInitial(params: LoadInitialParams<Key>): ListenableFuture<InitialResult<Value>>
+
+ /**
+ * Load list data after the key specified in
+ * [LoadParams.key][ItemKeyedDataSource.LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key to load after, and requested size.
+ * @return [ListenableFuture] of the loaded data.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>): ListenableFuture<Result<Value>>
+
+ /**
+ * Load list data after the key specified in
+ * [LoadParams.key][ItemKeyedDataSource.LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * **Note:** Data returned will be prepended just before the key
+ * passed, so if you don't return a page of the requested size, ensure that the last item is
+ * adjacent to the passed key.
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source,
+ * and prevent further loading.
+ *
+ * @param params Parameters for the load, including the key to load before, and requested size.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>): ListenableFuture<Result<Value>>
+
+ abstract fun getKey(item: Value): Key
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ final override fun getKeyInternal(item: Value): Key = getKey(item)
+
+ /**
+ * Type produced by [loadInitial] to represent initially loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class InitialResult<V : Any> : BaseResult<V> {
+ constructor(data: List<V>, position: Int, totalCount: Int) : super(
+ data,
+ null,
+ null,
+ position,
+ totalCount - data.size - position,
+ position,
+ true
+ )
+
+ constructor(data: List<V>) : super(data, null, null, 0, 0, 0, false)
+ }
+
+ /**
+ * Type produced by [loadBefore] and [loadAfter] to represent a page of loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class Result<V : Any>(data: List<V>) :
+ DataSource.BaseResult<V>(data, null, null, 0, 0, 0, false)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListenablePageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListenablePageKeyedDataSource.kt
new file mode 100644
index 0000000..7780b52
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListenablePageKeyedDataSource.kt
@@ -0,0 +1,199 @@
+/*
+ * 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.annotation.RestrictTo
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Incremental data loader for page-keyed content, where requests return keys for next/previous
+ * pages.
+ *
+ * Implement a DataSource using PageKeyedDataSource if you need to use data from page `N - 1`
+ * to load page `N`. This is common, for example, in network APIs that include a next/previous
+ * link or key with each page load.
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+abstract class ListenablePageKeyedDataSource<Key : Any, Value : Any> :
+ DataSource<Key, Value>(KeyType.PAGE_KEYED) {
+
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal final override fun load(params: Params<Key>): ListenableFuture<out BaseResult<Value>> {
+ if (params.type == LoadType.INITIAL) {
+ val initParams = PageKeyedDataSource.LoadInitialParams<Key>(
+ params.initialLoadSize,
+ params.placeholdersEnabled
+ )
+ return loadInitial(initParams)
+ } else {
+ if (params.key == null) {
+ // null key, immediately return empty data
+ val future = ResolvableFuture.create<BaseResult<Value>>()
+ future.set(BaseResult.empty())
+ return future
+ }
+
+ val loadParams = PageKeyedDataSource.LoadParams(params.key, params.pageSize)
+
+ if (params.type == LoadType.START) {
+ return loadBefore(loadParams)
+ } else if (params.type == LoadType.END) {
+ return loadAfter(loadParams)
+ }
+ }
+ throw IllegalArgumentException("Unsupported type " + params.type.toString())
+ }
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query pages.
+ * @property requestedLoadSize Requested number of items to load.
+ *
+ * Note that this may be larger than available data.
+ * @property placeholdersEnabled Defines whether placeholders are enabled, and whether the
+ * loaded total count will be ignored.
+ */
+ open class LoadInitialParams<Key : Any>(
+ @JvmField val requestedLoadSize: Int,
+ @JvmField val placeholdersEnabled: Boolean
+ )
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query pages.
+ * @property key Load items before/after this key.
+ *
+ * Returned data must begin directly adjacent to this position.
+ * @property requestedLoadSize Requested number of items to load.
+ *
+ * Returned page can be of this size, but it may be altered if that
+ * is easier, e.g. a network data source where the backend defines
+ * page size.
+ */
+ open class LoadParams<Key : Any>(@JvmField val key: Key, @JvmField val requestedLoadSize: Int)
+
+ /**
+ * Load initial data.
+ *
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the position and
+ * count to the [InitialResult constructor][InitialResult]. This enables PagedLists presenting
+ * data from this source to display placeholders to represent unloaded items.
+ *
+ * [LoadInitialParams.requestedLoadSize] is a hint, not a requirement, so it may be may be
+ * altered or ignored.
+ *
+ * @param params Parameters for initial load, including requested load size.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadInitial(
+ params: LoadInitialParams<Key>
+ ): ListenableFuture<InitialResult<Key, Value>>
+
+ /**
+ * Prepend page with the key specified by [LoadParams.key][PageKeyedDataSource.LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>): ListenableFuture<Result<Key, Value>>
+
+ /**
+ * Append page with the key specified by [LoadParams.key][PageKeyedDataSource.LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @return ListenableFuture of the loaded data.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>): ListenableFuture<Result<Key, Value>>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ override fun getKeyInternal(item: Value): Key =
+ throw IllegalStateException("Cannot get key by item in pageKeyedDataSource")
+
+ /**
+ * To support page dropping when PageKeyed, we'll need to:
+ * - Stash keys for every page we have loaded (can id by index relative to loadInitial)
+ * - Drop keys for any page not adjacent to loaded content
+ * - And either:
+ * - Allow impl to signal previous page key: onResult(data, nextPageKey, prevPageKey)
+ * - Re-trigger loadInitial, and break assumption it will only occur once.
+ */
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal override val supportsPageDropping = false
+
+ /**
+ * Type produced by [loadInitial] to represent initially loaded data.
+ *
+ * @param Key Type of key used to identify pages.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+ open class InitialResult<Key : Any, Value : Any> : BaseResult<Value> {
+ constructor(
+ data: List<Value>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: Key?,
+ nextPageKey: Key?
+ ) : super(
+ data,
+ previousPageKey,
+ nextPageKey,
+ position,
+ totalCount - data.size - position,
+ position,
+ true
+ )
+
+ constructor(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?) :
+ super(data, previousPageKey, nextPageKey, 0, 0, 0, false)
+ }
+
+ /**
+ * Type produced by [loadBefore] and [loadAfter] to represent a page of loaded data.
+ *
+ * @param Key Type of key used to identify pages.
+ * @param Value Type of items being loaded by the [DataSource].
+ */
+ open class Result<Key : Any, Value : Any>(data: List<Value>, adjacentPageKey: Key?) :
+ DataSource.BaseResult<Value>(data, adjacentPageKey, adjacentPageKey, 0, 0, 0, false)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/ListenablePositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/ListenablePositionalDataSource.kt
new file mode 100644
index 0000000..7058c2f
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/ListenablePositionalDataSource.kt
@@ -0,0 +1,313 @@
+/*
+ * 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.annotation.RestrictTo
+import androidx.paging.ListenablePositionalDataSource.InitialResult
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
+ * arbitrary page positions.
+ *
+ * Extend [ListenablePositionalDataSource] if you can load pages of a requested size at arbitrary
+ * positions, and provide a fixed item count. If your data source can't support loading arbitrary
+ * requested page sizes (e.g. when network page size constraints are only known at runtime), either
+ * use [PageKeyedDataSource] or [ItemKeyedDataSource], or pass the initial result with the two
+ * parameter [InitialResult constructor][InitialResult].
+ *
+ * @param T Type of items being loaded by the [PositionalDataSource].
+ *
+ * @see PositionalDataSource
+ */
+abstract class ListenablePositionalDataSource<T : Any> : DataSource<Int, T>(KeyType.POSITIONAL) {
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal final override fun load(params: Params<Int>): ListenableFuture<out BaseResult<T>> {
+ if (params.type == LoadType.INITIAL) {
+ var initialPosition = 0
+ var initialLoadSize = params.initialLoadSize
+ if (params.key != null) {
+ initialPosition = params.key
+
+ if (params.placeholdersEnabled) {
+ // snap load size to page multiple (minimum two)
+ initialLoadSize =
+ maxOf(initialLoadSize / params.pageSize, 2) * params.pageSize
+
+ // move start so the load is centered around the key, not starting at it
+ val idealStart = initialPosition - initialLoadSize / 2
+ initialPosition = maxOf(0, idealStart / params.pageSize * params.pageSize)
+ } else {
+ // not tiled, so don't try to snap or force multiple of a page size
+ initialPosition -= initialLoadSize / 2
+ }
+ }
+ val initParams = PositionalDataSource.LoadInitialParams(
+ initialPosition,
+ initialLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled
+ )
+ return loadInitial(initParams)
+ } else {
+ var startIndex = params.key!!
+ var loadSize = params.pageSize
+ if (params.type == LoadType.START) {
+ loadSize = minOf(loadSize, startIndex + 1)
+ startIndex = startIndex - loadSize + 1
+ }
+ return loadRange(PositionalDataSource.LoadRangeParams(startIndex, loadSize))
+ }
+ }
+
+ /**
+ * Holder object for inputs to [loadInitial].
+ */
+ open class LoadInitialParams(
+ /**
+ * Initial load position requested.
+ *
+ * Note that this may not be within the bounds of your data set, it may need to be adjusted
+ * before you execute your load.
+ */
+ @JvmField
+ val requestedStartPosition: Int,
+ /**
+ * Requested number of items to load.
+ *
+ * Note that this may be larger than available data.
+ */
+ @JvmField
+ val requestedLoadSize: Int,
+ /**
+ * Defines page size acceptable for return values.
+ *
+ * List of items passed to the callback must be an integer multiple of page size.
+ */
+ @JvmField
+ val pageSize: Int,
+ /**
+ * Defines whether placeholders are enabled, and whether the loaded total count will be
+ * ignored.
+ */
+ @JvmField
+ val placeholdersEnabled: Boolean
+ )
+
+ /**
+ * Holder object for inputs to [loadRange].
+ */
+ open class LoadRangeParams(
+ /**
+ * START position of data to load.
+ *
+ * Returned data must start at this position.
+ */
+ @JvmField
+ val startPosition: Int,
+ /**
+ * Number of items to load.
+ *
+ * Returned data must be of this size, unless at end of the list.
+ */
+ @JvmField
+ val loadSize: Int
+ )
+
+ /**
+ * Load initial list data.
+ *
+ * This method is called to load the initial page(s) from the DataSource.
+ *
+ * Result list must be a multiple of pageSize to enable efficient tiling.
+ *
+ * @param params Parameters for initial load, including requested start position, load size, and
+ * page size.
+ * @return [ListenableFuture] of the loaded data.
+ */
+ abstract fun loadInitial(params: LoadInitialParams): ListenableFuture<InitialResult<T>>
+
+ /**
+ * Called to load a range of data from the DataSource.
+ *
+ * This method is called to load additional pages from the DataSource after the
+ * [ItemKeyedDataSource.LoadInitialCallback] passed to dispatchLoadInitial has initialized a
+ * [PagedList].
+ *
+ * Unlike [ItemKeyedDataSource.loadInitial], this method must return the number of items
+ * requested, at the position requested.
+ *
+ * @param params Parameters for load, including start position and load size.
+ * @return [ListenableFuture] of the loaded data.
+ */
+ abstract fun loadRange(params: LoadRangeParams): ListenableFuture<RangeResult<T>>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ final override fun getKeyInternal(item: T): Int =
+ throw IllegalStateException("Cannot get key by item in positionalDataSource")
+
+ internal companion object {
+ /**
+ * Helper for computing an initial position in [loadInitial] when total data set size can be
+ * computed ahead of loading.
+ *
+ * The value computed by this function will do bounds checking, page alignment, and
+ * positioning based on initial load size requested.
+ *
+ * Example usage in a [PositionalDataSource] subclass:
+ * ```
+ * class ItemDataSource extends PositionalDataSource<Item> {
+ * private int computeCount() {
+ * // actual count code here
+ * }
+ *
+ * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+ * // actual load code here
+ * }
+ * }
+ * ```
+ *
+ * ```
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @NonNull LoadInitialCallback<Item> callback) {
+ * int totalCount = computeCount();
+ * int position = computeInitialLoadPosition(params, totalCount);
+ * int loadSize = computeInitialLoadSize(params, position, totalCount);
+ * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+ * }
+ * ```
+ *
+ * ```
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested start /
+ * loadSize.
+ * @param totalCount Total size of the data set.
+ * @return Position to start loading at.
+ *
+ * @see [computeInitialLoadSize]
+ */
+ @JvmStatic
+ fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int {
+ val position = params.requestedStartPosition
+ val initialLoadSize = params.requestedLoadSize
+ val pageSize = params.pageSize
+
+ var pageStart = position / pageSize * pageSize
+
+ // maximum start pos is that which will encompass end of list
+ val maximumLoadPage =
+ (totalCount - initialLoadSize + pageSize - 1) / pageSize * pageSize
+ pageStart = minOf(maximumLoadPage, pageStart)
+
+ // minimum start position is 0
+ pageStart = maxOf(0, pageStart)
+
+ return pageStart
+ }
+
+ /**
+ * Helper for computing an initial load size in [loadInitial] when total data set size can
+ * be computed ahead of loading.
+ *
+ * This function takes the requested load size, and bounds checks it against the value
+ * returned by [computeInitialLoadPosition].
+ *
+ * Example usage in a PositionalDataSource subclass:
+ * ```
+ * class ItemDataSource extends PositionalDataSource<Item> {
+ * private int computeCount() {
+ * // actual count code here
+ * }
+ *
+ * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+ * // actual load code here
+ * }
+ *
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @NonNull LoadInitialCallback<Item> callback) {
+ * int totalCount = computeCount();
+ * int position = computeInitialLoadPosition(params, totalCount);
+ * int loadSize = computeInitialLoadSize(params, position, totalCount);
+ * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+ * }
+ *
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested
+ * start / loadSize.
+ * @param initialLoadPosition Value returned by [computeInitialLoadPosition]
+ * @param totalCount Total size of the data set.
+ * @return Number of items to load.
+ *
+ * @see [computeInitialLoadPosition]
+ */
+ @JvmStatic
+ fun computeInitialLoadSize(
+ params: LoadInitialParams,
+ initialLoadPosition: Int,
+ totalCount: Int
+ ) = minOf(totalCount - initialLoadPosition, params.requestedLoadSize)
+ }
+
+ /**
+ * Type produced by [loadInitial] to represent initially loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class InitialResult<V : Any> : BaseResult<V> {
+ constructor(data: List<V>, position: Int, totalCount: Int) :
+ super(data, null, null, position, totalCount - data.size - position, 0, true) {
+ if (data.isEmpty() && position != 0) {
+ throw IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set."
+ )
+ }
+ }
+
+ constructor(data: List<V>, position: Int) : super(data, null, null, 0, 0, position, false) {
+ if (data.isEmpty() && position != 0) {
+ throw IllegalArgumentException(
+ "Initial result cannot be empty if items are present in data set."
+ )
+ }
+ }
+ }
+
+ /**
+ * Type produced by [loadRange] to represent a page of loaded data.
+ *
+ * @param V The type of the data loaded.
+ */
+ open class RangeResult<V : Any>(data: List<V>) : BaseResult<V>(data, null, null, 0, 0, 0, false)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
new file mode 100644
index 0000000..ad41ea0
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PageKeyedDataSource.kt
@@ -0,0 +1,322 @@
+/*
+ * 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.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Incremental data loader for page-keyed content, where requests return keys for next/previous
+ * pages.
+ *
+ * Implement a DataSource using PageKeyedDataSource if you need to use data from page `N - 1` to
+ * load page `N`. This is common, for example, in network APIs that include a next/previous link or
+ * key with each page load.
+ *
+ * The `InMemoryByPageRepository` in the
+ * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
+ * shows how to implement a network PageKeyedDataSource using
+ * [Retrofit](https://square.github.io/retrofit/), while
+ * handling swipe-to-refresh, network errors, and retry.
+ *
+ * @param Key Type of data used to query Value types out of the DataSource.
+ * @param Value Type of items being loaded by the DataSource.
+ */
+abstract class PageKeyedDataSource<Key : Any, Value : Any> :
+ ListenablePageKeyedDataSource<Key, Value>() {
+ /**
+ * Holder object for inputs to [loadInitial].
+ *
+ * @param Key Type of data used to query pages.
+ */
+ open class LoadInitialParams<Key : Any>(requestedLoadSize: Int, placeholdersEnabled: Boolean) :
+ ListenablePageKeyedDataSource.LoadInitialParams<Key>(requestedLoadSize, placeholdersEnabled)
+
+ /**
+ * Holder object for inputs to [loadBefore] and [loadAfter].
+ *
+ * @param Key Type of data used to query pages.
+ */
+ open class LoadParams<Key : Any>(key: Key, requestedLoadSize: Int) :
+ ListenablePageKeyedDataSource.LoadParams<Key>(key, requestedLoadSize)
+
+ /**
+ * Callback for [loadInitial] to return data and, optionally, position/count information.
+ *
+ * A callback can be called only once, and will throw if called again.
+ *
+ * If you can compute the number of items in the data set before and after the loaded range,
+ * call the five parameter [onResult] to pass that information. You can skip passing this
+ * information by calling the three parameter [onResult], either if it's difficult to compute,
+ * or if [LoadInitialParams.placeholdersEnabled] is `false`, so the positioning information will
+ * be ignored.
+ *
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param Key Type of data used to query pages.
+ * @param Value Type of items being loaded.
+ */
+ abstract class LoadInitialCallback<Key, Value> {
+ /**
+ * Called to pass initial load state from a DataSource.
+ *
+ * Call this method from your DataSource's `loadInitial` function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ *
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are `N`
+ * items before the items in data that can be loaded from this DataSource,
+ * pass `N`.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial `data` parameter as well as any
+ * items that can be loaded in front or behind of `data`.
+ */
+ abstract fun onResult(
+ data: List<Value>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: Key?,
+ nextPageKey: Key?
+ )
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ *
+ * Call this from [loadInitial] to initialize without counting available data, or supporting
+ * placeholders.
+ *
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the [PageKeyedDataSource].
+ * @param previousPageKey Key for page before the initial load result, or `null` if no more
+ * data can be loaded before.
+ * @param nextPageKey Key for page after the initial load result, or `null` if no
+ * more data can be loaded after.
+ */
+ abstract fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from [loadInitial].
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ /**
+ * Callback for [loadBefore] and [loadAfter] to return data.
+ *
+ * A callback can be called only once, and will throw if called again.
+ *
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param Key Type of data used to query pages.
+ * @param Value Type of items being loaded.
+ */
+ abstract class LoadCallback<Key, Value> {
+ /**
+ * Called to pass loaded data from a [DataSource].
+ *
+ * Call this method from your PageKeyedDataSource's [loadBefore] and [loadAfter] methods to
+ * return data.
+ *
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * Pass the key for the subsequent page to load to adjacentPageKey. For example, if you've
+ * loaded a page in [loadBefore], pass the key for the previous page, or `null` if the
+ * loaded page is the first. If in [loadAfter], pass the key for the next page, or `null`
+ * if the loaded page is the last.
+ *
+ * @param data List of items loaded from the PageKeyedDataSource.
+ * @param adjacentPageKey Key for subsequent page load (previous page in [loadBefore] / next
+ * page in [loadAfter]), or `null` if there are no more pages to load
+ * in the current load direction.
+ */
+ abstract fun onResult(data: List<Value>, adjacentPageKey: Key?)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from your PageKeyedDataSource's [loadBefore] and
+ * [loadAfter] methods.
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ final override fun loadInitial(
+ params: ListenablePageKeyedDataSource.LoadInitialParams<Key>
+ ): ListenableFuture<InitialResult<Key, Value>> {
+ val future = ResolvableFuture.create<InitialResult<Key, Value>>()
+ executor.execute {
+ val callback = object : LoadInitialCallback<Key, Value>() {
+ override fun onResult(
+ data: List<Value>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: Key?,
+ nextPageKey: Key?
+ ) {
+ future.set(
+ InitialResult(data, position, totalCount, previousPageKey, nextPageKey)
+ )
+ }
+
+ override fun onResult(data: List<Value>, previousPageKey: Key?, nextPageKey: Key?) {
+ future.set(InitialResult(data, previousPageKey, nextPageKey))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadInitial(
+ LoadInitialParams(params.requestedLoadSize, params.placeholdersEnabled),
+ callback
+ )
+ }
+ return future
+ }
+
+ private fun getFutureAsCallback(future: ResolvableFuture<Result<Key, Value>>) =
+ object : LoadCallback<Key, Value>() {
+ override fun onResult(data: List<Value>, adjacentPageKey: Key?) {
+ future.set(Result(data, adjacentPageKey))
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+
+ final override fun loadBefore(
+ params: ListenablePageKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Key, Value>> {
+ val future = ResolvableFuture.create<Result<Key, Value>>()
+ executor.execute {
+ loadBefore(
+ LoadParams(params.key, params.requestedLoadSize),
+ getFutureAsCallback(future)
+ )
+ }
+ return future
+ }
+
+ final override fun loadAfter(
+ params: ListenablePageKeyedDataSource.LoadParams<Key>
+ ): ListenableFuture<Result<Key, Value>> {
+ val future = ResolvableFuture.create<Result<Key, Value>>()
+ executor.execute {
+ loadAfter(LoadParams(params.key, params.requestedLoadSize), getFutureAsCallback(future))
+ }
+ return future
+ }
+
+ /**
+ * Load initial data.
+ *
+ * This method is called first to initialize a PagedList with data. If it's possible to count
+ * the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
+ * the callback via the three-parameter [LoadInitialCallback.onResult]. This enables PagedLists
+ * presenting data from this source to display placeholders to represent unloaded items.
+ *
+ * [LoadInitialParams.requestedLoadSize] is a hint, not a requirement, so it may be may be
+ * altered or ignored.
+ *
+ * @param params Parameters for initial load, including requested load size.
+ * @param callback Callback that receives initial load data.
+ */
+ abstract fun loadInitial(
+ params: LoadInitialParams<Key>,
+ callback: LoadInitialCallback<Key, Value>
+ )
+
+ /**
+ * Prepend page with the key specified by [LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * Data may be passed synchronously during the load method, or deferred and called at a later
+ * time. Further loads going down will be blocked until the callback is called.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @param callback Callback that receives loaded data.
+ */
+ abstract fun loadBefore(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+
+ /**
+ * Append page with the key specified by [LoadParams.key].
+ *
+ * It's valid to return a different list size than the page size if it's easier, e.g. if your
+ * backend defines page sizes. It is generally preferred to increase the number loaded than
+ * reduce.
+ *
+ * Data may be passed synchronously during the load method, or deferred and called at a later
+ * time. Further loads going down will be blocked until the callback is called.
+ *
+ * If data cannot be loaded (for example, if the request is invalid, or the data would be stale
+ * and inconsistent), it is valid to call [invalidate] to invalidate the data source, and
+ * prevent further loading.
+ *
+ * @param params Parameters for the load, including the key for the new page, and requested load
+ * size.
+ * @param callback Callback that receives loaded data.
+ */
+ abstract fun loadAfter(params: LoadParams<Key>, callback: LoadCallback<Key, Value>)
+
+ final override fun <ToValue : Any> mapByPage(
+ function: Function<List<Value>, List<ToValue>>
+ ): PageKeyedDataSource<Key, ToValue> = WrapperPageKeyedDataSource(this, function)
+
+ final override fun <ToValue : Any> map(
+ function: Function<Value, ToValue>
+ ): PageKeyedDataSource<Key, ToValue> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
new file mode 100644
index 0000000..8b2b5f5
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -0,0 +1,1402 @@
+/*
+ * 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.annotation.AnyThread
+import androidx.annotation.IntRange
+import androidx.annotation.MainThread
+import androidx.annotation.RestrictTo
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import androidx.arch.core.util.Function
+import androidx.paging.PagedList.Callback
+import androidx.paging.PagedList.Config
+import androidx.paging.PagedList.Config.Builder
+import androidx.paging.PagedList.Config.Companion.MAX_SIZE_UNBOUNDED
+import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.transform
+import com.google.common.util.concurrent.ListenableFuture
+import java.lang.ref.WeakReference
+import java.util.AbstractList
+import java.util.ArrayList
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+
+/**
+ * Lazy loading list that pages in immutable content from a [DataSource].
+ *
+ * A PagedList is a [List] which loads its data in chunks (pages) from a [DataSource]. Items can be
+ * accessed with [get], and further loading can be triggered with [loadAround]. To display a
+ * PagedList, see [androidx.paging.PagedListAdapter], which enables the binding of a PagedList to a
+ * [androidx.recyclerview.widget.RecyclerView].
+ *
+ * <h4>Loading Data</h4>
+ *
+ * All data in a PagedList is loaded from its [DataSource]. Creating a PagedList loads the
+ * first chunk of data from the DataSource immediately, and should for this reason be done on a
+ * background thread. The constructed PagedList may then be passed to and used on the UI thread.
+ * This is done to prevent passing a list with no loaded content to the UI thread, which should
+ * generally not be presented to the user.
+ *
+ * A [PagedList] initially presents this first partial load as its content, and expands over time as
+ * content is loaded in. When [loadAround] is called, items will be loaded in near the passed
+ * list index. If placeholder `null`s are present in the list, they will be replaced as
+ * content is loaded. If not, newly loaded items will be inserted at the beginning or end of the
+ * list.
+ *
+ * [PagedList] can present data for an unbounded, infinite scrolling list, or a very large but
+ * countable list. Use [Config] to control how many items a [PagedList] loads, and when.
+ *
+ * If you use [androidx.paging.LivePagedListBuilder] to get a [androidx.lifecycle.LiveData], it will
+ * initialize PagedLists on a background thread for you.
+ *
+ * <h4>Placeholders</h4>
+ *
+ * There are two ways that [PagedList] can represent its not-yet-loaded data - with or without
+ * `null` placeholders.
+ *
+ * With placeholders, the [PagedList] is always the full size of the data set. `get(N)` returns
+ * the `N`th item in the data set, or `null` if its not yet loaded.
+ *
+ * Without `null` placeholders, the [PagedList] is the sublist of data that has already been
+ * loaded. The size of the PagedList is the number of currently loaded items, and `get(N)`
+ * returns the `N`th *loaded* item. This is not necessarily the `N`th item in the
+ * data set.
+ *
+ * Placeholders have several benefits:
+ *
+ * * They express the full sized list to the presentation layer (often a
+ * [androidx.paging.PagedListAdapter]), and so can support scrollbars (without jumping as pages are
+ * loaded or dropped) and fast-scrolling to any position, loaded or not.
+ * * They avoid the need for a loading spinner at the end of the loaded list, since the list
+ * is always full sized.
+ *
+ * They also have drawbacks:
+ *
+ * * Your Adapter needs to account for `null` items. This often means providing default
+ * values in data you bind to a [androidx.recyclerview.widget.RecyclerView.ViewHolder].
+ * * They don't work well if your item views are of different sizes, as this will prevent
+ * loading items from cross-fading nicely.
+ * * They require you to count your data set, which can be expensive or impossible, depending
+ * on your [DataSource].
+ *
+ * Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the
+ * [DataSource] does not count its data set in its initial load, or if `false` is passed to
+ * [Config.Builder.setEnablePlaceholders] when building a [Config].
+ *
+ * <h4>Mutability and Snapshots</h4>
+ *
+ * A [PagedList] is *mutable* while loading, or ready to load from its [DataSource].
+ * As loads succeed, a mutable [PagedList] will be updated via Runnables on the main thread. You can
+ * listen to these updates with a [Callback]. (Note that [androidx.paging.PagedListAdapter] will
+ * listen to these to signal RecyclerView about the updates/changes).
+ *
+ * If a [PagedList] attempts to load from an invalid [DataSource], it will [detach] from the
+ * [DataSource], meaning that it will no longer attempt to load data. It will return true from
+ * [isImmutable], and a new [DataSource] / [PagedList] pair must be created to load further data.
+ *
+ * See [DataSource] and [androidx.paging.LivePagedListBuilder] for how new PagedLists are created to
+ * represent changed data.
+ *
+ * A [PagedList] snapshot is simply an immutable shallow copy of the current state of the
+ * [PagedList] as a `List`. It will reference the same inner items, and contain the same `null`
+ * placeholders, if present.
+ *
+ * @param T The type of the entries in the list.
+ */
+abstract class PagedList<T : Any> : AbstractList<T> {
+ internal companion object {
+ /**
+ * Create a [PagedList] which loads data from the provided data source on a background
+ * thread,posting updates to the main thread.
+ *
+ * @param dataSource DataSource providing data to the PagedList
+ * @param notifyExecutor Thread tat will use and consume data from the PagedList. Generally,
+ * this is the UI/main thread.
+ * @param fetchExecutor Data loading will be done via this executor - should be a background
+ * thread.
+ * @param boundaryCallback Optional boundary callback to attach to the list.
+ * @param config PagedList Config, which defines how the PagedList will load data.
+ * @param K Key type that indicates to the DataSource what data to load.
+ * @param T Type of items to be held and loaded by the PagedList.
+ *
+ * @return [ListenableFuture] for newly created [PagedList], which will page in data from
+ * the [DataSource] as needed.
+ */
+ @JvmStatic
+ internal fun <K : Any, T : Any> create(
+ dataSource: DataSource<K, T>,
+ notifyExecutor: Executor,
+ fetchExecutor: Executor,
+ initialLoadExecutor: Executor,
+ boundaryCallback: BoundaryCallback<T>?,
+ config: Config,
+ key: K?
+ ): ListenableFuture<PagedList<T>> {
+ dataSource.initExecutor(initialLoadExecutor)
+
+ val lastLoad = when {
+ dataSource.type == DataSource.KeyType.POSITIONAL && key != null -> key as Int
+ else -> ContiguousPagedList.LAST_LOAD_UNSPECIFIED
+ }
+
+ val params = DataSource.Params(
+ DataSource.LoadType.INITIAL,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize
+ )
+ return dataSource.load(params).transform(
+ Function { initialResult ->
+ dataSource.initExecutor(fetchExecutor)
+ ContiguousPagedList(
+ dataSource,
+ notifyExecutor,
+ fetchExecutor,
+ boundaryCallback,
+ config,
+ initialResult,
+ lastLoad
+ )
+ },
+ DirectExecutor
+ )
+ }
+ }
+
+ /**
+ * Type of load a PagedList can perform.
+ *
+ * You can use a [LoadStateListener] to observe [LoadState] of any [LoadType]. For UI purposes
+ * (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * Listener with the [androidx.paging.PagedListAdapter] or
+ * [androidx.paging.AsyncPagedListDiffer].
+ *
+ * @see LoadState
+ */
+ enum class LoadType {
+ /**
+ * PagedList content being reloaded, may contain content updates.
+ */
+ REFRESH,
+
+ /**
+ * Load at the start of the PagedList.
+ */
+ START,
+
+ /**
+ * Load at the end of the PagedList.
+ */
+ END
+ }
+
+ /**
+ * State of a PagedList load - associated with a `LoadType`
+ *
+ * You can use a [LoadStateListener] to observe [LoadState] of any [LoadType]. For UI purposes
+ * (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * Listener with the `PagedListAdapter` or `AsyncPagedListDiffer`.
+ */
+ enum class LoadState {
+ /**
+ * Indicates the PagedList is not currently loading, and no error currently observed.
+ */
+ IDLE,
+
+ /**
+ * Loading is in progress.
+ */
+ LOADING,
+
+ /**
+ * Loading is complete.
+ */
+ DONE,
+
+ /**
+ * Loading hit a non-retryable error.
+ */
+ ERROR,
+
+ /**
+ * Loading hit a retryable error.
+ *
+ * @see .retry
+ */
+ RETRYABLE_ERROR
+ }
+
+ /**
+ * Listener for changes to loading state - whether the refresh, prepend, or append is idle,
+ * loading, or has an error.
+ *
+ * Can be used to observe the [LoadState] of any [LoadType] (REFRESH/START/END). For UI purposes
+ * (swipe refresh, loading spinner, retry button), this is typically done by registering a
+ * Listener with the [PagedListAdapter] or [AsyncPagedListDiffer].
+ *
+ * These calls will be dispatched on the executor defined by [Builder.setNotifyExecutor], which
+ * is generally the main/UI thread.
+ *
+ * @see LoadType
+ *
+ * @see LoadState
+ */
+ interface LoadStateListener {
+ /**
+ * Called when the LoadState has changed - whether the refresh, prepend, or append is idle,
+ * loading, or has an error.
+ *
+ * REFRESH events can be used to drive a
+ * [androidx.swiperefreshlayout.widget.SwipeRefreshLayout], or START/END events can be used
+ * to drive loading spinner items in your `RecyclerView`.
+ *
+ * @param type Type of load - START, END, or REFRESH.
+ * @param state State of load - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR
+ * @param error Error, if in an error state, null otherwise.
+ *
+ * @see [retry]
+ */
+ fun onLoadStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+ }
+
+ /**
+ * Builder class for [PagedList].
+ *
+ * [DataSource], [Config], main thread and background executor must all be provided.
+ *
+ * A [PagedList] queries initial data from its [DataSource] during construction, to avoid empty
+ * PagedLists being presented to the UI when possible. It's preferred to present initial data,
+ * so that the UI doesn't show an empty list, or placeholders for a few frames, just before
+ * showing initial content.
+ *
+ * [androidx.paging.LivePagedListBuilder] does this creation on a background thread
+ * automatically, if you want to receive a `LiveData<PagedList<...>>`.
+ *
+ * @param Key Type of key used to load data from the [DataSource].
+ * @param Value Type of items held and loaded by the [PagedList].
+ */
+ class Builder<Key : Any, Value : Any> {
+ private val dataSource: DataSource<Key, Value>
+ private val config: Config
+ private var notifyExecutor: Executor? = null
+ private var fetchExecutor: Executor? = null
+ private var boundaryCallback: BoundaryCallback<Value>? = null
+ private var initialKey: Key? = null
+
+ /**
+ * Create a PagedList.Builder with the provided [DataSource] and [Config].
+ *
+ * @param dataSource [DataSource] the [PagedList] will load from.
+ * @param config [Config] that defines how the [PagedList] loads data from its [DataSource].
+ */
+ constructor(dataSource: DataSource<Key, Value>, config: Config) {
+ this.dataSource = dataSource
+ this.config = config
+ }
+
+ /**
+ * Create a [PagedList.Builder] with the provided [DataSource] and page size.
+ *
+ * This method is a convenience for:
+ * ```
+ * PagedList.Builder(dataSource,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build());
+ * ```
+ *
+ * @param dataSource [DataSource] the [PagedList] will load from.
+ * @param pageSize [Config] that defines how the [PagedList] loads data from its
+ * [DataSource].
+ */
+ constructor(dataSource: DataSource<Key, Value>, pageSize: Int) : this(
+ dataSource,
+ PagedList.Config.Builder().setPageSize(pageSize).build()
+ )
+
+ /**
+ * The executor defining where page loading updates are dispatched.
+ *
+ * @param notifyExecutor Executor that receives [PagedList] updates, and where [Callback]
+ * calls are dispatched. Generally, this is the ui/main thread.
+ * @return this
+ */
+ fun setNotifyExecutor(notifyExecutor: Executor) = apply {
+ this.notifyExecutor = notifyExecutor
+ }
+
+ /**
+ * The executor used to fetch additional pages from the [DataSource].
+ *
+ * Does not affect initial load, which will be done immediately on whichever thread the
+ * [PagedList] is created on.
+ *
+ * @param fetchExecutor [Executor] used to fetch from [DataSources], generally a background
+ * thread pool for e.g. I/O or network loading.
+ * @return this
+ */
+ fun setFetchExecutor(fetchExecutor: Executor) = apply {
+ this.fetchExecutor = fetchExecutor
+ }
+
+ /**
+ * The [BoundaryCallback] for out of data events.
+ *
+ * Pass a [BoundaryCallback] to listen to when the [PagedList] runs out of data to load.
+ *
+ * @param boundaryCallback [BoundaryCallback] for listening to out-of-data events.
+ * @return this
+ */
+ fun setBoundaryCallback(boundaryCallback: BoundaryCallback<Value>?) = apply {
+ this.boundaryCallback = boundaryCallback
+ }
+
+ /**
+ * Sets the initial key the [DataSource] should load around as part of initialization.
+ *
+ * @param initialKey Key the [DataSource] should load around as part of initialization.
+ * @return this
+ */
+ fun setInitialKey(initialKey: Key?) = apply {
+ this.initialKey = initialKey
+ }
+
+ /**
+ * Creates a [PagedList] with the given parameters.
+ *
+ * This call will dispatch the [androidx.paging.DataSource]'s loadInitial method immediately
+ * on the current thread, and block the current on the result. This method should always be
+ * called on a worker thread to prevent blocking the main thread.
+ *
+ * It's fine to create a PagedList with an async DataSource on the main thread, such as in
+ * the constructor of a ViewModel. An async network load won't block the initialLoad
+ * function. For a synchronous DataSource such as one created from a Room database, a
+ * `LiveData<PagedList>` can be safely constructed with
+ * [androidx.paging.LivePagedListBuilder] on the main thread, since actual construction work
+ * is deferred, and done on a background thread.
+ *
+ * While build() will always return a [PagedList], it's important to note that the
+ * [PagedList] initial load may fail to acquire data from the [DataSource]. This can happen
+ * for example if the [DataSource] is invalidated during its initial load. If this happens,
+ * the [PagedList] will be immediately [detached][PagedList.isDetached], and you can retry
+ * construction (including setting a new [DataSource]).
+ *
+ * @return The newly constructed [PagedList]
+ */
+ @WorkerThread
+ @Deprecated(
+ "This method has no means of handling errors encountered during initial load, and" +
+ " blocks on the initial load result. Use {@link #buildAsync()} instead."
+ )
+ fun build(): PagedList<Value> {
+ // TODO: define defaults, once they can be used in module without android dependency
+ if (notifyExecutor == null) {
+ throw IllegalArgumentException("MainThreadExecutor required")
+ }
+ if (fetchExecutor == null) {
+ throw IllegalArgumentException("BackgroundThreadExecutor required")
+ }
+
+ try {
+ return create(DirectExecutor).get()
+ } catch (e: InterruptedException) {
+ throw RuntimeException(e)
+ } catch (e: ExecutionException) {
+ throw RuntimeException(e)
+ }
+ }
+
+ /**
+ * Creates a [PagedList] asynchronously with the given parameters.
+ *
+ * This call will dispatch the [DataSource]'s loadInitial method immediately, and
+ * return a `ListenableFuture<PagedList<T>>` that will resolve (triggering listeners)
+ * once the initial load is completed (success or failure).
+ *
+ * @return The newly constructed PagedList
+ */
+ @Suppress("unused")
+ fun buildAsync(): ListenableFuture<PagedList<Value>> {
+ // TODO: define defaults, once they can be used in module without android dependency
+ if (notifyExecutor == null) {
+ throw IllegalArgumentException("MainThreadExecutor required")
+ }
+ if (fetchExecutor == null) {
+ throw IllegalArgumentException("BackgroundThreadExecutor required")
+ }
+
+ return create(fetchExecutor!!)
+ }
+
+ private fun create(initialFetchExecutor: Executor): ListenableFuture<PagedList<Value>> =
+ create(
+ dataSource,
+ notifyExecutor!!,
+ fetchExecutor!!,
+ initialFetchExecutor,
+ boundaryCallback,
+ config,
+ initialKey
+ )
+ }
+
+ /**
+ * Callback signaling when content is loaded into the list.
+ *
+ * Can be used to listen to items being paged in and out. These calls will be dispatched on
+ * the executor defined by [Builder.setNotifyExecutor], which is generally the main/UI thread.
+ */
+ abstract class Callback {
+ /**
+ * Called when null padding items have been loaded to signal newly available data, or when
+ * data that hasn't been used in a while has been dropped, and swapped back to null.
+ *
+ * @param position Position of first newly loaded items, out of total number of items
+ * (including padded nulls).
+ * @param count Number of items loaded.
+ */
+ abstract fun onChanged(position: Int, count: Int)
+
+ /**
+ * Called when new items have been loaded at the end or beginning of the list.
+ *
+ * @param position Position of the first newly loaded item (in practice, either `0` or
+ * `size - 1`.
+ * @param count Number of items loaded.
+ */
+ abstract fun onInserted(position: Int, count: Int)
+
+ /**
+ * Called when items have been removed at the end or beginning of the list, and have not
+ * been replaced by padded nulls.
+ *
+ * @param position Position of the first newly loaded item (in practice, either `0` or
+ * `size - 1`.
+ * @param count Number of items loaded.
+ */
+ abstract fun onRemoved(position: Int, count: Int)
+ }
+
+ /**
+ * Configures how a [PagedList] loads content from its [DataSource].
+ *
+ * Use [Config.Builder] to construct and define custom loading behavior, such as
+ * [Builder.setPageSize], which defines number of items loaded at a time}.
+ */
+ open class Config internal constructor(
+ /**
+ * Size of each page loaded by the PagedList.
+ */
+ @JvmField
+ val pageSize: Int,
+ /**
+ * Prefetch distance which defines how far ahead to load.
+ *
+ * If this value is set to 50, the paged list will attempt to load 50 items in advance of
+ * data that's already been accessed.
+ *
+ * @see PagedList.loadAround
+ */
+ @JvmField
+ val prefetchDistance: Int,
+ /**
+ * Defines whether the PagedList may display null placeholders, if the DataSource provides
+ * them.
+ */
+ @JvmField
+ val enablePlaceholders: Boolean,
+ /**
+ * Size hint for initial load of PagedList, often larger than a regular page.
+ */
+ @JvmField
+ val initialLoadSizeHint: Int,
+ /**
+ * Defines the maximum number of items that may be loaded into this pagedList before pages
+ * should be dropped.
+ *
+ * [PageKeyedDataSource] does not currently support dropping pages - when loading from a
+ * [PageKeyedDataSource], this value is ignored.
+ *
+ * @see MAX_SIZE_UNBOUNDED
+ * @see Builder.setMaxSize
+ */
+ @JvmField
+ val maxSize: Int
+ ) {
+ /**
+ * Builder class for [Config].
+ *
+ * You must at minimum specify page size with [setPageSize].
+ */
+ class Builder {
+ private var pageSize = -1
+ private var prefetchDistance = -1
+ private var initialLoadSizeHint = -1
+ private var enablePlaceholders = true
+ private var maxSize = MAX_SIZE_UNBOUNDED
+
+ /**
+ * Defines the number of items loaded at once from the [DataSource].
+ *
+ * Should be several times the number of visible items onscreen.
+ *
+ * Configuring your page size depends on how your data is being loaded and used. Smaller
+ * page sizes improve memory usage, latency, and avoid GC churn. Larger pages generally
+ * improve loading throughput, to a point (avoid loading more than 2MB from SQLite at
+ * once, since it incurs extra cost).
+ *
+ * If you're loading data for very large, social-media style cards that take up most of
+ * a screen, and your database isn't a bottleneck, 10-20 may make sense. If you're
+ * displaying dozens of items in a tiled grid, which can present items during a scroll
+ * much more quickly, consider closer to 100.
+ *
+ * @param pageSize Number of items loaded at once from the [DataSource].
+ * @return this
+ */
+ fun setPageSize(@IntRange(from = 1) pageSize: Int) = apply {
+ if (pageSize < 1) {
+ throw IllegalArgumentException("Page size must be a positive number")
+ }
+ this.pageSize = pageSize
+ }
+
+ /**
+ * Defines how far from the edge of loaded content an access must be to trigger further
+ * loading.
+ *
+ * Should be several times the number of visible items onscreen.
+ *
+ * If not set, defaults to page size.
+ *
+ * A value of 0 indicates that no list items will be loaded until they are specifically
+ * requested. This is generally not recommended, so that users don't observe a
+ * placeholder item (with placeholders) or end of list (without) while scrolling.
+ *
+ * @param prefetchDistance Distance the [PagedList] should prefetch.
+ * @return this
+ */
+ fun setPrefetchDistance(@IntRange(from = 0) prefetchDistance: Int) = apply {
+ this.prefetchDistance = prefetchDistance
+ }
+
+ /**
+ * Pass false to disable null placeholders in [PagedLists] using this [Config].
+ *
+ * If not set, defaults to true.
+ *
+ * A [PagedList] will present null placeholders for not-yet-loaded content if two
+ * conditions are met:
+ *
+ * 1) Its [DataSource] can count all unloaded items (so that the number of nulls to
+ * present is known).
+ *
+ * 2) placeholders are not disabled on the [Config].
+ *
+ * Call `setEnablePlaceholders(false)` to ensure the receiver of the PagedList
+ * (often a [androidx.paging.PagedListAdapter]) doesn't need to account for null items.
+ *
+ * If placeholders are disabled, not-yet-loaded content will not be present in the list.
+ * Paging will still occur, but as items are loaded or removed, they will be signaled
+ * as inserts to the [PagedList.Callback].
+ *
+ * [PagedList.Callback.onChanged] will not be issued as part of loading, though a
+ * [androidx.paging.PagedListAdapter] may still receive change events as a result of
+ * [PagedList] diffing.
+ *
+ * @param enablePlaceholders `false` if null placeholders should be disabled.
+ * @return this
+ */
+ fun setEnablePlaceholders(enablePlaceholders: Boolean) = apply {
+ this.enablePlaceholders = enablePlaceholders
+ }
+
+ /**
+ * Defines how many items to load when first load occurs.
+ *
+ * This value is typically larger than page size, so on first load data there's a large
+ * enough range of content loaded to cover small scrolls.
+ *
+ * When using a [PositionalDataSource], the initial load size will be coerced to an
+ * integer multiple of pageSize, to enable efficient tiling.
+ *
+ * If not set, defaults to three times page size.
+ *
+ * @param initialLoadSizeHint Number of items to load while initializing the
+ * [PagedList].
+ * @return this
+ */
+ fun setInitialLoadSizeHint(@IntRange(from = 1) initialLoadSizeHint: Int) = apply {
+ this.initialLoadSizeHint = initialLoadSizeHint
+ }
+
+ /**
+ * Defines how many items to keep loaded at once.
+ *
+ * This can be used to cap the number of items kept in memory by dropping pages. This
+ * value is typically many pages so old pages are cached in case the user scrolls back.
+ *
+ * This value must be at least two times the [prefetchDistance][setPrefetchDistance]
+ * plus the [pageSize][setPageSize]). This constraint prevent loads from being
+ * continuously fetched and discarded due to prefetching.
+ *
+ * The max size specified here best effort, not a guarantee. In practice, if [maxSize]
+ * is many times the page size, the number of items held by the [PagedList] will not
+ * grow above this number. Exceptions are made as necessary to guarantee:
+ * * Pages are never dropped until there are more than two pages loaded. Note that
+ * a [DataSource] may not be held strictly to [requested pageSize][Config.pageSize], so
+ * two pages may be larger than expected.
+ * * Pages are never dropped if they are within a prefetch window (defined to be
+ * `pageSize + (2 * prefetchDistance)`) of the most recent load.
+ *
+ * [PageKeyedDataSource] does not currently support dropping pages - when
+ * loading from a [PageKeyedDataSource], this value is ignored.
+ *
+ * If not set, defaults to [MAX_SIZE_UNBOUNDED], which disables page dropping.
+ *
+ * @param maxSize Maximum number of items to keep in memory, or [MAX_SIZE_UNBOUNDED] to
+ * disable page dropping.
+ * @return this
+ *
+ * @see Config.MAX_SIZE_UNBOUNDED
+ * @see Config.maxSize
+ */
+ fun setMaxSize(@IntRange(from = 2) maxSize: Int) = apply {
+ this.maxSize = maxSize
+ }
+
+ /**
+ * Creates a [Config] with the given parameters.
+ *
+ * @return A new [Config].
+ */
+ fun build(): Config {
+ if (prefetchDistance < 0) {
+ prefetchDistance = pageSize
+ }
+ if (initialLoadSizeHint < 0) {
+ initialLoadSizeHint = pageSize * DEFAULT_INITIAL_PAGE_MULTIPLIER
+ }
+ if (!enablePlaceholders && prefetchDistance == 0) {
+ throw IllegalArgumentException(
+ "Placeholders and prefetch are the only ways" +
+ " to trigger loading of more data in the PagedList, so either" +
+ " placeholders must be enabled, or prefetch distance must be > 0."
+ )
+ }
+ if (maxSize != MAX_SIZE_UNBOUNDED && maxSize < pageSize + prefetchDistance * 2) {
+ throw IllegalArgumentException(
+ "Maximum size must be at least pageSize + 2*prefetchDist" +
+ ", pageSize=$pageSize, prefetchDist=$prefetchDistance" +
+ ", maxSize=$maxSize"
+ )
+ }
+
+ return Config(
+ pageSize,
+ prefetchDistance,
+ enablePlaceholders,
+ initialLoadSizeHint,
+ maxSize
+ )
+ }
+
+ internal companion object {
+ internal const val DEFAULT_INITIAL_PAGE_MULTIPLIER = 3
+ }
+ }
+
+ internal companion object {
+ /**
+ * When [maxSize] is set to [MAX_SIZE_UNBOUNDED], the maximum number of items loaded is
+ * unbounded, and pages will never be dropped.
+ */
+ const val MAX_SIZE_UNBOUNDED = Int.MAX_VALUE
+ }
+ }
+
+ /**
+ * Signals when a PagedList has reached the end of available data.
+ *
+ * When local storage is a cache of network data, it's common to set up a streaming pipeline:
+ * Network data is paged into the database, database is paged into UI. Paging from the database
+ * to UI can be done with a `LiveData<PagedList>`, but it's still necessary to know when to
+ * trigger network loads.
+ *
+ * [BoundaryCallback] does this signaling - when a DataSource runs out of data at the end of
+ * the list, [onItemAtEndLoaded] is called, and you can start an async network load that will
+ * write the result directly to the database. Because the database is being observed, the UI
+ * bound to the `LiveData<PagedList>` will update automatically to account for the new items.
+ *
+ * Note that a BoundaryCallback instance shared across multiple PagedLists (e.g. when passed to
+ * [androidx.paging.LivePagedListBuilder.setBoundaryCallback], the callbacks may be issued
+ * multiple times. If for example [onItemAtEndLoaded] triggers a network load, it should avoid
+ * triggering it again while the load is ongoing.
+ *
+ * The database + network Repository in the
+ * [PagingWithNetworkSample](https://github.com/googlesamples/android-architecture-components/blob/master/PagingWithNetworkSample/README.md)
+ * shows how to implement a network BoundaryCallback using
+ * [Retrofit](https://square.github.io/retrofit/), while handling swipe-to-refresh,
+ * network errors, and retry.
+ *
+ * <h4>Requesting Network Data</h4>
+ * [BoundaryCallback] only passes the item at front or end of the list when out of data. This
+ * makes it an easy fit for item-keyed network requests, where you can use the item passed to
+ * the [BoundaryCallback] to request more data from the network. In these cases, the source of
+ * truth for next page to load is coming from local storage, based on what's already loaded.
+ *
+ * If you aren't using an item-keyed network API, you may be using page-keyed, or page-indexed.
+ * If this is the case, the paging library doesn't know about the page key or index used in the
+ * [BoundaryCallback], so you need to track it yourself. You can do this in one of two ways:
+ *
+ * <h5>Local storage Page key</h5>
+ * If you want to perfectly resume your query, even if the app is killed and resumed, you can
+ * store the key on disk. Note that with a positional/page index network API, there's a simple
+ * way to do this, by using the `listSize` as an input to the next load (or
+ * `listSize / NETWORK_PAGE_SIZE`, for page indexing).
+ *
+ * The current list size isn't passed to the BoundaryCallback though. This is because the
+ * PagedList doesn't necessarily know the number of items in local storage. Placeholders may be
+ * disabled, or the DataSource may not count total number of items.
+ *
+ * Instead, for these positional cases, you can query the database for the number of items, and
+ * pass that to the network.
+ * <h5>In-Memory Page key</h5>
+ * Often it doesn't make sense to query the next page from network if the last page you fetched
+ * was loaded many hours or days before. If you keep the key in memory, you can refresh any time
+ * you start paging from a network source.
+ *
+ * Store the next key in memory, inside your BoundaryCallback. When you create a new
+ * BoundaryCallback when creating a new `LiveData`/`Observable` of
+ * `PagedList`, refresh data. For example,
+ * [in the Paging Codelab](https://codelabs.developers.google.com/codelabs/android-paging/index.html#8),
+ * the GitHub network page index is stored in memory.
+ *
+ * @param T Type loaded by the PagedList.
+ */
+ @MainThread
+ abstract class BoundaryCallback<T> {
+ /**
+ * Called when zero items are returned from an initial load of the PagedList's data source.
+ */
+ open fun onZeroItemsLoaded() {}
+
+ /**
+ * Called when the item at the front of the PagedList has been loaded, and access has
+ * occurred within [Config.prefetchDistance] of it.
+ *
+ * No more data will be prepended to the PagedList before this item.
+ *
+ * @param itemAtFront The first item of PagedList
+ */
+ open fun onItemAtFrontLoaded(itemAtFront: T) {}
+
+ /**
+ * Called when the item at the end of the PagedList has been loaded, and access has
+ * occurred within [Config.prefetchDistance] of it.
+ *
+ * No more data will be appended to the [PagedList] after this item.
+ *
+ * @param itemAtEnd The first item of [PagedList]
+ */
+ open fun onItemAtEndLoaded(itemAtEnd: T) {}
+ }
+
+ internal abstract class LoadStateManager {
+ var refresh = LoadState.IDLE
+ private set
+ private var mRefreshError: Throwable? = null
+ var start = LoadState.IDLE
+ private set
+ private var mStartError: Throwable? = null
+ var end = LoadState.IDLE
+ private set
+ private var mEndError: Throwable? = null
+
+ fun setState(type: LoadType, state: LoadState, error: Throwable?) {
+ val expectError = state == LoadState.RETRYABLE_ERROR || state == LoadState.ERROR
+ val hasError = error != null
+ if (expectError != hasError) {
+ throw IllegalArgumentException(
+ "Error states must be accompanied by a throwable, other states must not"
+ )
+ }
+
+ // deduplicate signals
+ when (type) {
+ LoadType.REFRESH -> {
+ if (refresh == state && mRefreshError == error) return
+ refresh = state
+ mRefreshError = error
+ }
+ LoadType.START -> {
+ if (start == state && mStartError == error) return
+ start = state
+ mStartError = error
+ }
+ LoadType.END -> {
+ if (end == state && mEndError == error) return
+ end = state
+ mEndError = error
+ }
+ }
+ onStateChanged(type, state, error)
+ }
+
+ protected abstract fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+
+ fun dispatchCurrentLoadState(listener: LoadStateListener) {
+ listener.onLoadStateChanged(LoadType.REFRESH, refresh, mRefreshError)
+ listener.onLoadStateChanged(LoadType.START, start, mStartError)
+ listener.onLoadStateChanged(LoadType.END, end, mEndError)
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ constructor(
+ storage: PagedStorage<T>,
+ mainThreadExecutor: Executor,
+ backgroundThreadExecutor: Executor,
+ boundaryCallback: BoundaryCallback<T>?,
+ config: Config
+ ) : super() {
+ this.storage = storage
+ this.mainThreadExecutor = mainThreadExecutor
+ this.backgroundThreadExecutor = backgroundThreadExecutor
+ this.boundaryCallback = boundaryCallback
+ this.config = config
+ this.callbacks = ArrayList()
+ this.listeners = ArrayList()
+ requiredRemainder = this.config.prefetchDistance * 2 + this.config.pageSize
+ }
+
+ internal val storage: PagedStorage<T>
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ protected fun getStorage() = storage
+
+ internal val mainThreadExecutor: Executor
+ internal val backgroundThreadExecutor: Executor
+ internal val boundaryCallback: BoundaryCallback<T>?
+
+ internal var refreshRetryCallback: Runnable? = null
+
+ /**
+ * Last access location, in total position space (including offset).
+ *
+ * Used by positional data sources to initialize loading near viewport
+ */
+ internal var lastLoad = 0
+ internal var lastItem: T? = null
+
+ internal val requiredRemainder: Int
+
+ /**
+ * Return the Config used to construct this PagedList.
+ *
+ * @return the Config of this PagedList
+ */
+ open val config: Config
+
+ private val callbacks: MutableList<WeakReference<Callback>>
+
+ private val listeners: MutableList<WeakReference<LoadStateListener>>
+
+ // if set to true, boundaryCallback is non-null, and should
+ // be dispatched when nearby load has occurred
+ private var boundaryCallbackBeginDeferred = false
+
+ private var boundaryCallbackEndDeferred = false
+
+ // lowest and highest index accessed by loadAround. Used to
+ // decide when boundaryCallback should be dispatched
+ private var lowestIndexAccessed = Int.MAX_VALUE
+ private var highestIndexAccessed = Int.MIN_VALUE
+
+ /**
+ * Size of the list, including any placeholders (not-yet-loaded null padding).
+ *
+ * To get the number of loaded items, not counting placeholders, use [loadedCount].
+ *
+ * @see loadedCount
+ */
+ override val size
+ get() = storage.size
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ abstract val isContiguous: Boolean
+
+ /**
+ * The [DataSource] that provides data to this [PagedList].
+ */
+ abstract val dataSource: DataSource<*, T>
+
+ /**
+ * Return the key for the position passed most recently to [loadAround].
+ *
+ * When a PagedList is invalidated, you can pass the key returned by this function to initialize
+ * the next PagedList. This ensures (depending on load times) that the next PagedList that
+ * arrives will have data that overlaps. If you use androidx.paging.LivePagedListBuilder, it
+ * will do this for you.
+ *
+ * @return Key of position most recently passed to [loadAround].
+ */
+ abstract val lastKey: Any?
+
+ /**
+ * True if the [PagedList] has detached the [DataSource] it was loading from, and will no longer
+ * load new data.
+ *
+ * A detached list is [immutable][isImmutable].
+ *
+ * @return True if the data source is detached.
+ */
+ abstract val isDetached: Boolean
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ abstract fun dispatchCurrentLoadState(listener: LoadStateListener)
+
+ /**
+ * Dispatch updates since the non-empty snapshot was taken.
+ *
+ * @param snapshot Non-empty snapshot.
+ * @param callback [Callback] for updates that have occurred since snapshot.
+ *
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ abstract fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<T>, callback: Callback)
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ abstract fun loadAroundInternal(index: Int)
+
+ /**
+ * Detach the PagedList from its DataSource, and attempt to load no more data.
+ *
+ * This is called automatically when a DataSource is observed to be invalid, which is a
+ * signal to stop loading. The PagedList will continue to present existing data, but will not
+ * initiate new loads.
+ */
+ abstract fun detach()
+
+ /**
+ * Returns the number of items loaded in the [PagedList].
+ *
+ * Unlike [size] this counts only loaded items, not placeholders.
+ *
+ * If placeholders are [disabled][Config.enablePlaceholders], this method is equivalent to
+ * [size].
+ *
+ * @return Number of items currently loaded, not counting placeholders.
+ *
+ * @see size
+ */
+ open val loadedCount
+ get() = storage.loadedCount
+
+ /**
+ * Returns whether the list is immutable.
+ *
+ * Immutable lists may not become mutable again, and may safely be accessed from any thread.
+ *
+ * In the future, this method may return true when a PagedList has completed loading from its
+ * DataSource. Currently, it is equivalent to [isDetached].
+ *
+ * @return `true` if the [PagedList] is immutable.
+ */
+ open val isImmutable
+ get() = isDetached
+
+ /**
+ * Position offset of the data in the list.
+ *
+ * If data is supplied by a [PositionalDataSource], the item returned from `get(i)` has a
+ * position of `i + getPositionOffset()`.
+ *
+ * If the DataSource is a [ItemKeyedDataSource] or [PageKeyedDataSource], it doesn't use
+ * positions, returns 0.
+ */
+ open val positionOffset: Int
+ get() = storage.positionOffset
+
+ internal open fun setInitialLoadState(loadState: LoadState, error: Throwable?) {}
+
+ /**
+ * Retry any retryable errors associated with this [PagedList].
+ *
+ * If for example a network DataSource append timed out, calling this method will retry the
+ * failed append load. Note that your DataSource will need to pass `true` to `onError()` to
+ * signify the error as retryable.
+ *
+ * You can observe loading state via [addWeakLoadStateListener], though generally this is done
+ * through the [PagedListAdapter][androidx.paging.PagedListAdapter] or
+ * [AsyncPagedListDiffer][androidx.paging.AsyncPagedListDiffer].
+ *
+ * @see addWeakLoadStateListener
+ * @see removeWeakLoadStateListener
+ */
+ open fun retry() {}
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun setRetryCallback(refreshRetryCallback: Runnable?) {
+ this.refreshRetryCallback = refreshRetryCallback
+ }
+
+ internal fun dispatchStateChange(type: LoadType, state: LoadState, error: Throwable?) {
+ for (i in listeners.indices.reversed()) {
+ val currentListener = listeners[i].get()
+ currentListener?.onLoadStateChanged(type, state, error) ?: listeners.removeAt(i)
+ }
+ }
+
+ /**
+ * Get the item in the list of loaded items at the provided index.
+ *
+ * @param index Index in the loaded item list. Must be >= 0, and < [size]
+ * @return The item at the passed index, or `null` if a `null` placeholder is at the specified
+ * position.
+ *
+ * @see size
+ */
+ override fun get(index: Int): T? {
+ val item = storage[index]
+ if (item != null) {
+ lastItem = item
+ }
+ return item
+ }
+
+ /**
+ * Load adjacent items to passed index.
+ *
+ * @param index Index at which to load.
+ */
+ open fun loadAround(index: Int) {
+ if (index < 0 || index >= size) {
+ throw IndexOutOfBoundsException("Index: $index, Size: $size")
+ }
+
+ lastLoad = index + positionOffset
+ loadAroundInternal(index)
+
+ lowestIndexAccessed = minOf(lowestIndexAccessed, index)
+ highestIndexAccessed = maxOf(highestIndexAccessed, index)
+
+ /*
+ * lowestIndexAccessed / highestIndexAccessed have been updated, so check if we need to
+ * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
+ * and accesses happen near the boundaries.
+ *
+ * Note: we post here, since RecyclerView may want to add items in response, and this
+ * call occurs in PagedListAdapter bind.
+ */
+ tryDispatchBoundaryCallbacks(true)
+ }
+
+ // Creation thread for initial synchronous load, otherwise main thread
+ // Safe to access main thread only state - no other thread has reference during construction
+ @AnyThread
+ internal fun deferBoundaryCallbacks(
+ deferEmpty: Boolean,
+ deferBegin: Boolean,
+ deferEnd: Boolean
+ ) {
+ if (boundaryCallback == null) {
+ throw IllegalStateException("Can't defer BoundaryCallback, no instance")
+ }
+
+ /*
+ * If lowest/highest haven't been initialized, set them to storage size,
+ * since placeholders must already be computed by this point.
+ *
+ * This is just a minor optimization so that BoundaryCallback callbacks are sent immediately
+ * if the initial load size is smaller than the prefetch window (see
+ * TiledPagedListTest#boundaryCallback_immediate())
+ */
+ if (lowestIndexAccessed == Int.MAX_VALUE) {
+ lowestIndexAccessed = storage.size
+ }
+ if (highestIndexAccessed == Int.MIN_VALUE) {
+ highestIndexAccessed = 0
+ }
+
+ if (deferEmpty || deferBegin || deferEnd) {
+ // Post to the main thread, since we may be on creation thread currently
+ mainThreadExecutor.execute {
+ // on is dispatched immediately, since items won't be accessed
+
+ if (deferEmpty) {
+ boundaryCallback.onZeroItemsLoaded()
+ }
+
+ // for other callbacks, mark deferred, and only dispatch if loadAround
+ // has been called near to the position
+ if (deferBegin) {
+ boundaryCallbackBeginDeferred = true
+ }
+ if (deferEnd) {
+ boundaryCallbackEndDeferred = true
+ }
+ tryDispatchBoundaryCallbacks(false)
+ }
+ }
+ }
+
+ /**
+ * Call this when lowest/HighestIndexAccessed are changed, or boundaryCallbackBegin/EndDeferred
+ * is set.
+ */
+ @Suppress("MemberVisibilityCanBePrivate") // synthetic access
+ internal fun tryDispatchBoundaryCallbacks(post: Boolean) {
+ val dispatchBegin =
+ boundaryCallbackBeginDeferred && lowestIndexAccessed <= config.prefetchDistance
+ val dispatchEnd = boundaryCallbackEndDeferred &&
+ highestIndexAccessed >= size - 1 - config.prefetchDistance
+
+ if (!dispatchBegin && !dispatchEnd) {
+ return
+ }
+
+ if (dispatchBegin) {
+ boundaryCallbackBeginDeferred = false
+ }
+ if (dispatchEnd) {
+ boundaryCallbackEndDeferred = false
+ }
+ if (post) {
+ mainThreadExecutor.execute { dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd) }
+ } else {
+ dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd)
+ }
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate") // synthetic access
+ internal fun dispatchBoundaryCallbacks(begin: Boolean, end: Boolean) {
+ // safe to deref boundaryCallback here, since we only defer if boundaryCallback present
+ if (begin) {
+ boundaryCallback!!.onItemAtFrontLoaded(storage.firstLoadedItem!!)
+ }
+ if (end) {
+ boundaryCallback!!.onItemAtEndLoaded(storage.lastLoadedItem!!)
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+ internal fun offsetAccessIndices(offset: Int) {
+ // update last loadAround index
+ lastLoad += offset
+
+ // update access range
+ lowestIndexAccessed += offset
+ highestIndexAccessed += offset
+ }
+
+ /**
+ * Returns an immutable snapshot of the [PagedList] in its current state.
+ *
+ * If this [PagedList] [is immutable][isImmutable] due to its DataSource being invalid, it will
+ * be returned.
+ *
+ * @return Immutable snapshot of PagedList data.
+ */
+ open fun snapshot(): List<T> = when {
+ isImmutable -> this
+ else -> SnapshotPagedList(this)
+ }
+
+ /**
+ * Add a [LoadStateListener] to observe the loading state of the [PagedList].
+ *
+ * @param listener Listener to receive updates.
+ *
+ * @see removeWeakLoadStateListener
+ */
+ open fun addWeakLoadStateListener(listener: LoadStateListener) {
+ // first, clean up any empty weak refs
+ for (i in listeners.indices.reversed()) {
+ val currentListener = listeners[i].get()
+ if (currentListener == null) {
+ listeners.removeAt(i)
+ }
+ }
+
+ // then add the new one
+ listeners.add(WeakReference(listener))
+ dispatchCurrentLoadState(listener)
+ }
+
+ /**
+ * Remove a previously registered [LoadStateListener].
+ *
+ * @param listener Previously registered listener.
+ * @see addWeakLoadStateListener
+ */
+ open fun removeWeakLoadStateListener(listener: LoadStateListener) {
+ for (i in listeners.indices.reversed()) {
+ val currentListener = listeners[i].get()
+ if (currentListener == null || currentListener === listener) {
+ // found Listener, or empty weak ref
+ listeners.removeAt(i)
+ }
+ }
+ }
+
+ /**
+ * Adds a callback, and issues updates since the [previousSnapshot] was created.
+ *
+ * If [previousSnapshot] is passed, the [callback] will also immediately be dispatched any
+ * differences between the previous snapshot, and the current state. For example, if the
+ * previousSnapshot was of 5 nulls, 10 items, 5 nulls, and the current state was 5 nulls,
+ * 12 items, 3 nulls, the callback would immediately receive a call of`onChanged(14, 2)`.
+ *
+ * This allows an observer that's currently presenting a snapshot to catch up to the most recent
+ * version, including any changes that may have been made.
+ *
+ * The callback is internally held as weak reference, so [PagedList] doesn't hold a strong
+ * reference to its observer, such as a [androidx.paging.PagedListAdapter]. If an adapter were
+ * held with a strong reference, it would be necessary to clear its [PagedList] observer before
+ * it could be GC'd.
+ *
+ * @param previousSnapshot Snapshot previously captured from this List, or `null`.
+ * @param callback Callback to dispatch to.
+ *
+ * @see removeWeakCallback
+ */
+ open fun addWeakCallback(previousSnapshot: List<T>?, callback: Callback) {
+ if (previousSnapshot != null && previousSnapshot !== this) {
+ if (previousSnapshot.isEmpty()) {
+ if (!storage.isEmpty()) {
+ // If snapshot is empty, diff is trivial - just notify number new items.
+ // Note: occurs in async init, when snapshot taken before init page arrives
+ callback.onInserted(0, storage.size)
+ }
+ } else {
+ val storageSnapshot = previousSnapshot as PagedList<T>
+ dispatchUpdatesSinceSnapshot(storageSnapshot, callback)
+ }
+ }
+
+ // first, clean up any empty weak refs
+ for (i in callbacks.indices.reversed()) {
+ val currentCallback = callbacks[i].get()
+ if (currentCallback == null) {
+ callbacks.removeAt(i)
+ }
+ }
+
+ // then add the new one
+ callbacks.add(WeakReference(callback))
+ }
+
+ /**
+ * Removes a previously added callback.
+ *
+ * @param callback Callback, previously added.
+ * @see addWeakCallback
+ */
+ open fun removeWeakCallback(callback: Callback) {
+ for (i in callbacks.indices.reversed()) {
+ val currentCallback = callbacks[i].get()
+ if (currentCallback == null || currentCallback === callback) {
+ // found callback, or empty weak ref
+ callbacks.removeAt(i)
+ }
+ }
+ }
+
+ internal fun notifyInserted(position: Int, count: Int) {
+ if (count == 0) return
+
+ for (i in callbacks.indices.reversed()) {
+ val callback = callbacks[i].get()
+ callback?.onInserted(position, count)
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun notifyChanged(position: Int, count: Int) {
+ if (count != 0) {
+ for (i in callbacks.indices.reversed()) {
+ callbacks[i].get()?.onChanged(position, count)
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun notifyRemoved(position: Int, count: Int) {
+ if (count != 0) {
+ for (i in callbacks.indices.reversed()) {
+ callbacks[i].get()?.onRemoved(position, count)
+ }
+ }
+ }
+}
+
+/**
+ * Constructs a [PagedList], convenience for [PagedList.Builder].
+ *
+ * @param Key Type of key used to load data from the DataSource.
+ * @param Value Type of items held and loaded by the PagedList.
+ * @param dataSource DataSource the PagedList will load from.
+ * @param config Config that defines how the PagedList loads data from its DataSource.
+ * @param notifyExecutor Executor that receives PagedList updates, and where [PagedList.Callback]
+ * calls are dispatched. Generally, this is the UI/main thread.
+ * @param fetchExecutor Executor used to fetch from DataSources, generally a background thread pool
+ * for e.g. I/O or network loading.
+ * @param boundaryCallback BoundaryCallback for listening to out-of-data events.
+ * @param initialKey Key the DataSource should load around as part of initialization.
+ */
+@Suppress("FunctionName")
+fun <Key : Any, Value : Any> PagedList(
+ dataSource: DataSource<Key, Value>,
+ config: Config,
+ notifyExecutor: Executor,
+ fetchExecutor: Executor,
+ boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
+ initialKey: Key? = null
+): PagedList<Value> {
+ @Suppress("DEPRECATION")
+ return PagedList.Builder(dataSource, config)
+ .setNotifyExecutor(notifyExecutor)
+ .setFetchExecutor(fetchExecutor)
+ .setBoundaryCallback(boundaryCallback)
+ .setInitialKey(initialKey)
+ .build()
+}
diff --git a/paging/common/ktx/src/main/java/androidx/paging/PagedListConfig.kt b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
similarity index 76%
rename from paging/common/ktx/src/main/java/androidx/paging/PagedListConfig.kt
rename to paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
index b1671a8..47676e2 100644
--- a/paging/common/ktx/src/main/java/androidx/paging/PagedListConfig.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedListConfig.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 The Android Open Source Project
+ * 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.
@@ -31,15 +31,14 @@
pageSize: Int,
prefetchDistance: Int = pageSize,
enablePlaceholders: Boolean = true,
- initialLoadSizeHint: Int =
- pageSize * PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER,
+ initialLoadSizeHint: Int = pageSize * PagedList.Config.Builder.DEFAULT_INITIAL_PAGE_MULTIPLIER,
maxSize: Int = PagedList.Config.MAX_SIZE_UNBOUNDED
): PagedList.Config {
return PagedList.Config.Builder()
- .setPageSize(pageSize)
- .setPrefetchDistance(prefetchDistance)
- .setEnablePlaceholders(enablePlaceholders)
- .setInitialLoadSizeHint(initialLoadSizeHint)
- .setMaxSize(maxSize)
- .build()
+ .setPageSize(pageSize)
+ .setPrefetchDistance(prefetchDistance)
+ .setEnablePlaceholders(enablePlaceholders)
+ .setInitialLoadSizeHint(initialLoadSizeHint)
+ .setMaxSize(maxSize)
+ .build()
}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
new file mode 100644
index 0000000..e9df6d8
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedStorage.kt
@@ -0,0 +1,651 @@
+/*
+ * 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.annotation.RestrictTo
+import java.util.AbstractList
+
+/**
+ * Class holding the pages of data backing a [PagedList], presenting sparse loaded data as a List.
+ *
+ * It has two modes of operation: contiguous and non-contiguous (tiled). This class only holds
+ * data, and does not have any notion of the ideas of async loads, or prefetching.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class PagedStorage<T : Any> : AbstractList<T>, Pager.AdjacentProvider<T> {
+ /**
+ * List of pages in storage.
+ *
+ * Two storage modes:
+ *
+ * Contiguous - all content in pages is valid and loaded, but may return `false` from [isTiled].
+ * Safe to access any item in any page.
+ *
+ * Non-contiguous - pages may have nulls or a placeholder page, [isTiled] always returns `true`.
+ * pages may have nulls, or placeholder (empty) pages while content is loading.
+ */
+ private val pages: ArrayList<List<T>?>
+
+ var leadingNullCount: Int = 0
+ private set
+
+ var trailingNullCount: Int = 0
+ private set
+
+ var positionOffset: Int = 0
+ private set
+ /**
+ * Number of loaded items held by [pages]. When tiling, doesn't count unloaded pages in [pages].
+ * If tiling is disabled, same as [storageCount].
+ *
+ * This count is the one used for trimming.
+ */
+ var loadedCount: Int = 0
+ private set
+
+ /**
+ * Number of items represented by [pages]. If tiling is enabled, unloaded items in [pages] may
+ * be `null`, but this value still counts them.
+ */
+ var storageCount: Int = 0
+ private set
+
+ /**
+ *If pageSize > 0, tiling is enabled, 'pages' may have gaps, and leadingPages is set
+ */
+ private var pageSize: Int = 0
+
+ var numberPrepended: Int = 0
+ private set
+ var numberAppended: Int = 0
+ private set
+
+ /**
+ * `true` if all pages are the same size, except for the last, which may be smaller
+ */
+ val isTiled
+ get() = pageSize > 0
+
+ val pageCount
+ get() = pages.size
+
+ val middleOfLoadedRange
+ get() = leadingNullCount + positionOffset + storageCount / 2
+
+ // ------------- Adjacent Provider interface (contiguous-only) ------------------
+
+ override val firstLoadedItem
+ // Safe to access first page's first item here
+ // If contiguous, pages can't be empty, can't hold null Pages, and items can't be empty
+ get() = pages[0]?.first()
+
+ override val lastLoadedItem
+ // Safe to access last page's last item here:
+ // If contiguous, pages can't be empty, can't hold null Pages, and items can't be empty
+ get() = pages.last()?.last()
+
+ override val firstLoadedItemIndex
+ get() = leadingNullCount + positionOffset
+
+ override val lastLoadedItemIndex
+ get() = leadingNullCount + storageCount - 1 + positionOffset
+
+ constructor() {
+ leadingNullCount = 0
+ pages = ArrayList()
+ trailingNullCount = 0
+ positionOffset = 0
+ loadedCount = 0
+ storageCount = 0
+ pageSize = 1
+ numberPrepended = 0
+ numberAppended = 0
+ }
+
+ constructor(leadingNulls: Int, page: List<T>, trailingNulls: Int) : this() {
+ init(leadingNulls, page, trailingNulls, 0)
+ }
+
+ private constructor(other: PagedStorage<T>) {
+ leadingNullCount = other.leadingNullCount
+ pages = ArrayList(other.pages)
+ trailingNullCount = other.trailingNullCount
+ positionOffset = other.positionOffset
+ loadedCount = other.loadedCount
+ storageCount = other.storageCount
+ pageSize = other.pageSize
+ numberPrepended = other.numberPrepended
+ numberAppended = other.numberAppended
+ }
+
+ fun snapshot() = PagedStorage(this)
+
+ private fun init(leadingNulls: Int, page: List<T>, trailingNulls: Int, positionOffset: Int) {
+ leadingNullCount = leadingNulls
+ pages.clear()
+ pages.add(page)
+ trailingNullCount = trailingNulls
+
+ this.positionOffset = positionOffset
+ loadedCount = page.size
+ storageCount = loadedCount
+
+ // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled
+ // even if it will break if nulls convert.
+ pageSize = page.size
+
+ numberPrepended = 0
+ numberAppended = 0
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun init(
+ leadingNulls: Int,
+ page: List<T>,
+ trailingNulls: Int,
+ positionOffset: Int,
+ callback: Callback
+ ) {
+ init(leadingNulls, page, trailingNulls, positionOffset)
+ callback.onInitialized(size)
+ }
+
+ override fun get(index: Int): T? {
+ // is it definitely outside 'pages'?
+ val localIndex = index - leadingNullCount
+
+ when {
+ index < 0 || index >= size ->
+ throw IndexOutOfBoundsException("Index: $index, Size: $size")
+ localIndex < 0 || localIndex >= storageCount -> return null
+ }
+
+ var localPageIndex: Int
+ var pageInternalIndex: Int
+
+ if (isTiled) {
+ // it's inside pages, and we're tiled. Jump to correct tile.
+ localPageIndex = localIndex / pageSize
+ pageInternalIndex = localIndex % pageSize
+ } else {
+ // it's inside pages, but page sizes aren't regular. Walk to correct tile.
+ // Pages can only be null while tiled, so accessing page count is safe.
+ pageInternalIndex = localIndex
+ val localPageCount = pages.size
+ localPageIndex = 0
+ while (localPageIndex < localPageCount) {
+ val pageSize = pages[localPageIndex]!!.size
+ if (pageSize > pageInternalIndex) {
+ // stop, found the page
+ break
+ }
+ pageInternalIndex -= pageSize
+ localPageIndex++
+ }
+ }
+
+ val page = pages[localPageIndex]
+ return when {
+ // can only occur in tiled case, with untouched inner/placeholder pages
+ page == null || page.isEmpty() -> null
+ else -> page[pageInternalIndex]
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ interface Callback {
+ fun onInitialized(count: Int)
+ fun onPagePrepended(leadingNulls: Int, changed: Int, added: Int)
+ fun onPageAppended(endPosition: Int, changed: Int, added: Int)
+ fun onPagePlaceholderInserted(pageIndex: Int)
+ fun onPageInserted(start: Int, count: Int)
+ fun onPagesRemoved(startOfDrops: Int, count: Int)
+ fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int)
+ }
+
+ override val size
+ get() = leadingNullCount + storageCount + trailingNullCount
+
+ fun computeLeadingNulls(): Int {
+ var total = leadingNullCount
+ val pageCount = pages.size
+ for (i in 0 until pageCount) {
+ val page = pages[i]
+ if (page != null && page !is PlaceholderList) {
+ break
+ }
+ total += pageSize
+ }
+ return total
+ }
+
+ fun computeTrailingNulls(): Int {
+ var total = trailingNullCount
+ for (i in pages.indices.reversed()) {
+ val page = pages[i]
+ if (page != null && page !is PlaceholderList) {
+ break
+ }
+ total += pageSize
+ }
+ return total
+ }
+
+ // ---------------- Trimming API -------------------
+ // Trimming is always done at the beginning or end of the list, as content is loaded.
+ // In addition to trimming pages in the storage, we also support pre-trimming pages (dropping
+ // them just before they're added) to avoid dispatching an add followed immediately by a trim.
+ //
+ // Note - we avoid trimming down to a single page to reduce chances of dropping page in
+ // viewport, since we don't strictly know the viewport. If trim is aggressively set to size of a
+ // single page, trimming while the user can see a page boundary is dangerous. To be safe, we
+ // just avoid trimming in these cases entirely.
+
+ private fun needsTrim(maxSize: Int, requiredRemaining: Int, localPageIndex: Int): Boolean {
+ val page = pages[localPageIndex]
+ return page == null || (loadedCount > maxSize &&
+ pages.size > 2 &&
+ page !is PlaceholderList &&
+ loadedCount - page.size >= requiredRemaining)
+ }
+
+ fun needsTrimFromFront(maxSize: Int, requiredRemaining: Int) =
+ needsTrim(maxSize, requiredRemaining, 0)
+
+ fun needsTrimFromEnd(maxSize: Int, requiredRemaining: Int) =
+ needsTrim(maxSize, requiredRemaining, pages.size - 1)
+
+ fun shouldPreTrimNewPage(maxSize: Int, requiredRemaining: Int, countToBeAdded: Int) =
+ loadedCount + countToBeAdded > maxSize &&
+ pages.size > 1 &&
+ loadedCount >= requiredRemaining
+
+ internal fun trimFromFront(
+ insertNulls: Boolean,
+ maxSize: Int,
+ requiredRemaining: Int,
+ callback: Callback
+ ): Boolean {
+ var totalRemoved = 0
+ while (needsTrimFromFront(maxSize, requiredRemaining)) {
+ val page = pages.removeAt(0)
+ val removed = page?.size ?: pageSize
+ totalRemoved += removed
+ storageCount -= removed
+ loadedCount -= page?.size ?: 0
+ }
+
+ if (totalRemoved > 0) {
+ if (insertNulls) {
+ // replace removed items with nulls
+ val previousLeadingNulls = leadingNullCount
+ leadingNullCount += totalRemoved
+ callback.onPagesSwappedToPlaceholder(previousLeadingNulls, totalRemoved)
+ } else {
+ // simply remove, and handle offset
+ positionOffset += totalRemoved
+ callback.onPagesRemoved(leadingNullCount, totalRemoved)
+ }
+ }
+ return totalRemoved > 0
+ }
+
+ internal fun trimFromEnd(
+ insertNulls: Boolean,
+ maxSize: Int,
+ requiredRemaining: Int,
+ callback: Callback
+ ): Boolean {
+ var totalRemoved = 0
+ while (needsTrimFromEnd(maxSize, requiredRemaining)) {
+ val page = pages.removeAt(pages.size - 1)
+ val removed = page?.size ?: pageSize
+ totalRemoved += removed
+ storageCount -= removed
+ loadedCount -= page?.size ?: 0
+ }
+
+ if (totalRemoved > 0) {
+ val newEndPosition = leadingNullCount + storageCount
+ if (insertNulls) {
+ // replace removed items with nulls
+ trailingNullCount += totalRemoved
+ callback.onPagesSwappedToPlaceholder(newEndPosition, totalRemoved)
+ } else {
+ // items were just removed, signal
+ callback.onPagesRemoved(newEndPosition, totalRemoved)
+ }
+ }
+ return totalRemoved > 0
+ }
+
+ // ---------------- Contiguous API -------------------
+
+ internal fun prependPage(page: List<T>, callback: Callback) {
+ val count = page.size
+ if (count == 0) {
+ // Nothing returned from source, nothing to do
+ return
+ }
+ if (pageSize > 0 && count != pageSize) {
+ if (pages.size == 1 && count > pageSize) {
+ // prepending to a single item - update current page size to that of 'inner' page
+ pageSize = count
+ } else {
+ // no longer tiled
+ pageSize = -1
+ }
+ }
+
+ pages.add(0, page)
+ loadedCount += count
+ storageCount += count
+
+ val changedCount = minOf(leadingNullCount, count)
+ val addedCount = count - changedCount
+
+ if (changedCount != 0) {
+ leadingNullCount -= changedCount
+ }
+ positionOffset -= addedCount
+ numberPrepended += count
+
+ callback.onPagePrepended(leadingNullCount, changedCount, addedCount)
+ }
+
+ internal fun appendPage(page: List<T>, callback: Callback) {
+ val count = page.size
+ if (count == 0) {
+ // Nothing returned from source, nothing to do
+ return
+ }
+
+ if (pageSize > 0) {
+ // if the previous page was smaller than pageSize,
+ // or if this page is larger than the previous, disable tiling
+ if (pages[pages.size - 1]!!.size != pageSize || count > pageSize) {
+ pageSize = -1
+ }
+ }
+
+ pages.add(page)
+ loadedCount += count
+ storageCount += count
+
+ val changedCount = minOf(trailingNullCount, count)
+ val addedCount = count - changedCount
+
+ if (changedCount != 0) {
+ trailingNullCount -= changedCount
+ }
+ numberAppended += count
+ callback.onPageAppended(
+ leadingNullCount + storageCount - count,
+ changedCount, addedCount
+ )
+ }
+
+ override fun onPageResultResolution(
+ type: PagedList.LoadType,
+ result: DataSource.BaseResult<T>
+ ) {
+ // ignored
+ }
+
+ // ------------------ Non-Contiguous API (tiling required) ----------------------
+
+ /**
+ * Return true if the page at the passed position would be the first (if trimFromFront) or last
+ * page that's currently loading.
+ */
+ fun pageWouldBeBoundary(positionOfPage: Int, trimFromFront: Boolean): Boolean {
+ when {
+ pageSize < 1 || pages.size < 2 ->
+ throw IllegalStateException("Trimming attempt before sufficient load")
+ // position represent page in leading nulls
+ positionOfPage < leadingNullCount -> return trimFromFront
+ // position represent page in trailing nulls
+ positionOfPage >= leadingNullCount + storageCount -> return !trimFromFront
+ }
+
+ val localPageIndex = (positionOfPage - leadingNullCount) / pageSize
+
+ // walk outside in, return false if we find non-placeholder page before localPageIndex
+ if (trimFromFront) {
+ for (i in 0 until localPageIndex) {
+ if (pages[i] != null) {
+ return false
+ }
+ }
+ } else {
+ for (i in pages.size - 1 downTo localPageIndex + 1) {
+ if (pages[i] != null) {
+ return false
+ }
+ }
+ }
+
+ // didn't find another page, so this one would be a boundary
+ return true
+ }
+
+ internal fun initAndSplit(
+ leadingNulls: Int,
+ multiPageList: List<T>,
+ trailingNulls: Int,
+ positionOffset: Int,
+ pageSize: Int,
+ callback: Callback
+ ) {
+ val pageCount = (multiPageList.size + (pageSize - 1)) / pageSize
+ for (i in 0 until pageCount) {
+ val beginInclusive = i * pageSize
+ val endExclusive = minOf(multiPageList.size, (i + 1) * pageSize)
+
+ val sublist = multiPageList.subList(beginInclusive, endExclusive)
+
+ if (i == 0) {
+ // Trailing nulls for first page includes other pages in multiPageList
+ val initialTrailingNulls = trailingNulls + multiPageList.size - sublist.size
+ init(leadingNulls, sublist, initialTrailingNulls, positionOffset)
+ } else {
+ val insertPosition = leadingNulls + beginInclusive
+ insertPage(insertPosition, sublist, null)
+ }
+ }
+ callback.onInitialized(size)
+ }
+
+ internal fun tryInsertPageAndTrim(
+ position: Int,
+ page: List<T>,
+ lastLoad: Int,
+ maxSize: Int,
+ requiredRemaining: Int,
+ callback: Callback
+ ) {
+ val trim = maxSize != PagedList.Config.MAX_SIZE_UNBOUNDED
+ val trimFromFront = lastLoad > middleOfLoadedRange
+
+ val pageInserted = (!trim ||
+ !shouldPreTrimNewPage(maxSize, requiredRemaining, page.size) ||
+ !pageWouldBeBoundary(position, trimFromFront))
+
+ if (pageInserted) {
+ insertPage(position, page, callback)
+ } else {
+ // trim would have us drop the page we just loaded - swap it to null
+ val localPageIndex = (position - leadingNullCount) / pageSize
+ pages.set(localPageIndex, null)
+
+ // note: we also remove it, so we don't have to guess how large a 'null' page is later
+ storageCount -= page.size
+ if (trimFromFront) {
+ pages.removeAt(0)
+ leadingNullCount += page.size
+ } else {
+ pages.removeAt(pages.size - 1)
+ trailingNullCount += page.size
+ }
+ }
+
+ if (trim) {
+ if (trimFromFront) {
+ trimFromFront(true, maxSize, requiredRemaining, callback)
+ } else {
+ trimFromEnd(true, maxSize, requiredRemaining, callback)
+ }
+ }
+ }
+
+ internal fun insertPage(position: Int, page: List<T>, callback: Callback?) {
+ val newPageSize = page.size
+ if (newPageSize != pageSize) {
+ // differing page size is OK in 2 cases, when the page is being added:
+ // 1) to the end (in which case, ignore new smaller size)
+ // 2) only the last page has been added so far (in which case, adopt new bigger size)
+
+ val size = size
+ val addingLastPage = position == size - size % pageSize && newPageSize < pageSize
+ val == 0 && pages.size == 1 &&
+ newPageSize > pageSize)
+
+ // OK only if existing single page, and it's the last one
+ if (!onlyEndPagePresent && !addingLastPage) {
+ throw IllegalArgumentException("page introduces incorrect tiling")
+ }
+ if (onlyEndPagePresent) {
+ pageSize = newPageSize
+ }
+ }
+
+ val pageIndex = position / pageSize
+
+ allocatePageRange(pageIndex, pageIndex)
+
+ val localPageIndex = pageIndex - leadingNullCount / pageSize
+
+ val oldPage = pages[localPageIndex]
+ if (oldPage != null && oldPage !is PlaceholderList) {
+ throw IllegalArgumentException(
+ "Invalid position $position: data already loaded"
+ )
+ }
+ pages[localPageIndex] = page
+ loadedCount += newPageSize
+ callback?.onPageInserted(position, newPageSize)
+ }
+
+ @Suppress("MemberVisibilityCanBePrivate")
+ fun allocatePageRange(minimumPage: Int, maximumPage: Int) {
+ var leadingNullPages = leadingNullCount / pageSize
+
+ if (minimumPage < leadingNullPages) {
+ for (i in 0 until leadingNullPages - minimumPage) {
+ pages.add(0, null)
+ }
+ val newStorageAllocated = (leadingNullPages - minimumPage) * pageSize
+ storageCount += newStorageAllocated
+ leadingNullCount -= newStorageAllocated
+
+ leadingNullPages = minimumPage
+ }
+ if (maximumPage >= leadingNullPages + pages.size) {
+ val newStorageAllocated = minOf(
+ trailingNullCount,
+ (maximumPage + 1 - (leadingNullPages + pages.size)) * pageSize
+ )
+ for (i in pages.size..maximumPage - leadingNullPages) {
+ pages.add(pages.size, null)
+ }
+ storageCount += newStorageAllocated
+ trailingNullCount -= newStorageAllocated
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ fun allocatePlaceholders(index: Int, prefetchDistance: Int, pageSize: Int, callback: Callback) {
+ if (pageSize != this.pageSize) {
+ if (pageSize < this.pageSize) {
+ throw IllegalArgumentException("Page size cannot be reduced")
+ }
+ if (pages.size != 1 || trailingNullCount != 0) {
+ // not in single, last page allocated case - can't change page size
+ throw IllegalArgumentException(
+ "Page size can change only if last page is only one present"
+ )
+ }
+ this.pageSize = pageSize
+ }
+
+ val maxPageCount = (size + this.pageSize - 1) / this.pageSize
+ val minimumPage = maxOf((index - prefetchDistance) / this.pageSize, 0)
+ val maximumPage = minOf((index + prefetchDistance) / this.pageSize, maxPageCount - 1)
+
+ allocatePageRange(minimumPage, maximumPage)
+ val leadingNullPages = leadingNullCount / this.pageSize
+ for (pageIndex in minimumPage..maximumPage) {
+ val localPageIndex = pageIndex - leadingNullPages
+ if (pages[localPageIndex] == null) {
+ pages[localPageIndex] = placeholderList
+ callback.onPagePlaceholderInserted(pageIndex)
+ }
+ }
+ }
+
+ fun hasPage(pageSize: Int, index: Int): Boolean {
+ // NOTE: we pass pageSize here to avoid in case pageSize not fully initialized (when last
+ // page only one loaded).
+ val leadingNullPages = leadingNullCount / pageSize
+
+ if (index < leadingNullPages || index >= leadingNullPages + pages.size) {
+ return false
+ }
+
+ val page = pages[index - leadingNullPages]
+
+ return page != null && page !is PlaceholderList
+ }
+
+ override fun toString(): String {
+ var ret = "leading $leadingNullCount, storage $storageCount, trailing $trailingNullCount"
+ if (pages.isNotEmpty()) {
+ ret += " ${pages.joinToString(" ")}"
+ }
+ return ret
+ }
+
+ /**
+ * Lists instances are compared (with instance equality) to [placeholderList] to check if an
+ * item in that position is already loading. We use a singleton placeholder list that is
+ * distinct from `Collections.emptyList()` for safety.
+ */
+ private class PlaceholderList<T> : ArrayList<T>()
+
+ private val placeholderList = PlaceholderList<T>()
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/Pager.kt b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
new file mode 100644
index 0000000..3387f9f
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/Pager.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.paging.DataSource.BaseResult
+import androidx.paging.PagedList.LoadState
+import androidx.paging.PagedList.LoadType
+import androidx.paging.futures.FutureCallback
+import androidx.paging.futures.addCallback
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+
+internal class Pager<K : Any, V : Any>(
+ val config: PagedList.Config,
+ val source: DataSource<K, V>,
+ val notifyExecutor: Executor,
+ private val fetchExecutor: Executor,
+ val pageConsumer: PageConsumer<V>,
+ adjacentProvider: AdjacentProvider<V>?,
+ result: BaseResult<V>
+) {
+ private val totalCount: Int
+ private val adjacentProvider: AdjacentProvider<V>
+ private var prevKey: K? = null
+ private var nextKey: K? = null
+ private val detached = AtomicBoolean(false)
+
+ var loadStateManager = object : PagedList.LoadStateManager() {
+ override fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?) =
+ pageConsumer.onStateChanged(type, state, error)
+ }
+
+ val isDetached
+ get() = detached.get()
+
+ init {
+ this.adjacentProvider = adjacentProvider ?: SimpleAdjacentProvider()
+ @Suppress("UNCHECKED_CAST")
+ prevKey = result.prevKey as K?
+ @Suppress("UNCHECKED_CAST")
+ nextKey = result.nextKey as K?
+ this.adjacentProvider.onPageResultResolution(LoadType.REFRESH, result)
+ totalCount = result.totalCount()
+
+ // TODO: move this validation to tiled paging impl, once that's added back
+ if (source.type === DataSource.KeyType.POSITIONAL && config.enablePlaceholders) {
+ result.validateForInitialTiling(config.pageSize)
+ }
+ }
+
+ private fun listenTo(type: LoadType, future: ListenableFuture<out BaseResult<V>>) {
+ // First listen on the BG thread if the DataSource is invalid, since it can be expensive
+ future.addListener(Runnable {
+ // if invalid, drop result on the floor
+ if (source.isInvalid) {
+ detach()
+ return@Runnable
+ }
+
+ // Source has been verified to be valid after producing data, so sent data to UI
+ future.addCallback(
+ object : FutureCallback<BaseResult<V>> {
+ override fun onSuccess(value: BaseResult<V>) = onLoadSuccess(type, value)
+ override fun onError(throwable: Throwable) = onLoadError(type, throwable)
+ },
+ notifyExecutor
+ )
+ }, fetchExecutor)
+ }
+
+ internal interface PageConsumer<V : Any> {
+ /**
+ * @return `true` if we need to fetch more
+ */
+ fun onPageResult(type: LoadType, pageResult: BaseResult<V>): Boolean
+
+ fun onStateChanged(type: LoadType, state: LoadState, error: Throwable?)
+ }
+
+ internal interface AdjacentProvider<V : Any> {
+ val firstLoadedItem: V?
+ val lastLoadedItem: V?
+ val firstLoadedItemIndex: Int
+ val lastLoadedItemIndex: Int
+
+ /**
+ * Notify the [AdjacentProvider] of new loaded data, to update first/last item/index.
+ *
+ * NOTE: this data may not be committed (e.g. it may be dropped due to max size). Up to the
+ * implementation of the AdjacentProvider to handle this (generally by ignoring this call if
+ * dropping is supported).
+ */
+ fun onPageResultResolution(type: LoadType, result: BaseResult<V>)
+ }
+
+ fun onLoadSuccess(type: LoadType, value: BaseResult<V>) {
+ if (isDetached) return // abort!
+
+ adjacentProvider.onPageResultResolution(type, value)
+
+ if (pageConsumer.onPageResult(type, value)) {
+ when (type) {
+ LoadType.START -> {
+ @Suppress("UNCHECKED_CAST")
+ prevKey = value.prevKey as K?
+ schedulePrepend()
+ }
+ LoadType.END -> {
+ @Suppress("UNCHECKED_CAST")
+ nextKey = value.nextKey as K?
+ scheduleAppend()
+ }
+ else -> throw IllegalStateException("Can only fetch more during append/prepend")
+ }
+ } else {
+ val state = if (value.data.isEmpty()) LoadState.DONE else LoadState.IDLE
+ loadStateManager.setState(type, state, null)
+ }
+ }
+
+ fun onLoadError(type: LoadType, throwable: Throwable) {
+ if (isDetached) return // abort!
+
+ // TODO: handle nesting
+ val state = when {
+ source.isRetryableError(throwable) -> LoadState.RETRYABLE_ERROR
+ else -> LoadState.ERROR
+ }
+ loadStateManager.setState(type, state, throwable)
+ }
+
+ fun trySchedulePrepend() {
+ if (loadStateManager.start == LoadState.IDLE) schedulePrepend()
+ }
+
+ fun tryScheduleAppend() {
+ if (loadStateManager.end == LoadState.IDLE) scheduleAppend()
+ }
+
+ private fun canPrepend() = when (totalCount) {
+ // don't know count / position from initial load, so be conservative, return true
+ BaseResult.TOTAL_COUNT_UNKNOWN -> true
+ // position is known, do we have space left?
+ else -> adjacentProvider.firstLoadedItemIndex > 0
+ }
+
+ private fun canAppend() = when (totalCount) {
+ // don't know count / position from initial load, so be conservative, return true
+ BaseResult.TOTAL_COUNT_UNKNOWN -> true
+ // count is known, do we have space left?
+ else -> adjacentProvider.lastLoadedItemIndex < totalCount - 1
+ }
+
+ private fun schedulePrepend() {
+ if (!canPrepend()) {
+ onLoadSuccess(LoadType.START, BaseResult.empty())
+ return
+ }
+
+ val key = when (source.type) {
+ DataSource.KeyType.POSITIONAL ->
+ @Suppress("UNCHECKED_CAST")
+ (adjacentProvider.firstLoadedItemIndex - 1) as K
+ DataSource.KeyType.PAGE_KEYED -> prevKey
+ DataSource.KeyType.ITEM_KEYED -> (source as ListenableItemKeyedDataSource).getKey(
+ adjacentProvider.firstLoadedItem!!
+ )
+ }
+
+ loadStateManager.setState(LoadType.START, LoadState.LOADING, null)
+ listenTo(
+ LoadType.START,
+ source.load(
+ DataSource.Params(
+ DataSource.LoadType.START,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize
+ )
+ )
+ )
+ }
+
+ private fun scheduleAppend() {
+ if (!canAppend()) {
+ onLoadSuccess(LoadType.END, BaseResult.empty())
+ return
+ }
+
+ val key = when (source.type) {
+ DataSource.KeyType.POSITIONAL ->
+ @Suppress("UNCHECKED_CAST")
+ (adjacentProvider.lastLoadedItemIndex + 1) as K
+ DataSource.KeyType.PAGE_KEYED -> nextKey
+ DataSource.KeyType.ITEM_KEYED -> (source as ListenableItemKeyedDataSource).getKey(
+ adjacentProvider.lastLoadedItem!!
+ )
+ }
+
+ loadStateManager.setState(LoadType.END, LoadState.LOADING, null)
+ listenTo(
+ LoadType.END,
+ source.load(
+ DataSource.Params(
+ DataSource.LoadType.END,
+ key,
+ config.initialLoadSizeHint,
+ config.enablePlaceholders,
+ config.pageSize
+ )
+ )
+ )
+ }
+
+ fun retry() {
+ if (loadStateManager.start == LoadState.RETRYABLE_ERROR) schedulePrepend()
+ if (loadStateManager.end == LoadState.RETRYABLE_ERROR) scheduleAppend()
+ }
+
+ fun detach() = detached.set(true)
+
+ internal class SimpleAdjacentProvider<V : Any> : AdjacentProvider<V> {
+ override var firstLoadedItemIndex: Int = 0
+ private set
+ override var lastLoadedItemIndex: Int = 0
+ private set
+ override var firstLoadedItem: V? = null
+ private set
+ override var lastLoadedItem: V? = null
+ private set
+
+ private var counted: Boolean = false
+ private var leadingUnloadedCount: Int = 0
+ private var trailingUnloadedCount: Int = 0
+
+ override fun onPageResultResolution(type: LoadType, result: BaseResult<V>) {
+ if (result.data.isEmpty()) return
+
+ if (type == LoadType.START) {
+ firstLoadedItemIndex -= result.data.size
+ firstLoadedItem = result.data[0]
+ if (counted) {
+ leadingUnloadedCount -= result.data.size
+ }
+ } else if (type == LoadType.END) {
+ lastLoadedItemIndex += result.data.size
+ lastLoadedItem = result.data.last()
+ if (counted) {
+ trailingUnloadedCount -= result.data.size
+ }
+ } else {
+ firstLoadedItemIndex = result.leadingNulls + result.offset
+ lastLoadedItemIndex = firstLoadedItemIndex + result.data.size - 1
+ firstLoadedItem = result.data[0]
+ lastLoadedItem = result.data.last()
+
+ if (result.counted) {
+ counted = true
+ leadingUnloadedCount = result.leadingNulls
+ trailingUnloadedCount = result.trailingNulls
+ }
+ }
+ }
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
new file mode 100644
index 0000000..50bb351
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -0,0 +1,389 @@
+/*
+ * 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.annotation.RestrictTo
+import androidx.annotation.WorkerThread
+import androidx.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.paging.PositionalDataSource.LoadInitialCallback
+import com.google.common.util.concurrent.ListenableFuture
+
+/**
+ * Position-based data loader for a fixed-size, countable data set, supporting fixed-size loads at
+ * arbitrary page positions.
+ *
+ * Extend PositionalDataSource if you can load pages of a requested size at arbitrary positions,
+ * and provide a fixed item count. If your data source can't support loading arbitrary requested
+ * page sizes (e.g. when network page size constraints are only known at runtime), either use
+ * [PageKeyedDataSource] or [ItemKeyedDataSource], or pass the initial result with the two parameter
+ * [LoadInitialCallback.onResult].
+ *
+ * Room can generate a Factory of PositionalDataSources for you:
+ * ```
+ * @Dao
+ * interface UserDao {
+ * @Query("SELECT * FROM user ORDER BY age DESC")
+ * public abstract DataSource.Factory<Integer, User> loadUsersByAgeDesc();
+ * }
+ * ```
+ *
+ * @param T Type of items being loaded by the [PositionalDataSource].
+ *
+ * @see [ListenablePositionalDataSource]
+ */
+abstract class PositionalDataSource<T : Any> : ListenablePositionalDataSource<T>() {
+ /**
+ * Holder object for inputs to [loadInitial].
+ */
+ open class LoadInitialParams(
+ requestedStartPosition: Int,
+ requestedLoadSize: Int,
+ pageSize: Int,
+ placeholdersEnabled: Boolean
+ ) : ListenablePositionalDataSource.LoadInitialParams(
+ requestedStartPosition,
+ requestedLoadSize,
+ pageSize,
+ placeholdersEnabled
+ )
+
+ /**
+ * Holder object for inputs to [loadRange].
+ */
+ open class LoadRangeParams(startPosition: Int, loadSize: Int) :
+ ListenablePositionalDataSource.LoadRangeParams(startPosition, loadSize)
+
+ /**
+ * Callback for [loadInitial] to return data, position, and count.
+ *
+ * A callback should be called only once, and may throw if called again.
+ *
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param T Type of items being loaded.
+ */
+ abstract class LoadInitialCallback<T> {
+ /**
+ * Called to pass initial load state from a DataSource.
+ *
+ * Call this method from [loadInitial] function to return data, and inform how many
+ * placeholders should be shown before and after. If counting is cheap compute (for example,
+ * if a network load returns the information regardless), it's recommended to pass the total
+ * size to the totalCount parameter. If placeholders are not requested (when
+ * [LoadInitialParams.placeholdersEnabled] is false), you can instead call [onResult].
+ *
+ * @param data List of items loaded from the [DataSource]. If this is empty, the
+ * [DataSource] is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are N items
+ * before the items in data that can be loaded from this DataSource, pass N.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial [data] parameter as well as any
+ * items that can be loaded in front or behind of [data].
+ */
+ abstract fun onResult(data: List<T>, position: Int, totalCount: Int)
+
+ /**
+ * Called to pass initial load state from a DataSource without total count, when
+ * placeholders aren't requested.
+ *
+ * **Note:** This method can only be called when placeholders are disabled (i.e.,
+ * [LoadInitialParams.placeholdersEnabled] is `false`).
+ *
+ * Call this method from [loadInitial] function to return data, if position is known but
+ * total size is not. If placeholders are requested, call the three parameter variant:
+ * [onResult].
+ *
+ * @param data List of items loaded from the [DataSource]. If this is empty, the
+ * [DataSource] is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are N items
+ * before the items in data that can be provided by this [DataSource], pass
+ * N.
+ */
+ abstract fun onResult(data: List<T>, position: Int)
+
+ /**
+ * Called to report an error from a DataSource.
+ *
+ * Call this method to report an error from [loadInitial].
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ /**
+ * Callback for PositionalDataSource [loadRange] to return data.
+ *
+ * A callback should be called only once, and may throw if called again.
+ *
+ * It is always valid for a [DataSource] loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param T Type of items being loaded.
+ */
+ abstract class LoadRangeCallback<T> {
+ /**
+ * Called to pass loaded data from [loadRange].
+ *
+ * @param data List of items loaded from the [DataSource]. Must be same size as requested,
+ * unless at end of list.
+ */
+ abstract fun onResult(data: List<T>)
+
+ /**
+ * Called to report an error from a [DataSource].
+ *
+ * Call this method to report an error from [loadRange].
+ *
+ * @param error The error that occurred during loading.
+ */
+ open fun onError(error: Throwable) {
+ // TODO: remove default implementation in 3.0
+ throw IllegalStateException(
+ "You must implement onError if implementing your own load callback"
+ )
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY)
+ companion object {
+ /**
+ * Helper for computing an initial position in [loadInitial] when total data set size can be
+ * computed ahead of loading.
+ *
+ * The value computed by this function will do bounds checking, page alignment, and
+ * positioning based on initial load size requested.
+ *
+ * Example usage in a [PositionalDataSource] subclass:
+ * ```
+ * class ItemDataSource extends PositionalDataSource<Item> {
+ * private int computeCount() {
+ * // actual count code here
+ * }
+ *
+ * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+ * // actual load code here
+ * }
+ *
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @NonNull LoadInitialCallback<Item> callback) {
+ * int totalCount = computeCount();
+ * int position = computeInitialLoadPosition(params, totalCount);
+ * int loadSize = computeInitialLoadSize(params, position, totalCount);
+ * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+ * }
+ *
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested start /
+ * loadSize.
+ * @param totalCount Total size of the data set.
+ * @return Position to start loading at.
+ *
+ * @see [computeInitialLoadSize]
+ */
+ @JvmStatic
+ fun computeInitialLoadPosition(params: LoadInitialParams, totalCount: Int): Int =
+ ListenablePositionalDataSource.computeInitialLoadPosition(params, totalCount)
+
+ /**
+ * Helper for computing an initial load size in [loadInitial] when total data set size can
+ * be computed ahead of loading.
+ *
+ * This function takes the requested load size, and bounds checks it against the value
+ * returned by [computeInitialLoadPosition].
+ *
+ * Example usage in a [PositionalDataSource] subclass:
+ * ```
+ * class ItemDataSource extends PositionalDataSource<Item> {
+ * private int computeCount() {
+ * // actual count code here
+ * }
+ *
+ * private List<Item> loadRangeInternal(int startPosition, int loadCount) {
+ * // actual load code here
+ * }
+ *
+ * @Override
+ * public void loadInitial(@NonNull LoadInitialParams params,
+ * @NonNull LoadInitialCallback<Item> callback) {
+ * int totalCount = computeCount();
+ * int position = computeInitialLoadPosition(params, totalCount);
+ * int loadSize = computeInitialLoadSize(params, position, totalCount);
+ * callback.onResult(loadRangeInternal(position, loadSize), position, totalCount);
+ * }
+ *
+ * @Override
+ * public void loadRange(@NonNull LoadRangeParams params,
+ * @NonNull LoadRangeCallback<Item> callback) {
+ * callback.onResult(loadRangeInternal(params.startPosition, params.loadSize));
+ * }
+ * }
+ * ```
+ *
+ * @param params Params passed to [loadInitial], including page size, and requested start /
+ * loadSize.
+ * @param initialLoadPosition Value returned by [computeInitialLoadPosition]
+ * @param totalCount Total size of the data set.
+ * @return Number of items to load.
+ *
+ * @see [computeInitialLoadPosition]
+ */
+ @JvmStatic
+ fun computeInitialLoadSize(
+ params: LoadInitialParams,
+ initialLoadPosition: Int,
+ totalCount: Int
+ ) = ListenablePositionalDataSource.computeInitialLoadSize(
+ params,
+ initialLoadPosition,
+ totalCount
+ )
+ }
+
+ final override fun loadInitial(
+ params: ListenablePositionalDataSource.LoadInitialParams
+ ): ListenableFuture<InitialResult<T>> {
+ val future = ResolvableFuture.create<InitialResult<T>>()
+ executor.execute {
+ val newParams = LoadInitialParams(
+ params.requestedStartPosition,
+ params.requestedLoadSize,
+ params.pageSize,
+ params.placeholdersEnabled
+ )
+ val callback = object : LoadInitialCallback<T>() {
+ override fun onResult(data: List<T>, position: Int, totalCount: Int) {
+ if (isInvalid) {
+ // NOTE: this isInvalid check works around
+ // https://issuetracker.google.com/issues/124511903
+ future.set(InitialResult(emptyList(), 0, 0))
+ } else {
+ setFuture(newParams, InitialResult(data, position, totalCount))
+ }
+ }
+
+ override fun onResult(data: List<T>, position: Int) {
+ if (isInvalid) {
+ // NOTE: this isInvalid check works around
+ // https://issuetracker.google.com/issues/124511903
+ future.set(InitialResult(emptyList(), 0))
+ } else {
+ setFuture(newParams, InitialResult(data, position))
+ }
+ }
+
+ private fun setFuture(
+ params: ListenablePositionalDataSource.LoadInitialParams,
+ result: InitialResult<T>
+ ) {
+ if (params.placeholdersEnabled) {
+ result.validateForInitialTiling(params.pageSize)
+ }
+ future.set(result)
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadInitial(newParams, callback)
+ }
+ return future
+ }
+
+ final override fun loadRange(
+ params: ListenablePositionalDataSource.LoadRangeParams
+ ): ListenableFuture<RangeResult<T>> {
+ val future = ResolvableFuture.create<RangeResult<T>>()
+ executor.execute {
+ val callback = object : LoadRangeCallback<T>() {
+ override fun onResult(data: List<T>) {
+ when {
+ isInvalid -> future.set(RangeResult(emptyList()))
+ else -> future.set(RangeResult(data))
+ }
+ }
+
+ override fun onError(error: Throwable) {
+ future.setException(error)
+ }
+ }
+ loadRange(LoadRangeParams(params.startPosition, params.loadSize), callback)
+ }
+ return future
+ }
+
+ /**
+ * Load initial list data.
+ *
+ * This method is called to load the initial page(s) from the [DataSource].
+ *
+ * Result list must be a multiple of pageSize to enable efficient tiling.
+ *
+ * @param params Parameters for initial load, including requested start position, load size, and
+ * page size.
+ * @param callback Callback that receives initial load data, including position and total data
+ * set size.
+ */
+ @WorkerThread
+ abstract fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>)
+
+ /**
+ * Called to load a range of data from the DataSource.
+ *
+ * This method is called to load additional pages from the DataSource after the
+ * [LoadInitialCallback] passed to dispatchLoadInitial has initialized a [PagedList].
+ *
+ * Unlike [loadInitial], this method must return the number of items requested, at the position
+ * requested.
+ *
+ * @param params Parameters for load, including start position and load size.
+ * @param callback Callback that receives loaded data.
+ */
+ @WorkerThread
+ abstract fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>)
+
+ @Suppress("RedundantVisibilityModifier") // Metalava doesn't inherit visibility properly.
+ internal override val isContiguous = false
+
+ final override fun <V : Any> mapByPage(
+ function: Function<List<T>, List<V>>
+ ): PositionalDataSource<V> = WrapperPositionalDataSource(this, function)
+
+ final override fun <V : Any> map(function: Function<T, V>): PositionalDataSource<V> =
+ mapByPage(Function { list -> list.map { function.apply(it) } })
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
new file mode 100644
index 0000000..7217d82
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/SnapshotPagedList.kt
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+internal class SnapshotPagedList<T : Any>(private val pagedList: PagedList<T>) : PagedList<T>(
+ pagedList.storage.snapshot(),
+ pagedList.mainThreadExecutor,
+ pagedList.backgroundThreadExecutor,
+ null,
+ pagedList.config
+) {
+ init {
+ lastLoad = pagedList.lastLoad
+ }
+
+ override val isContiguous
+ get() = pagedList.isContiguous
+
+ override val dataSource: DataSource<*, T> = pagedList.dataSource
+ override val isImmutable = true
+
+ override val lastKey
+ get() = pagedList.lastKey
+
+ override val isDetached = true
+
+ override fun detach() {}
+ override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<T>, callback: Callback) {}
+ override fun dispatchCurrentLoadState(listener: LoadStateListener) {}
+ override fun loadAroundInternal(index: Int) {}
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperDataSource.kt
new file mode 100644
index 0000000..fa5b145
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperDataSource.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.arch.core.util.Function
+import androidx.paging.futures.DirectExecutor
+import androidx.paging.futures.transform
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.IdentityHashMap
+
+/**
+ * @param Key DataSource key type, same for original and wrapped.
+ * @param ValueFrom Value type of original DataSource.
+ * @param ValueTo Value type of new DataSource.
+ */
+internal open class WrapperDataSource<Key : Any, ValueFrom : Any, ValueTo : Any>(
+ private val source: DataSource<Key, ValueFrom>,
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ val listFunction: Function<List<ValueFrom>, List<ValueTo>>
+) : DataSource<Key, ValueTo>(source.type) {
+ private val keyMap = when (source.type) {
+ KeyType.ITEM_KEYED -> IdentityHashMap<ValueTo, Key>()
+ else -> null
+ }
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.addInvalidatedCallback(onInvalidatedCallback)
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+
+ override fun invalidate() = source.invalidate()
+
+ override val isInvalid
+ get() = source.isInvalid
+
+ override fun getKeyInternal(item: ValueTo): Key = when {
+ keyMap != null -> synchronized(keyMap) {
+ return keyMap[item]!!
+ }
+ // positional / page-keyed
+ else -> throw IllegalStateException("Cannot get key by item in non-item keyed DataSource")
+ }
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ fun stashKeysIfNeeded(source: List<ValueFrom>, dest: List<ValueTo>) {
+ if (keyMap != null) {
+ synchronized(keyMap) {
+ for (i in dest.indices) {
+ keyMap[dest[i]] =
+ (this.source as ListenableItemKeyedDataSource).getKey(source[i])
+ }
+ }
+ }
+ }
+
+ override fun load(params: Params<Key>): ListenableFuture<out BaseResult<ValueTo>> =
+ source.load(params).transform(
+ Function { input ->
+ val result = BaseResult.convert(input, listFunction)
+ stashKeysIfNeeded(input.data, result.data)
+ result
+ },
+ DirectExecutor
+ )
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
new file mode 100644
index 0000000..65047b9
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperItemKeyedDataSource.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.arch.core.util.Function
+import java.util.IdentityHashMap
+
+internal class WrapperItemKeyedDataSource<K : Any, A : Any, B : Any>(
+ private val source: ItemKeyedDataSource<K, A>,
+ private val listFunction: Function<List<A>, List<B>>
+) : ItemKeyedDataSource<K, B>() {
+
+ private val keyMap = IdentityHashMap<B, K>()
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.addInvalidatedCallback(onInvalidatedCallback)
+ }
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+ }
+
+ override fun invalidate() {
+ source.invalidate()
+ }
+
+ override val isInvalid
+ get() = source.isInvalid
+
+ fun convertWithStashedKeys(source: List<A>): List<B> {
+ val dest = convert(listFunction, source)
+ synchronized(keyMap) {
+ // synchronize on keyMap, since multiple loads may occur simultaneously.
+ // Note: manually sync avoids locking per-item (e.g. Collections.synchronizedMap)
+ for (i in dest.indices) {
+ keyMap[dest[i]] = this.source.getKey(source[i])
+ }
+ }
+ return dest
+ }
+
+ override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
+ source.loadInitial(params, object : ItemKeyedDataSource.LoadInitialCallback<A>() {
+ override fun onResult(data: List<A>, position: Int, totalCount: Int) {
+ callback.onResult(convertWithStashedKeys(data), position, totalCount)
+ }
+
+ override fun onResult(data: List<A>) {
+ callback.onResult(convertWithStashedKeys(data))
+ }
+
+ override fun onError(error: Throwable) {
+ callback.onError(error)
+ }
+ })
+ }
+
+ override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
+ source.loadAfter(params, object : ItemKeyedDataSource.LoadCallback<A>() {
+ override fun onResult(data: List<A>) {
+ callback.onResult(convertWithStashedKeys(data))
+ }
+
+ override fun onError(error: Throwable) {
+ callback.onError(error)
+ }
+ })
+ }
+
+ override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
+ source.loadBefore(params, object : ItemKeyedDataSource.LoadCallback<A>() {
+ override fun onResult(data: List<A>) {
+ callback.onResult(convertWithStashedKeys(data))
+ }
+
+ override fun onError(error: Throwable) {
+ callback.onError(error)
+ }
+ })
+ }
+
+ override fun getKey(item: B): K = synchronized(keyMap) {
+ return keyMap[item]!!
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
new file mode 100644
index 0000000..c49b480
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperPageKeyedDataSource.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.arch.core.util.Function
+
+internal class WrapperPageKeyedDataSource<K : Any, A : Any, B : Any>(
+ private val source: PageKeyedDataSource<K, A>,
+ private val listFunction: Function<List<A>, List<B>>
+) : PageKeyedDataSource<K, B>() {
+ override val isInvalid
+ get() = source.isInvalid
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.addInvalidatedCallback(onInvalidatedCallback)
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+
+ override fun invalidate() = source.invalidate()
+
+ override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<K, B>) {
+ source.loadInitial(params, object : PageKeyedDataSource.LoadInitialCallback<K, A>() {
+ override fun onResult(
+ data: List<A>,
+ position: Int,
+ totalCount: Int,
+ previousPageKey: K?,
+ nextPageKey: K?
+ ) {
+ val convertedData = convert(listFunction, data)
+ callback.onResult(convertedData, position, totalCount, previousPageKey, nextPageKey)
+ }
+
+ override fun onResult(data: List<A>, previousPageKey: K?, nextPageKey: K?) {
+ val convertedData = convert(listFunction, data)
+ callback.onResult(convertedData, previousPageKey, nextPageKey)
+ }
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+
+ override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<K, B>) {
+ source.loadBefore(params, object : PageKeyedDataSource.LoadCallback<K, A>() {
+ override fun onResult(data: List<A>, adjacentPageKey: K?) =
+ callback.onResult(convert(listFunction, data), adjacentPageKey)
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+
+ override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<K, B>) {
+ source.loadAfter(params, object : PageKeyedDataSource.LoadCallback<K, A>() {
+ override fun onResult(data: List<A>, adjacentPageKey: K?) =
+ callback.onResult(convert(listFunction, data), adjacentPageKey)
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
new file mode 100644
index 0000000..683d670
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/WrapperPositionalDataSource.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.arch.core.util.Function
+
+internal class WrapperPositionalDataSource<A : Any, B : Any>(
+ private val source: PositionalDataSource<A>,
+ val listFunction: Function<List<A>, List<B>>
+) : PositionalDataSource<B>() {
+ override val isInvalid
+ get() = source.isInvalid
+
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.addInvalidatedCallback(onInvalidatedCallback)
+
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) =
+ source.removeInvalidatedCallback(onInvalidatedCallback)
+
+ override fun invalidate() = source.invalidate()
+
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
+ source.loadInitial(params, object : LoadInitialCallback<A>() {
+ override fun onResult(data: List<A>, position: Int, totalCount: Int) =
+ callback.onResult(convert(listFunction, data), position, totalCount)
+
+ override fun onResult(data: List<A>, position: Int) =
+ callback.onResult(convert(listFunction, data), position)
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<B>) {
+ source.loadRange(params, object : LoadRangeCallback<A>() {
+ override fun onResult(data: List<A>) = callback.onResult(convert(listFunction, data))
+
+ override fun onError(error: Throwable) = callback.onError(error)
+ })
+ }
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/DirectExecutor.kt b/paging/common/src/main/kotlin/androidx/paging/futures/DirectExecutor.kt
new file mode 100644
index 0000000..0d0c733
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/DirectExecutor.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.futures
+
+import java.util.concurrent.Executor
+
+/**
+ * An [Executor] that runs each task in the thread that invokes [execute][Executor.execute]
+ *
+ * This instance is equivalent to:
+ *```
+ * final class DirectExecutor implements Executor {
+ * public void execute(Runnable r) {
+ * r.run();
+ * }
+ * }
+ *```
+ */
+internal object DirectExecutor : Executor {
+ override fun execute(runnable: Runnable) = runnable.run()
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/FutureCallback.kt b/paging/common/src/main/kotlin/androidx/paging/futures/FutureCallback.kt
new file mode 100644
index 0000000..899a5ec
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/FutureCallback.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.futures
+
+import androidx.annotation.RestrictTo
+
+import com.google.common.util.concurrent.ListenableFuture
+
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Future
+
+/**
+ * A callback for accepting the results of a [Future] computation asynchronously.
+ *
+ * To attach to a [ListenableFuture] use [addCallback].
+ * @param V Type of the Future result.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+interface FutureCallback<V> {
+ /**
+ * Invoked with the result of the `Future` computation when it is successful.
+ */
+ fun onSuccess(value: V)
+
+ /**
+ * Invoked when a `Future` computation fails or is canceled.
+ *
+ * If the future's [get][Future.get] method throws an [ExecutionException], then the cause is
+ * passed to this method. Any other thrown object is passed unaltered.
+ */
+ fun onError(throwable: Throwable)
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/futures/Futures.kt b/paging/common/src/main/kotlin/androidx/paging/futures/Futures.kt
new file mode 100644
index 0000000..a44dddd
--- /dev/null
+++ b/paging/common/src/main/kotlin/androidx/paging/futures/Futures.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+// TODO: Remove once paging-runtime is converted to .kt.
+@file:JvmName("Futures")
+
+package androidx.paging.futures
+
+import androidx.annotation.RestrictTo
+import androidx.arch.core.util.Function
+import androidx.concurrent.futures.ResolvableFuture
+
+import com.google.common.util.concurrent.ListenableFuture
+
+import java.util.concurrent.CancellationException
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+
+/**
+ * Registers separate success and failure callbacks to be run when the `Future`'s computation is
+ * complete or, if the computation is already complete, immediately.
+ *
+ * The callback is run on `executor`. There is no guaranteed ordering of execution of callbacks,
+ * but any callback added through this method is guaranteed to be called once the computation is
+ * complete.
+ *
+ * Example:
+ * ```
+ * ListenableFuture<QueryResult> future = ...;
+ * Executor e = ...
+ * addCallback(future, new FutureCallback<QueryResult>() {
+ * public void onSuccess(QueryResult result) {
+ * storeInCache(result);
+ * }
+ * public void onFailure(Throwable t) {
+ * reportError(t);
+ * }
+ * }, e);
+ * ```
+ *
+ * When selecting an executor, note that `directExecutor` is dangerous in some cases. See the
+ * discussion in the [ListenableFuture.addListener] documentation. All its warnings about
+ * heavyweight listeners are also applicable to heavyweight callbacks passed to this method.
+ *
+ * For a more general interface to attach a completion listener to a `Future`, see
+ * [ListenableFuture.addListener].
+ *
+ * @param callback The callback to invoke when `future` is completed.
+ * @param executor The executor to run `callback` when the future completes.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <V> ListenableFuture<out V>.addCallback(callback: FutureCallback<in V>, executor: Executor) {
+ addListener(Runnable {
+ val value: V
+ try {
+ value = get()
+ } catch (e: ExecutionException) {
+ callback.onError(e.cause ?: e)
+ return@Runnable
+ } catch (e: Throwable) {
+ callback.onError(e)
+ return@Runnable
+ }
+
+ callback.onSuccess(value)
+ }, executor)
+}
+
+/**
+ * Returns a new `Future` whose result is derived from the result of the given `Future`. If
+ * `input` fails, the returned `Future` fails with the same exception (and the function is not
+ * invoked). Example usage:
+ *
+ * ```
+ * ListenableFuture<QueryResult> queryFuture = ...;
+ * ListenableFuture<List<Row>> rowsFuture =
+ * transform(queryFuture, QueryResult::getRows, executor);
+ * ```
+ *
+ * When selecting an executor, note that `directExecutor` is dangerous in some cases. See the
+ * discussion in the [ListenableFuture.addListener] documentation. All its warnings about
+ * heavyweight listeners are also applicable to heavyweight functions passed to this method.
+ *
+ * The returned `Future` attempts to keep its cancellation state in sync with that of the input
+ * future. That is, if the returned `Future` is cancelled, it will attempt to cancel the input,
+ * and if the input is cancelled, the returned `Future` will receive a callback in which it will
+ * attempt to cancel itself.
+ *
+ * An example use of this method is to convert a serializable object returned from an RPC into a
+ * POJO.
+ *
+ * @param function A Function to transform the results of the provided future to the results of
+ * the returned future.
+ * @param executor Executor to run the function in.
+ * @return A future that holds result of the transformation.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <I, O> ListenableFuture<out I>.transform(
+ function: Function<in I, out O>,
+ executor: Executor
+): ListenableFuture<O> {
+ val out = ResolvableFuture.create<O>()
+
+ // Add success/error callback.
+ addCallback(object : FutureCallback<I> {
+ override fun onSuccess(value: I) {
+ out.set(function.apply(value))
+ }
+
+ override fun onError(throwable: Throwable) {
+ out.setException(throwable)
+ }
+ }, executor)
+
+ // Propagate output future's cancellation to input future.
+ out.addCallback(object : FutureCallback<O> {
+ override fun onSuccess(value: O) {}
+
+ override fun onError(throwable: Throwable) {
+ if (throwable is CancellationException) {
+ cancel(false)
+ }
+ }
+ }, executor)
+ return out
+}
diff --git a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
deleted file mode 100644
index 2cae585..0000000
--- a/paging/common/src/test/java/androidx/paging/TiledDataSourceTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2017 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.paging.futures.DirectExecutor
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@Suppress("DEPRECATION")
-@RunWith(JUnit4::class)
-class TiledDataSourceTest {
-
- fun TiledDataSource<String>.loadInitial(
- startPosition: Int,
- count: Int,
- pageSize: Int
- ): List<String> {
- initExecutor(DirectExecutor.INSTANCE)
- return loadInitial(PositionalDataSource.LoadInitialParams(
- startPosition, count, pageSize, true)).get().data
- }
-
- @Test
- fun loadInitialEmpty() {
- class EmptyDataSource : TiledDataSource<String>() {
- override fun countItems(): Int {
- return 0
- }
-
- override fun loadRange(startPosition: Int, count: Int): List<String> {
- return emptyList()
- }
- }
-
- assertEquals(emptyList<String>(), EmptyDataSource().loadInitial(0, 1, 5))
- }
-
- @Test
- fun loadInitialTooLong() {
- val list = List(26) { "" + 'a' + it }
- class AlphabetDataSource : TiledDataSource<String>() {
- override fun countItems(): Int {
- return list.size
- }
-
- override fun loadRange(startPosition: Int, count: Int): List<String> {
- return list.subList(startPosition, startPosition + count)
- }
- }
- // baseline behavior
- assertEquals(list, AlphabetDataSource().loadInitial(0, 26, 10))
- assertEquals(list, AlphabetDataSource().loadInitial(50, 26, 10))
- }
-}
diff --git a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
similarity index 90%
rename from paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
index c2a6f82..9ce5682 100644
--- a/paging/common/src/test/java/androidx/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ContiguousPagedListTest.kt
@@ -21,22 +21,22 @@
import androidx.paging.PagedList.LoadState.RETRYABLE_ERROR
import androidx.paging.futures.DirectExecutor
import androidx.testutils.TestExecutor
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.reset
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
+import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
@RunWith(Parameterized::class)
class ContiguousPagedListTest(private val placeholdersEnabled: Boolean) {
- private val mMainThread = TestExecutor()
- private val mBackgroundThread = TestExecutor()
+ private val mainThread = TestExecutor()
+ private val backgroundThread = TestExecutor()
private class Item(position: Int) {
val pos: Int = position
@@ -59,7 +59,7 @@
callback: LoadInitialCallback<Item>
) {
val initPos = params.requestedInitialKey ?: 0
- val start = Math.max(initPos - params.requestedLoadSize / 2, 0)
+ val start = maxOf(initPos - params.requestedLoadSize / 2, 0)
val result = getClampedRange(start, start + params.requestedLoadSize)
if (result == null) {
@@ -94,13 +94,13 @@
override fun getKey(item: Item): Int = item.pos
private fun getClampedRange(startInc: Int, endExc: Int): List<Item>? {
- val matching = errorIndices.filter { it in startInc..(endExc - 1) }
+ val matching = errorIndices.filter { it in startInc until endExc }
if (matching.isNotEmpty()) {
// found indices with errors enqueued - fail to load them
errorIndices.removeAll(matching)
return null
}
- return listData.subList(Math.max(0, startInc), Math.min(listData.size, endExc))
+ return listData.subList(maxOf(0, startInc), minOf(listData.size, endExc))
}
fun enqueueErrorForIndex(index: Int) {
@@ -124,14 +124,20 @@
return data
}
- private fun <E> PagedList<E>.addLoadStateCapture(desiredType: PagedList.LoadType):
+ private fun <E : Any> PagedList<E>.addLoadStateCapture(desiredType: PagedList.LoadType):
MutableList<PagedList.LoadState> {
val list = mutableListOf<PagedList.LoadState>()
- this.addWeakLoadStateListener { type, state, _ ->
- if (type == desiredType) {
- list.add(state)
+ this.addWeakLoadStateListener(object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ if (type == desiredType) {
+ list.add(state)
+ }
}
- }
+ })
return list
}
@@ -162,7 +168,7 @@
}
private fun verifyRange(start: Int, count: Int, actual: PagedList<Item>) {
- verifyRange(start, count, actual.mStorage)
+ verifyRange(start, count, actual.storage)
assertEquals(count, actual.loadedCount)
}
@@ -178,9 +184,9 @@
): ContiguousPagedList<Int, Item> {
val ret = PagedList.create(
dataSource,
- mMainThread,
- mBackgroundThread,
- DirectExecutor.INSTANCE,
+ mainThread,
+ backgroundThread,
+ DirectExecutor,
boundaryCallback,
PagedList.Config.Builder()
.setPageSize(pageSize)
@@ -260,7 +266,7 @@
@Test
fun append() {
val pagedList = createCountedPagedList(0)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 40, pagedList)
verifyZeroInteractions(callback)
@@ -276,7 +282,7 @@
@Test
fun prepend() {
val pagedList = createCountedPagedList(80)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(60, 40, pagedList)
verifyZeroInteractions(callback)
@@ -292,7 +298,7 @@
@Test
fun outwards() {
val pagedList = createCountedPagedList(40)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(20, 40, pagedList)
verifyZeroInteractions(callback)
@@ -379,7 +385,7 @@
prefetchDistance = 1,
maxSize = 70
)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 20, pagedList)
verifyZeroInteractions(callback)
@@ -416,7 +422,7 @@
prefetchDistance = 1,
maxSize = 70
)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(80, 20, pagedList)
verifyZeroInteractions(callback)
@@ -462,18 +468,18 @@
drain()
verifyRange(1, 3, pagedList)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
// start a load at the beginning...
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
- mBackgroundThread.executeAll()
+ backgroundThread.executeAll()
// but before page received, access near end of list
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
verifyZeroInteractions(callback)
- mMainThread.executeAll()
+ mainThread.executeAll()
// and the load at the beginning is dropped without signaling callback
verifyNoMoreInteractions(callback)
verifyRange(1, 3, pagedList)
@@ -504,18 +510,18 @@
pagedList.loadAround(if (placeholdersEnabled) 2 else 0)
drain()
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
// start a load at the end...
pagedList.loadAround(if (placeholdersEnabled) 3 else 2)
- mBackgroundThread.executeAll()
+ backgroundThread.executeAll()
// but before page received, access near front of list
pagedList.loadAround(if (placeholdersEnabled) 1 else 0)
verifyZeroInteractions(callback)
- mMainThread.executeAll()
+ mainThread.executeAll()
// and the load at the end is dropped without signaling callback
verifyNoMoreInteractions(callback)
verifyRange(1, 3, pagedList)
@@ -542,7 +548,7 @@
// trigger load
pagedList.loadAround(35)
- mMainThread.executeAll()
+ mainThread.executeAll()
assertEquals(listOf(LOADING), states.getAllAndClear())
verifyRange(0, 40, pagedList)
@@ -555,7 +561,7 @@
// trigger load which will error
pagedList.loadAround(55)
- mMainThread.executeAll()
+ mainThread.executeAll()
assertEquals(listOf(LOADING), states.getAllAndClear())
verifyRange(0, 60, pagedList)
@@ -566,7 +572,7 @@
// retry
pagedList.retry()
- mMainThread.executeAll()
+ mainThread.executeAll()
assertEquals(listOf(LOADING), states.getAllAndClear())
// load finishes
@@ -644,11 +650,17 @@
// have an error, move loading range, error goes away
val pagedList = createCountedPagedList(0)
val states = mutableListOf<PagedList.LoadState>()
- pagedList.addWeakLoadStateListener { type, state, _ ->
- if (type == PagedList.LoadType.END) {
- states.add(state)
+ pagedList.addWeakLoadStateListener(object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ if (type == PagedList.LoadType.END) {
+ states.add(state)
+ }
}
- }
+ })
pagedList.dataSource.enqueueErrorForIndex(45)
pagedList.loadAround(35)
@@ -661,9 +673,12 @@
fun distantPrefetch() {
val pagedList = createCountedPagedList(
0,
- initLoadSize = 10, pageSize = 10, prefetchDistance = 30
+ initLoadSize = 10,
+ pageSize = 10,
+ prefetchDistance = 30
)
- val callback = mock(PagedList.Callback::class.java)
+
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(null, callback)
verifyRange(0, 10, pagedList)
verifyZeroInteractions(callback)
@@ -700,7 +715,7 @@
verifyRange(0, 60, snapshot)
// and verify the snapshot hasn't received them
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(snapshot, callback)
verifyCallback(callback, 60)
verifyNoMoreInteractions(callback)
@@ -724,7 +739,7 @@
verifyRange(20, 80, pagedList)
verifyRange(40, 60, snapshot)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
pagedList.addWeakCallback(snapshot, callback)
verifyCallback(callback, 40, 0)
verifyNoMoreInteractions(callback)
@@ -739,7 +754,7 @@
dataSource = ListDataSource(ITEMS)
)
// With positional DataSource, last load is param passed
- assertEquals(4, pagedList.mLastLoad)
+ assertEquals(4, pagedList.lastLoad)
verifyRange(0, 20, pagedList)
}
@@ -750,14 +765,14 @@
initLoadSize = 20
)
// last load is middle of initial load
- assertEquals(10, pagedList.mLastLoad)
+ assertEquals(10, pagedList.lastLoad)
verifyRange(0, 20, pagedList)
}
@Test
fun addWeakCallbackEmpty() {
val pagedList = createCountedPagedList(0)
- val callback = mock(PagedList.Callback::class.java)
+ val callback = mock<PagedList.Callback>()
verifyRange(0, 40, pagedList)
// capture empty snapshot
@@ -782,8 +797,7 @@
@Test
fun boundaryCallback_empty() {
@Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+ val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
0,
listData = ArrayList(), boundaryCallback = boundaryCallback
@@ -803,8 +817,7 @@
fun boundaryCallback_singleInitialLoad() {
val shortList = ITEMS.subList(0, 4)
@Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+ val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
0, listData = shortList,
initLoadSize = shortList.size, boundaryCallback = boundaryCallback
@@ -826,8 +839,7 @@
@Test
fun boundaryCallback_delayed() {
@Suppress("UNCHECKED_CAST")
- val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<Item>
+ val boundaryCallback = mock<PagedList.BoundaryCallback<Item>>()
val pagedList = createCountedPagedList(
90,
initLoadSize = 20, prefetchDistance = 5, boundaryCallback = boundaryCallback
@@ -872,8 +884,8 @@
private fun drain() {
var executed: Boolean
do {
- executed = mBackgroundThread.executeAll()
- executed = mMainThread.executeAll() || executed
+ executed = backgroundThread.executeAll()
+ executed = mainThread.executeAll() || executed
} while (executed)
}
diff --git a/paging/common/src/test/java/androidx/paging/FailExecutor.kt b/paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/FailExecutor.kt
rename to paging/common/src/test/kotlin/androidx/paging/FailExecutor.kt
diff --git a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
similarity index 86%
rename from paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index fb72c74..819d9db 100644
--- a/paging/common/src/test/java/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -16,7 +16,10 @@
package androidx.paging
+import androidx.arch.core.util.Function
import androidx.paging.futures.DirectExecutor
+import com.nhaarman.mockitokotlin2.capture
+import com.nhaarman.mockitokotlin2.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
@@ -24,7 +27,6 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
@@ -39,9 +41,10 @@
initialLoadSize: Int,
enablePlaceholders: Boolean
): DataSource.BaseResult<Item> {
- dataSource.initExecutor(DirectExecutor.INSTANCE)
+ dataSource.initExecutor(DirectExecutor)
return dataSource.loadInitial(
- ItemKeyedDataSource.LoadInitialParams(key, initialLoadSize, enablePlaceholders)).get()
+ ItemKeyedDataSource.LoadInitialParams(key, initialLoadSize, enablePlaceholders)
+ ).get()
}
@Test
@@ -178,15 +181,15 @@
fun loadBefore() {
val dataSource = ItemDataSource()
@Suppress("UNCHECKED_CAST")
- val callback = mock(ItemKeyedDataSource.LoadCallback::class.java)
- as ItemKeyedDataSource.LoadCallback<Item>
+ val callback = mock<ItemKeyedDataSource.LoadCallback<Item>>()
dataSource.loadBefore(
- ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback)
+ ItemKeyedDataSource.LoadParams(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5), callback
+ )
@Suppress("UNCHECKED_CAST")
val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
- verify(callback).onResult(argument.capture())
+ verify(callback).onResult(capture(argument))
verifyNoMoreInteractions(callback)
val observed = argument.value
@@ -219,9 +222,9 @@
return
}
- val key = params.requestedInitialKey ?: Key("", Integer.MAX_VALUE)
- val start = Math.max(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
- val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
+ val key = params.requestedInitialKey ?: Key("", Int.MAX_VALUE)
+ val start = maxOf(0, findFirstIndexAfter(key) - params.requestedLoadSize / 2)
+ val endExclusive = minOf(start + params.requestedLoadSize, items.size)
if (params.placeholdersEnabled && counted) {
callback.onResult(items.subList(start, endExclusive), start, items.size)
@@ -238,7 +241,7 @@
}
val start = findFirstIndexAfter(params.key)
- val endExclusive = Math.min(start + params.requestedLoadSize, items.size)
+ val endExclusive = minOf(start + params.requestedLoadSize, items.size)
callback.onResult(items.subList(start, endExclusive))
}
@@ -251,8 +254,8 @@
}
val firstIndexBefore = findFirstIndexBefore(params.key)
- val endExclusive = Math.max(0, firstIndexBefore + 1)
- val start = Math.max(0, firstIndexBefore - params.requestedLoadSize + 1)
+ val endExclusive = maxOf(0, firstIndexBefore + 1)
+ val start = maxOf(0, firstIndexBefore - params.requestedLoadSize + 1)
callback.onResult(items.subList(start, endExclusive))
}
@@ -307,14 +310,16 @@
}
}
- PagedList.create(dataSource, FailExecutor(),
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
+ PagedList.create(
+ dataSource, FailExecutor(),
+ DirectExecutor,
+ DirectExecutor,
null,
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").get()
+ ""
+ ).get()
}
@Test
@@ -354,8 +359,9 @@
it.onResult(emptyList(), 0, 2)
}
- private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
- : ItemKeyedDataSource<K, B>() {
+ private abstract class WrapperDataSource<K : Any, A : Any, B : Any>(
+ private val source: ItemKeyedDataSource<K, A>
+ ) : ItemKeyedDataSource<K, B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
source.addInvalidatedCallback(onInvalidatedCallback)
}
@@ -368,9 +374,8 @@
source.invalidate()
}
- override fun isInvalid(): Boolean {
- return source.isInvalid
- }
+ override val isInvalid
+ get() = source.isInvalid
override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
source.loadInitial(params, object : LoadInitialCallback<A>() {
@@ -378,7 +383,7 @@
callback.onResult(convert(data), position, totalCount)
}
- override fun onResult(data: MutableList<A>) {
+ override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
@@ -390,7 +395,7 @@
override fun loadAfter(params: LoadParams<K>, callback: LoadCallback<B>) {
source.loadAfter(params, object : LoadCallback<A>() {
- override fun onResult(data: MutableList<A>) {
+ override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
@@ -402,7 +407,7 @@
override fun loadBefore(params: LoadParams<K>, callback: LoadCallback<B>) {
source.loadBefore(params, object : LoadCallback<A>() {
- override fun onResult(data: MutableList<A>) {
+ override fun onResult(data: List<A>) {
callback.onResult(convert(data))
}
@@ -417,8 +422,8 @@
private data class DecoratedItem(val item: Item)
- private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>)
- : WrapperDataSource<Key, Item, DecoratedItem>(source) {
+ private class DecoratedWrapperDataSource(private val source: ItemKeyedDataSource<Key, Item>) :
+ WrapperDataSource<Key, Item, DecoratedItem>(source) {
override fun convert(source: List<Item>): List<DecoratedItem> {
return source.map { DecoratedItem(it) }
}
@@ -438,14 +443,15 @@
// load initial - success
@Suppress("UNCHECKED_CAST")
- val loadInitialCallback = mock(ItemKeyedDataSource.LoadInitialCallback::class.java)
- as ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>
+ val loadInitialCallback = mock<ItemKeyedDataSource.LoadInitialCallback<DecoratedItem>>()
val initKey = orig.getKey(ITEMS_BY_NAME_ID.first())
val initParams = ItemKeyedDataSource.LoadInitialParams(initKey, 10, false)
- wrapper.loadInitial(initParams,
- loadInitialCallback)
+ wrapper.loadInitial(
+ initParams,
+ loadInitialCallback
+ )
verify(loadInitialCallback).onResult(
- ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
+ ITEMS_BY_NAME_ID.subList(0, 10).map { DecoratedItem(it) })
// error
orig.enqueueError()
wrapper.loadInitial(initParams, loadInitialCallback)
@@ -454,8 +460,8 @@
val key = orig.getKey(ITEMS_BY_NAME_ID[20])
@Suppress("UNCHECKED_CAST")
- var loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
- as ItemKeyedDataSource.LoadCallback<DecoratedItem>
+ var loadCallback = mock<ItemKeyedDataSource.LoadCallback<DecoratedItem>>()
+
// load after
wrapper.loadAfter(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(21, 31).map { DecoratedItem(it) })
@@ -467,8 +473,7 @@
// load before
@Suppress("UNCHECKED_CAST")
- loadCallback = mock(ItemKeyedDataSource.LoadCallback::class.java)
- as ItemKeyedDataSource.LoadCallback<DecoratedItem>
+ loadCallback = mock()
wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
// load before - error
@@ -489,18 +494,18 @@
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.mapByPage { page -> page.map { DecoratedItem(it) } }
+ dataSource.mapByPage(Function { page -> page.map { DecoratedItem(it) } })
}
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.map { DecoratedItem(it) }
+ dataSource.map(Function { DecoratedItem(it) })
}
@Test
fun testInvalidateToWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { DecoratedItem(it) }
+ val wrapper = orig.map<DecoratedItem>(Function { DecoratedItem(it) })
orig.invalidate()
assertTrue(wrapper.isInvalid)
@@ -509,7 +514,7 @@
@Test
fun testInvalidateFromWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { DecoratedItem(it) }
+ val wrapper = orig.map<DecoratedItem>(Function { DecoratedItem(it) })
wrapper.invalidate()
assertTrue(orig.isInvalid)
@@ -521,10 +526,12 @@
private val ITEMS_BY_NAME_ID = List(100) {
val names = Array(10) { index -> "f" + ('a' + index) }
- Item(names[it % 10],
- it,
- Math.random() * 1000,
- (Math.random() * 200).toInt().toString() + " fake st.")
+ Item(
+ names[it % 10],
+ it,
+ Math.random() * 1000,
+ (Math.random() * 200).toInt().toString() + " fake st."
+ )
}.sortedWith(ITEM_COMPARATOR)
private val EXCEPTION = Exception()
diff --git a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
similarity index 85%
rename from paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
index 6122172..63d3b28 100644
--- a/paging/common/src/test/java/androidx/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageKeyedDataSourceTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.arch.core.util.Function
import androidx.paging.futures.DirectExecutor
import androidx.testutils.TestExecutor
import org.junit.Assert.assertEquals
@@ -32,15 +33,15 @@
@RunWith(JUnit4::class)
class PageKeyedDataSourceTest {
- private val mMainThread = TestExecutor()
- private val mBackgroundThread = TestExecutor()
+ private val mainThread = TestExecutor()
+ private val backgroundThread = TestExecutor()
internal data class Item(val name: String)
internal data class Page(val prev: String?, val data: List<Item>, val next: String?)
- internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP)
- : PageKeyedDataSource<String, Item>() {
+ internal class ItemDataSource(val data: Map<String, Page> = PAGE_MAP) :
+ PageKeyedDataSource<String, Item>() {
private var error = false
private fun getPage(key: String): Page = data[key]!!
@@ -91,14 +92,14 @@
// validate paging entire ItemDataSource results in full, correctly ordered data
val pagedListFuture = PagedList.create(
ItemDataSource(),
- mMainThread,
- mBackgroundThread,
- mBackgroundThread,
+ mainThread,
+ backgroundThread,
+ backgroundThread,
null,
PagedList.Config.Builder().setPageSize(100).build(),
null
)
- mBackgroundThread.executeAll()
+ backgroundThread.executeAll()
val pagedList = pagedListFuture.get()
// validate initial load
@@ -118,7 +119,7 @@
private fun performLoadInitial(
invalidateDataSource: Boolean = false,
callbackInvoker:
- (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit
+ (callback: PageKeyedDataSource.LoadInitialCallback<String, String>) -> Unit
) {
val dataSource = object : PageKeyedDataSource<String, String>() {
override fun loadInitial(
@@ -147,14 +148,17 @@
}
}
- PagedList.create(dataSource, FailExecutor(),
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
+ PagedList.create(
+ dataSource,
+ FailExecutor(),
+ DirectExecutor,
+ DirectExecutor,
null,
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").get()
+ ""
+ ).get()
}
@Test
@@ -196,7 +200,7 @@
@Test
fun pageDroppingNotSupported() {
- assertFalse(ItemDataSource().supportsPageDropping())
+ assertFalse(ItemDataSource().supportsPageDropping)
}
@Test
@@ -228,10 +232,11 @@
@Suppress("UNCHECKED_CAST")
val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
+ mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
val executor = TestExecutor()
- val pagedList = PagedList.create(dataSource,
+ val pagedList = PagedList.create(
+ dataSource,
executor,
executor,
executor,
@@ -239,7 +244,8 @@
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").apply { executor.executeAll() }.get()
+ ""
+ ).apply { executor.executeAll() }.get()
pagedList.loadAround(0)
@@ -281,10 +287,11 @@
@Suppress("UNCHECKED_CAST")
val boundaryCallback =
- mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
+ mock(PagedList.BoundaryCallback::class.java) as PagedList.BoundaryCallback<String>
val executor = TestExecutor()
- val pagedList = PagedList.create(dataSource,
+ val pagedList = PagedList.create(
+ dataSource,
executor,
executor,
executor,
@@ -292,7 +299,8 @@
PagedList.Config.Builder()
.setPageSize(10)
.build(),
- "").apply { executor.executeAll() }.get()
+ ""
+ ).apply { executor.executeAll() }.get()
pagedList.loadAround(0)
@@ -306,8 +314,9 @@
verifyNoMoreInteractions(boundaryCallback)
}
- private abstract class WrapperDataSource<K, A, B>(private val source: PageKeyedDataSource<K, A>)
- : PageKeyedDataSource<K, B>() {
+ private abstract class WrapperDataSource<K : Any, A : Any, B : Any>(
+ private val source: PageKeyedDataSource<K, A>
+ ) : PageKeyedDataSource<K, B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
source.addInvalidatedCallback(onInvalidatedCallback)
}
@@ -320,9 +329,8 @@
source.invalidate()
}
- override fun isInvalid(): Boolean {
- return source.isInvalid
- }
+ override val isInvalid
+ get() = source.isInvalid
override fun loadInitial(
params: LoadInitialParams<K>,
@@ -336,11 +344,16 @@
previousPageKey: K?,
nextPageKey: K?
) {
- callback.onResult(convert(data), position, totalCount,
- previousPageKey, nextPageKey)
+ callback.onResult(
+ convert(data),
+ position,
+ totalCount,
+ previousPageKey,
+ nextPageKey
+ )
}
- override fun onResult(data: MutableList<A>, previousPageKey: K?, nextPageKey: K?) {
+ override fun onResult(data: List<A>, previousPageKey: K?, nextPageKey: K?) {
callback.onResult(convert(data), previousPageKey, nextPageKey)
}
@@ -377,8 +390,8 @@
protected abstract fun convert(source: List<A>): List<B>
}
- private class StringWrapperDataSource<K, V>(source: PageKeyedDataSource<K, V>)
- : WrapperDataSource<K, V, String>(source) {
+ private class StringWrapperDataSource<K : Any, V : Any>(source: PageKeyedDataSource<K, V>) :
+ WrapperDataSource<K, V, String>(source) {
override fun convert(source: List<V>): List<String> {
return source.map { it.toString() }
}
@@ -398,9 +411,11 @@
val initParams = PageKeyedDataSource.LoadInitialParams<String>(4, true)
wrapper.loadInitial(initParams, loadInitialCallback)
- val expectedInitial = PAGE_MAP[INIT_KEY]!!
- verify(loadInitialCallback).onResult(expectedInitial.data.map { it.toString() },
- expectedInitial.prev, expectedInitial.next)
+ val expectedInitial = PAGE_MAP.getValue(INIT_KEY)
+ verify(loadInitialCallback).onResult(
+ expectedInitial.data.map { it.toString() },
+ expectedInitial.prev, expectedInitial.next
+ )
verifyNoMoreInteractions(loadInitialCallback)
@Suppress("UNCHECKED_CAST")
@@ -421,8 +436,10 @@
loadCallback = mock(PageKeyedDataSource.LoadCallback::class.java)
as PageKeyedDataSource.LoadCallback<String, String>
wrapper.loadBefore(PageKeyedDataSource.LoadParams(expectedAfter.prev!!, 4), loadCallback)
- verify(loadCallback).onResult(expectedInitial.data.map { it.toString() },
- expectedInitial.prev)
+ verify(loadCallback).onResult(
+ expectedInitial.data.map { it.toString() },
+ expectedInitial.prev
+ )
verifyNoMoreInteractions(loadCallback)
// load before - error
orig.enqueueError()
@@ -442,18 +459,18 @@
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.mapByPage { page -> page.map { it.toString() } }
+ dataSource.mapByPage(Function { page -> page.map { it.toString() } })
}
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.map { it.toString() }
+ dataSource.map(Function { it.toString() })
}
@Test
fun testInvalidateToWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
orig.invalidate()
assertTrue(wrapper.isInvalid)
@@ -462,7 +479,7 @@
@Test
fun testInvalidateFromWrapper() {
val orig = ItemDataSource()
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
wrapper.invalidate()
assertTrue(orig.isInvalid)
@@ -470,7 +487,7 @@
companion object {
// first load is 2nd page to ensure we test prepend as well as append behavior
- private val INIT_KEY: String = "key 2"
+ private const val INIT_KEY: String = "key 2"
private val PAGE_MAP: Map<String, Page>
private val ITEM_LIST: List<Item>
private val EXCEPTION = Exception()
@@ -496,8 +513,8 @@
private fun drain() {
var executed: Boolean
do {
- executed = mBackgroundThread.executeAll()
- executed = mMainThread.executeAll() || executed
+ executed = backgroundThread.executeAll()
+ executed = mainThread.executeAll() || executed
} while (executed)
}
}
diff --git a/paging/common/src/test/java/androidx/paging/PagedListConfigBuilderTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/PagedListConfigBuilderTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedListConfigBuilderTest.kt
diff --git a/paging/common/ktx/src/test/java/PagedListConfigTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListConfigTest.kt
similarity index 100%
rename from paging/common/ktx/src/test/java/PagedListConfigTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedListConfigTest.kt
diff --git a/paging/common/src/test/java/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
similarity index 77%
rename from paging/common/src/test/java/androidx/paging/PagedListTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
index be783c1..c8594fa 100644
--- a/paging/common/src/test/java/androidx/paging/PagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.paging.futures.DirectExecutor
import androidx.testutils.TestExecutor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -26,6 +27,23 @@
@RunWith(JUnit4::class)
class PagedListTest {
+ companion object {
+ private val ITEMS = List(100) { "$it" }
+ private val config = Config(10)
+ private val dataSource = object : PositionalDataSource<String>() {
+ override fun loadInitial(
+ params: LoadInitialParams,
+ callback: LoadInitialCallback<String>
+ ) {
+ callback.onResult(listOf("a"), 0, 1)
+ }
+
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
+ fail()
+ }
+ }
+ }
+
private val mainThread = TestExecutor()
private val backgroundThread = TestExecutor()
@@ -104,5 +122,16 @@
assertTrue(success[0])
}
- private val ITEMS = List(100) { "$it" }
+ @Test
+ fun defaults() {
+ val pagedList = PagedList(
+ dataSource = dataSource,
+ config = config,
+ fetchExecutor = DirectExecutor,
+ notifyExecutor = DirectExecutor
+ )
+
+ assertEquals(dataSource, pagedList.dataSource)
+ assertEquals(config, pagedList.config)
+ }
}
diff --git a/paging/common/src/test/java/androidx/paging/PagedStorageTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
similarity index 100%
rename from paging/common/src/test/java/androidx/paging/PagedStorageTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagedStorageTest.kt
diff --git a/paging/common/src/test/java/androidx/paging/PagerTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
similarity index 91%
rename from paging/common/src/test/java/androidx/paging/PagerTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
index 4e6dc4f..f4c3a26 100644
--- a/paging/common/src/test/java/androidx/paging/PagerTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagerTest.kt
@@ -62,8 +62,8 @@
executor.execute {
val position = params.startPosition
- val end = Math.min(position + params.loadSize, data.size)
- future.set(RangeResult(position, end))
+ val end = minOf(position + params.loadSize, data.size)
+ future.set(rangeResult(position, end))
}
return future
@@ -72,10 +72,8 @@
val data = List(9) { "$it" }
- private fun RangeResult(start: Int, end: Int):
- ListenablePositionalDataSource.RangeResult<String> {
- return ListenablePositionalDataSource.RangeResult(data.subList(start, end))
- }
+ private fun rangeResult(start: Int, end: Int) =
+ ListenablePositionalDataSource.RangeResult(data.subList(start, end))
private data class Result(
val type: PagedList.LoadType,
@@ -125,8 +123,8 @@
private fun createPager(consumer: MockConsumer, start: Int = 0, end: Int = 10) = Pager(
PagedList.Config(2, 2, true, 10, PagedList.Config.MAX_SIZE_UNBOUNDED),
ImmediateListDataSource(data),
- DirectExecutor.INSTANCE,
- DirectExecutor.INSTANCE,
+ DirectExecutor,
+ DirectExecutor,
consumer,
null,
ListenablePositionalDataSource.InitialResult(data.subList(start, end), start, data.size)
@@ -152,7 +150,7 @@
testExecutor.executeAll()
assertEquals(
- listOf(Result(END, RangeResult(6, 8))),
+ listOf(Result(END, rangeResult(6, 8))),
consumer.takeResults()
)
assertEquals(
@@ -178,7 +176,7 @@
testExecutor.executeAll()
assertEquals(
- listOf(Result(START, RangeResult(2, 4))),
+ listOf(Result(START, rangeResult(2, 4))),
consumer.takeResults()
)
assertEquals(
@@ -199,8 +197,8 @@
assertEquals(
listOf(
- Result(END, RangeResult(6, 8)),
- Result(END, RangeResult(8, 9))
+ Result(END, rangeResult(6, 8)),
+ Result(END, rangeResult(8, 9))
), consumer.takeResults()
)
assertEquals(
@@ -225,8 +223,8 @@
assertEquals(
listOf(
- Result(START, RangeResult(2, 4)),
- Result(START, RangeResult(0, 2))
+ Result(START, rangeResult(2, 4)),
+ Result(START, rangeResult(0, 2))
), consumer.takeResults()
)
assertEquals(
diff --git a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
similarity index 80%
rename from paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
index 08cd3fe..6b7868a 100644
--- a/paging/common/src/test/java/androidx/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PositionalDataSourceTest.kt
@@ -16,6 +16,7 @@
package androidx.paging
+import androidx.arch.core.util.Function
import androidx.paging.futures.DirectExecutor
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -26,6 +27,7 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import java.util.concurrent.Executor
@RunWith(JUnit4::class)
class PositionalDataSourceTest {
@@ -43,54 +45,69 @@
@Test
fun computeInitialLoadPositionZero() {
- assertEquals(0, computeInitialLoadPos(
+ assertEquals(
+ 0, computeInitialLoadPos(
requestedStartPosition = 0,
requestedLoadSize = 30,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionRequestedPositionIncluded() {
- assertEquals(10, computeInitialLoadPos(
+ assertEquals(
+ 10, computeInitialLoadPos(
requestedStartPosition = 10,
requestedLoadSize = 10,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionRound() {
- assertEquals(10, computeInitialLoadPos(
+ assertEquals(
+ 10, computeInitialLoadPos(
requestedStartPosition = 13,
requestedLoadSize = 30,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionEndAdjusted() {
- assertEquals(70, computeInitialLoadPos(
+ assertEquals(
+ 70, computeInitialLoadPos(
requestedStartPosition = 99,
requestedLoadSize = 30,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
@Test
fun computeInitialLoadPositionEndAdjustedAndAligned() {
- assertEquals(70, computeInitialLoadPos(
+ assertEquals(
+ 70, computeInitialLoadPos(
requestedStartPosition = 99,
requestedLoadSize = 35,
pageSize = 10,
- totalCount = 100))
+ totalCount = 100
+ )
+ )
}
private fun validatePositionOffset(enablePlaceholders: Boolean) {
val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(enablePlaceholders)
- .build()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
val success = mutableListOf(false)
val dataSource = object : PositionalDataSource<String>() {
override fun loadInitial(
@@ -116,10 +133,10 @@
@Suppress("DEPRECATION")
PagedList.Builder(dataSource, config)
- .setFetchExecutor { it.run() }
- .setNotifyExecutor { it.run() }
- .setInitialKey(36)
- .build()
+ .setFetchExecutor(Executor { it.run() })
+ .setNotifyExecutor(Executor { it.run() })
+ .setInitialKey(36)
+ .build()
assertTrue(success[0])
}
@@ -156,14 +173,19 @@
}
val config = PagedList.Config.Builder()
- .setPageSize(10)
- .setEnablePlaceholders(enablePlaceholders)
- .build()
+ .setPageSize(10)
+ .setEnablePlaceholders(enablePlaceholders)
+ .build()
- dataSource.initExecutor(DirectExecutor.INSTANCE)
+ dataSource.initExecutor(DirectExecutor)
- dataSource.loadInitial(PositionalDataSource.LoadInitialParams(
- 0, config.initialLoadSizeHint, config.pageSize, config.enablePlaceholders)).get()
+ val params = PositionalDataSource.LoadInitialParams(
+ 0,
+ config.initialLoadSizeHint,
+ config.pageSize,
+ config.enablePlaceholders
+ )
+ dataSource.loadInitial(params).get()
}
@Test
@@ -239,8 +261,9 @@
it.onResult(emptyList(), 0, 1)
}
- private abstract class WrapperDataSource<in A, B>(private val source: PositionalDataSource<A>)
- : PositionalDataSource<B>() {
+ private abstract class WrapperDataSource<in A : Any, B : Any>(
+ private val source: PositionalDataSource<A>
+ ) : PositionalDataSource<B>() {
override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
source.addInvalidatedCallback(onInvalidatedCallback)
}
@@ -249,13 +272,10 @@
source.removeInvalidatedCallback(onInvalidatedCallback)
}
- override fun invalidate() {
- source.invalidate()
- }
+ override fun invalidate() = source.invalidate()
- override fun isInvalid(): Boolean {
- return source.isInvalid
- }
+ override val isInvalid
+ get() = source.isInvalid
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
source.loadInitial(params, object : LoadInitialCallback<A>() {
@@ -288,20 +308,17 @@
protected abstract fun convert(source: List<A>): List<B>
}
- private class StringWrapperDataSource<in A>(source: PositionalDataSource<A>)
- : WrapperDataSource<A, String>(source) {
+ private class StringWrapperDataSource<in A : Any>(source: PositionalDataSource<A>) :
+ WrapperDataSource<A, String>(source) {
override fun convert(source: List<A>): List<String> {
return source.map { it.toString() }
}
}
- class ListDataSource<T>(val list: List<T>) : PositionalDataSource<T>() {
+ class ListDataSource<T : Any>(val list: List<T>) : PositionalDataSource<T>() {
private var error = false
- override fun loadInitial(
- params: PositionalDataSource.LoadInitialParams,
- callback: PositionalDataSource.LoadInitialCallback<T>
- ) {
+ override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<T>) {
if (error) {
callback.onError(ERROR)
error = false
@@ -309,8 +326,8 @@
}
val totalCount = list.size
- val position = PositionalDataSource.computeInitialLoadPosition(params, totalCount)
- val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, totalCount)
+ val position = computeInitialLoadPosition(params, totalCount)
+ val loadSize = computeInitialLoadSize(params, position, totalCount)
// for simplicity, we could return everything immediately,
// but we tile here since it's expected behavior
@@ -318,20 +335,14 @@
callback.onResult(sublist, position, totalCount)
}
- override fun loadRange(
- params: PositionalDataSource.LoadRangeParams,
- callback: PositionalDataSource.LoadRangeCallback<T>
- ) {
+ override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<T>) {
if (error) {
callback.onError(ERROR)
error = false
return
}
callback.onResult(
- list.subList(
- params.startPosition,
- params.startPosition + params.loadSize
- )
+ list.subList(params.startPosition, params.startPosition + params.loadSize)
)
}
@@ -372,11 +383,11 @@
verifyNoMoreInteractions(loadRangeCallback)
// check invalidation behavior
- val invalCallback = mock(DataSource.InvalidatedCallback::class.java)
- wrapper.addInvalidatedCallback(invalCallback)
+ val invalidCallback = mock(DataSource.InvalidatedCallback::class.java)
+ wrapper.addInvalidatedCallback(invalidCallback)
orig.invalidate()
- verify(invalCallback).onInvalidated()
- verifyNoMoreInteractions(invalCallback)
+ verify(invalidCallback).onInvalidated()
+ verifyNoMoreInteractions(invalidCallback)
// verify invalidation
orig.invalidate()
@@ -390,26 +401,18 @@
@Test
fun testListConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.mapByPage { page -> page.map { it.toString() } }
+ dataSource.mapByPage(Function { page -> page.map { it.toString() } })
}
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource { dataSource ->
- dataSource.map { it.toString() }
- }
-
- @Test
- fun testGetKey() {
- val source = ListDataSource(listOf("a", "b"))
- assertEquals(null, source.getKey("a"))
- assertEquals(1, source.getKey(1, "a"))
- assertEquals(1, source.getKey(1, null))
+ dataSource.map(Function { it.toString() })
}
@Test
fun testInvalidateToWrapper() {
val orig = ListDataSource(listOf(0, 1, 2))
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
orig.invalidate()
assertTrue(wrapper.isInvalid)
@@ -418,7 +421,7 @@
@Test
fun testInvalidateFromWrapper() {
val orig = ListDataSource(listOf(0, 1, 2))
- val wrapper = orig.map { it.toString() }
+ val wrapper = orig.map<String>(Function { it.toString() })
wrapper.invalidate()
assertTrue(orig.isInvalid)
diff --git a/paging/common/src/test/java/androidx/paging/futures/FuturesTest.kt b/paging/common/src/test/kotlin/androidx/paging/futures/FuturesTest.kt
similarity index 89%
rename from paging/common/src/test/java/androidx/paging/futures/FuturesTest.kt
rename to paging/common/src/test/kotlin/androidx/paging/futures/FuturesTest.kt
index 9301bd0..d740578 100644
--- a/paging/common/src/test/java/androidx/paging/futures/FuturesTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/futures/FuturesTest.kt
@@ -16,8 +16,8 @@
package androidx.paging.futures
+import androidx.arch.core.util.Function
import androidx.concurrent.futures.ResolvableFuture
-import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import org.junit.Assert.assertEquals
@@ -35,19 +35,19 @@
if (type == guava) {
val future = SettableFuture.create<String>()
- val wrapper = Futures.transform(
- future,
- com.google.common.base.Function<String, String> { it -> it },
- DirectExecutor.INSTANCE)
+ val wrapper = future.transform(
+ Function<String, String> { it },
+ DirectExecutor
+ )
tester(future, wrapper)
} else {
val future = ResolvableFuture.create<String>()
- val wrapper = androidx.paging.futures.Futures.transform(
- future,
- androidx.arch.core.util.Function<String, String> { it -> it },
- DirectExecutor.INSTANCE)
+ val wrapper = future.transform(
+ Function<String, String> { it },
+ DirectExecutor
+ )
tester(future, wrapper)
}
}
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
index e10301a..cc831a3 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/ItemDataSource.kt
@@ -60,8 +60,8 @@
}
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
- val position = PositionalDataSource.computeInitialLoadPosition(params, COUNT)
- val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, COUNT)
+ val position = computeInitialLoadPosition(params, COUNT)
+ val loadSize = computeInitialLoadSize(params, position, COUNT)
val data = loadRangeInternal(position, loadSize)
if (data == null) {
callback.onError(RetryableItemError())
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
index 474ac25..677e2f8 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/custom/PagedListSampleActivity.kt
@@ -69,34 +69,40 @@
adapter.currentList?.retry()
}
- adapter.addLoadStateListener { type, state, _ ->
- val button = when (type) {
- PagedList.LoadType.REFRESH -> buttonRefresh
- PagedList.LoadType.START -> buttonStart
- PagedList.LoadType.END -> buttonEnd
- }
- when (state) {
- PagedList.LoadState.IDLE -> {
- button.text = "Idle"
- button.isEnabled = type == PagedList.LoadType.REFRESH
+ adapter.addLoadStateListener(object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ val button = when (type) {
+ PagedList.LoadType.REFRESH -> buttonRefresh
+ PagedList.LoadType.START -> buttonStart
+ PagedList.LoadType.END -> buttonEnd
}
- PagedList.LoadState.LOADING -> {
- button.text = "Loading"
- button.isEnabled = false
- }
- PagedList.LoadState.DONE -> {
- button.text = "Done"
- button.isEnabled = false
- }
- PagedList.LoadState.ERROR -> {
- button.text = "Error"
- button.isEnabled = false
- }
- PagedList.LoadState.RETRYABLE_ERROR -> {
- button.text = "Error"
- button.isEnabled = true
+ when (state) {
+ PagedList.LoadState.IDLE -> {
+ button.text = "Idle"
+ button.isEnabled = type == PagedList.LoadType.REFRESH
+ }
+ PagedList.LoadState.LOADING -> {
+ button.text = "Loading"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.DONE -> {
+ button.text = "Done"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.ERROR -> {
+ button.text = "Error"
+ button.isEnabled = false
+ }
+ PagedList.LoadState.RETRYABLE_ERROR -> {
+ button.text = "Error"
+ button.isEnabled = true
+ }
}
}
- }
+ })
}
}
diff --git a/paging/runtime/ktx/build.gradle b/paging/runtime/ktx/build.gradle
index ab8154d..7113572 100644
--- a/paging/runtime/ktx/build.gradle
+++ b/paging/runtime/ktx/build.gradle
@@ -38,12 +38,13 @@
api(project(":paging:paging-runtime"))
// Ensure that the -ktx dependency graph mirrors the Java dependency graph
api(project(":paging:paging-common-ktx"))
+ implementation(KOTLIN_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
- androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ARCH_CORE_TESTING)
+ androidTestImplementation(ESPRESSO_CORE)
}
androidx {
diff --git a/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt b/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt
index 07e0bac..52343f4 100644
--- a/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt
+++ b/paging/runtime/ktx/src/main/java/androidx/paging/LivePagedList.kt
@@ -34,17 +34,17 @@
*
* @see LivePagedListBuilder
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toLiveData(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
): LiveData<PagedList<Value>> {
return LivePagedListBuilder(this, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
- .setFetchExecutor(fetchExecutor)
- .build()
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
+ .setFetchExecutor(fetchExecutor)
+ .build()
}
/**
@@ -61,15 +61,15 @@
*
* @see LivePagedListBuilder
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toLiveData(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toLiveData(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
fetchExecutor: Executor = ArchTaskExecutor.getIOThreadExecutor()
): LiveData<PagedList<Value>> {
return LivePagedListBuilder(this, Config(pageSize))
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
- .setFetchExecutor(fetchExecutor)
- .build()
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
+ .setFetchExecutor(fetchExecutor)
+ .build()
}
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
index 156334e..416934f 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/AsyncPagedListDifferTest.kt
@@ -59,13 +59,13 @@
return differ
}
- private fun <V> createPagedListFromListAndPos(
+ private fun <V : Any> createPagedListFromListAndPos(
config: PagedList.Config,
data: List<V>,
initialKey: Int
): PagedList<V> {
@Suppress("DEPRECATION")
- return PagedList.Builder<Int, V>(ListDataSource(data), config)
+ return PagedList.Builder(ListDataSource(data), config)
.setInitialKey(initialKey)
.setNotifyExecutor(mMainThread)
.setFetchExecutor(mPageLoadingThread)
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
index 28aa2ba..56ec88c 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListBuilderTest.kt
@@ -181,9 +181,15 @@
assertNotNull(initPagedList!!)
assertTrue(initPagedList is InitialPagedList<*, *>)
- val loadStateListener = PagedList.LoadStateListener { type, state, error ->
- if (type == REFRESH) {
- loadStates.add(LoadState(type, state, error))
+ val loadStateListener = object : PagedList.LoadStateListener {
+ override fun onLoadStateChanged(
+ type: PagedList.LoadType,
+ state: PagedList.LoadState,
+ error: Throwable?
+ ) {
+ if (type == REFRESH) {
+ loadStates.add(LoadState(type, state, error))
+ }
}
}
initPagedList.addWeakLoadStateListener(loadStateListener)
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
index 2e85715..36e69c0 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/StringPagedList.kt
@@ -23,7 +23,7 @@
trailingNulls: Int,
vararg items: String
) : PagedList<String>(
- PagedStorage<String>(),
+ PagedStorage(),
TestExecutor(),
TestExecutor(),
null,
@@ -33,8 +33,7 @@
var detached = false
init {
- @Suppress("UNCHECKED_CAST")
- val keyedStorage = mStorage as PagedStorage<String>
+ val keyedStorage = getStorage()
keyedStorage.init(
leadingNulls,
list,
@@ -44,23 +43,20 @@
)
}
- internal override fun isContiguous(): Boolean = true
+ override val isContiguous = true
- override fun getLastKey(): Any? = null
+ override val lastKey: Any? = null
- override fun isDetached(): Boolean = detached
+ override val isDetached
+ get() = detached
override fun detach() {
detached = true
}
- override fun dispatchUpdatesSinceSnapshot(
- storageSnapshot: PagedList<String>,
- callback: Callback
- ) {
- }
+ override fun dispatchUpdatesSinceSnapshot(snapshot: PagedList<String>, callback: Callback) {}
- override fun dispatchCurrentLoadState(listener: LoadStateListener?) {}
+ override fun dispatchCurrentLoadState(listener: LoadStateListener) {}
override fun loadAroundInternal(index: Int) {}
@@ -74,15 +70,10 @@
override fun onPageInserted(start: Int, count: Int) {}
- override fun getDataSource(): DataSource<*, String> {
- return ListDataSource<String>(list)
- }
+ override val dataSource = ListDataSource(list)
- override fun onPagesRemoved(startOfDrops: Int, count: Int) {
- notifyRemoved(startOfDrops, count)
- }
+ override fun onPagesRemoved(startOfDrops: Int, count: Int) = notifyRemoved(startOfDrops, count)
- override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) {
+ override fun onPagesSwappedToPlaceholder(startOfDrops: Int, count: Int) =
notifyChanged(startOfDrops, count)
- }
}
diff --git a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
index 0a02746..2c2e6f2 100644
--- a/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
+++ b/paging/runtime/src/main/java/androidx/paging/AsyncPagedListDiffer.java
@@ -31,7 +31,7 @@
import java.util.concurrent.Executor;
/**
- * Helper object for mapping a {@link PagedList} into a
+ * Helper object for mapping a {@link androidx.paging.PagedList} into a
* {@link androidx.recyclerview.widget.RecyclerView.Adapter RecyclerView.Adapter}.
* <p>
* For simplicity, the {@link PagedListAdapter} wrapper class can often be used instead of the
@@ -39,9 +39,9 @@
* base class to support paging isn't convenient.
* <p>
* When consuming a {@link LiveData} of PagedList, you can observe updates and dispatch them
- * directly to {@link #submitList(PagedList)}. The AsyncPagedListDiffer then can present this
- * updating data set simply for an adapter. It listens to PagedList loading callbacks, and uses
- * DiffUtil on a background thread to compute updates as new PagedLists are received.
+ * directly to {@link #submitList(androidx.paging.PagedList)}. The AsyncPagedListDiffer then can
+ * present this updating data set simply for an adapter. It listens to PagedList loading callbacks,
+ * and uses DiffUtil on a background thread to compute updates as new PagedLists are received.
* <p>
* It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an
* adapter to acquire and present data objects.
@@ -135,7 +135,7 @@
* Called after the current PagedList has been updated.
*
* @param previousList The previous list, may be null.
- * @param currentList The new current list, may be null.
+ * @param currentList The new current list, may be null.
*/
void onCurrentListChanged(
@Nullable PagedList<T> previousList, @Nullable PagedList<T> currentList);
@@ -152,7 +152,9 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
int mMaxScheduledGeneration;
- @SuppressWarnings("WeakerAccess") /* synthetic access */
+ // Intentional higher visibility for synthetic access.
+ // LoadStateManager is marked internal, but we really intend it to be package-private.
+ @SuppressWarnings({"WeakerAccess", "KotlinInternalInJava"})
final PagedList.LoadStateManager mLoadStateManager = new PagedList.LoadStateManager() {
@Override
protected void onStateChanged(@NonNull PagedList.LoadType type,
@@ -164,7 +166,7 @@
}
};
@SuppressWarnings("WeakerAccess") // synthetic access
- PagedList.LoadStateListener mLoadStateListener = new PagedList.LoadStateListener() {
+ PagedList.LoadStateListener mLoadStateListener = new PagedList.LoadStateListener() {
@Override
public void onLoadStateChanged(@NonNull PagedList.LoadType type,
@NonNull PagedList.LoadState state, @Nullable Throwable error) {
@@ -180,9 +182,9 @@
* Convenience for {@code AsyncPagedListDiffer(new AdapterListUpdateCallback(adapter),
* new AsyncDifferConfig.Builder<T>(diffCallback).build();}
*
- * @param adapter Adapter that will receive update signals.
+ * @param adapter Adapter that will receive update signals.
* @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to
- * compare items in the list.
+ * compare items in the list.
*/
@SuppressWarnings("WeakerAccess")
public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
@@ -281,7 +283,7 @@
* may not be executed. If PagedList B is submitted immediately after PagedList A, and is
* committed directly, the callback associated with PagedList A will not be run.
*
- * @param pagedList The new PagedList.
+ * @param pagedList The new PagedList.
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
* it is committed.
*/
@@ -359,18 +361,19 @@
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
- final DiffUtil.DiffResult result;
- result = PagedStorageDiffHelper.computeDiff(
- oldSnapshot.mStorage,
- newSnapshot.mStorage,
+ //noinspection KotlinInternalInJava
+ final DiffUtil.DiffResult result = PagedStorageDiffHelper.computeDiff(
+ oldSnapshot.getStorage$paging_common(),
+ newSnapshot.getStorage$paging_common(),
mConfig.getDiffCallback());
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
+ //noinspection KotlinInternalInJava
latchPagedList(pagedList, newSnapshot, result,
- oldSnapshot.mLastLoad, commitCallback);
+ oldSnapshot.getLastLoad$paging_common(), commitCallback);
}
}
});
@@ -395,8 +398,10 @@
mSnapshot = null;
// dispatch update callback after updating mPagedList/mSnapshot
+ //noinspection KotlinInternalInJava
PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
- previousSnapshot.mStorage, newList.mStorage, diffResult);
+ previousSnapshot.getStorage$paging_common(), newList.getStorage$paging_common(),
+ diffResult);
newList.addWeakCallback(diffSnapshot, mPagedListCallback);
@@ -407,8 +412,10 @@
// Note: we don't take into account loads between new list snapshot and new list, but
// this is only a problem in rare cases when placeholders are disabled, and a load
// starts (for some reason) and finishes before diff completes.
- int newPosition = PagedStorageDiffHelper.transformAnchorIndex(
- diffResult, previousSnapshot.mStorage, diffSnapshot.mStorage, lastAccessIndex);
+ @SuppressWarnings("KotlinInternalInJava") int newPosition =
+ PagedStorageDiffHelper.transformAnchorIndex(
+ diffResult, previousSnapshot.getStorage$paging_common(),
+ diffSnapshot.getStorage$paging_common(), lastAccessIndex);
// Trigger load in new list at this position, clamped to list bounds.
// This is a load, not just an update of last load position, since the new list may be
@@ -436,7 +443,6 @@
* Add a PagedListListener to receive updates when the current PagedList changes.
*
* @param listener Listener to receive updates.
- *
* @see #getCurrentList()
* @see #removePagedListListener(PagedListListener)
*/
@@ -462,7 +468,6 @@
* current REFRESH, START, and END states.
*
* @param listener Listener to receive updates.
- *
* @see #removeLoadStateListListener(PagedList.LoadStateListener)
*/
public void addLoadStateListener(@NonNull PagedList.LoadStateListener listener) {
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedList.java b/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
index a4b04ca..dfdfbef 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedList.java
@@ -98,9 +98,11 @@
mCurrentData.getDataSource().removeInvalidatedCallback(mCallback);
dataSource.addInvalidatedCallback(mCallback);
- mCurrentData.setInitialLoadState(PagedList.LoadState.LOADING, null);
+ //noinspection KotlinInternalInJava
+ mCurrentData.setInitialLoadState$paging_common(PagedList.LoadState.LOADING, null);
- return PagedList.create(
+ //noinspection KotlinInternalInJava
+ return PagedList.create$paging_common(
dataSource,
mNotifyExecutor,
mFetchExecutor,
@@ -116,7 +118,8 @@
.isRetryableError(throwable)
? PagedList.LoadState.RETRYABLE_ERROR
: PagedList.LoadState.ERROR;
- mCurrentData.setInitialLoadState(loadState, throwable);
+ //noinspection KotlinInternalInJava
+ mCurrentData.setInitialLoadState$paging_common(loadState, throwable);
}
@Override
diff --git a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
index d156143..9fbcdb0 100644
--- a/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
+++ b/paging/runtime/src/main/java/androidx/paging/LivePagedListBuilder.java
@@ -27,14 +27,14 @@
import java.util.concurrent.Executor;
/**
- * Builder for {@code LiveData<PagedList>}, given a {@link DataSource.Factory} and a
- * {@link PagedList.Config}.
+ * Builder for {@code LiveData<PagedList>}, given a {@link androidx.paging.DataSource.Factory} and a
+ * {@link androidx.paging.PagedList.Config}.
* <p>
* The required parameters are in the constructor, so you can simply construct and build, or
* optionally enable extra features (such as initial load key, or BoundaryCallback).
*
* @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
- * you're using PositionalDataSource.
+ * you're using PositionalDataSource.
* @param <Value> Item type being presented.
*/
public final class LivePagedListBuilder<Key, Value> {
@@ -99,8 +99,8 @@
}
/**
- * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load
- * additional data from network when paging from local storage.
+ * Sets a {@link androidx.paging.PagedList.BoundaryCallback} on each PagedList created,
+ * typically used to load additional data from network when paging from local storage.
* <p>
* Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
* method is not called, or {@code null} is passed, you will not be notified when each
diff --git a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
index 5b28d8a..9877ffb 100644
--- a/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
+++ b/paging/runtime/src/main/java/androidx/paging/PagedListAdapter.java
@@ -27,7 +27,7 @@
/**
* {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from
- * {@link PagedList}s in a {@link RecyclerView}.
+ * {@link androidx.paging.PagedList}s in a {@link RecyclerView}.
* <p>
* This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common
* default behavior for item counting, and listening to PagedList update callbacks.
diff --git a/paging/rxjava2/ktx/build.gradle b/paging/rxjava2/ktx/build.gradle
index 2f49334..2ec023d 100644
--- a/paging/rxjava2/ktx/build.gradle
+++ b/paging/rxjava2/ktx/build.gradle
@@ -38,12 +38,13 @@
api(project(":paging:paging-rxjava2"))
// Ensure that the -ktx dependency graph mirrors the Java dependency graph
api(project(":paging:paging-common-ktx"))
+ implementation(KOTLIN_STDLIB)
androidTestImplementation(ANDROIDX_TEST_EXT_JUNIT)
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
- androidTestImplementation(ESPRESSO_CORE)
androidTestImplementation(ARCH_CORE_TESTING)
+ androidTestImplementation(ESPRESSO_CORE)
}
androidx {
diff --git a/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt b/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
index 2a07c7a..3fa1a22 100644
--- a/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
+++ b/paging/rxjava2/ktx/src/androidTest/java/androidx/paging/RxPagedListTest.kt
@@ -70,7 +70,7 @@
params: LoadInitialParams,
callback: LoadInitialCallback<String>
) {
- callback.onResult(listOf<String>(), 0, 0)
+ callback.onResult(listOf(), 0, 0)
}
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<String>) {
diff --git a/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt b/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt
index 29378f3..539fda5 100644
--- a/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt
+++ b/paging/rxjava2/ktx/src/main/java/androidx/paging/RxPagedList.kt
@@ -21,7 +21,7 @@
import io.reactivex.Observable
import io.reactivex.Scheduler
-private fun <Key, Value> createRxPagedListBuilder(
+private fun <Key : Any, Value : Any> createRxPagedListBuilder(
dataSourceFactory: DataSource.Factory<Key, Value>,
config: PagedList.Config,
initialLoadKey: Key?,
@@ -30,8 +30,8 @@
notifyScheduler: Scheduler?
): RxPagedListBuilder<Key, Value> {
val builder = RxPagedListBuilder(dataSourceFactory, config)
- .setInitialLoadKey(initialLoadKey)
- .setBoundaryCallback(boundaryCallback)
+ .setInitialLoadKey(initialLoadKey)
+ .setBoundaryCallback(boundaryCallback)
if (fetchScheduler != null) builder.setFetchScheduler(fetchScheduler)
if (notifyScheduler != null) builder.setNotifyScheduler(notifyScheduler)
return builder
@@ -56,7 +56,7 @@
* @see RxPagedListBuilder
* @see toFlowable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toObservable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -64,12 +64,13 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildObservable()
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildObservable()
}
/**
@@ -91,7 +92,7 @@
* @see RxPagedListBuilder
* @see toFlowable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toObservable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toObservable(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -99,12 +100,13 @@
notifyScheduler: Scheduler? = null
): Observable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildObservable()
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildObservable()
}
/**
@@ -127,7 +129,7 @@
* @see RxPagedListBuilder
* @see toObservable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toFlowable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
config: PagedList.Config,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -136,12 +138,13 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = config,
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = config,
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildFlowable(backpressureStrategy)
}
/**
@@ -164,7 +167,7 @@
* @see RxPagedListBuilder
* @see toObservable
*/
-fun <Key, Value> DataSource.Factory<Key, Value>.toFlowable(
+fun <Key : Any, Value : Any> DataSource.Factory<Key, Value>.toFlowable(
pageSize: Int,
initialLoadKey: Key? = null,
boundaryCallback: PagedList.BoundaryCallback<Value>? = null,
@@ -173,10 +176,11 @@
backpressureStrategy: BackpressureStrategy = BackpressureStrategy.LATEST
): Flowable<PagedList<Value>> {
return createRxPagedListBuilder(
- dataSourceFactory = this,
- config = Config(pageSize),
- initialLoadKey = initialLoadKey,
- boundaryCallback = boundaryCallback,
- fetchScheduler = fetchScheduler,
- notifyScheduler = notifyScheduler).buildFlowable(backpressureStrategy)
+ dataSourceFactory = this,
+ config = Config(pageSize),
+ initialLoadKey = initialLoadKey,
+ boundaryCallback = boundaryCallback,
+ fetchScheduler = fetchScheduler,
+ notifyScheduler = notifyScheduler
+ ).buildFlowable(backpressureStrategy)
}
diff --git a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java
index a070466..271a56d 100644
--- a/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java
+++ b/paging/rxjava2/src/main/java/androidx/paging/RxPagedListBuilder.java
@@ -33,7 +33,7 @@
/**
* Builder for {@code Observable<PagedList>} or {@code Flowable<PagedList>}, given a
- * {@link DataSource.Factory} and a {@link PagedList.Config}.
+ * {@link androidx.paging.DataSource.Factory} and a {@link androidx.paging.PagedList.Config}.
* <p>
* The required parameters are in the constructor, so you can simply construct and build, or
* optionally enable extra features (such as initial load key, or BoundaryCallback).
@@ -90,8 +90,10 @@
* @param pageSize Size of pages to load.
*/
@SuppressWarnings("unused")
- public RxPagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
- int pageSize) {
+ public RxPagedListBuilder(
+ @NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ int pageSize
+ ) {
this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
}
@@ -112,8 +114,8 @@
}
/**
- * Sets a {@link PagedList.BoundaryCallback} on each PagedList created, typically used to load
- * additional data from network when paging from local storage.
+ * Sets a {@link androidx.paging.PagedList.BoundaryCallback} on each PagedList created,
+ * typically used to load additional data from network when paging from local storage.
*
* Pass a BoundaryCallback to listen to when the PagedList runs out of data to load. If this
* method is not called, or {@code null} is passed, you will not be notified when each
@@ -149,8 +151,8 @@
* receiving PagedLists will also receive the internal updates to the PagedList.
*
* @param scheduler Scheduler that receives PagedList updates, and where
- * {@link PagedList.Callback} calls are dispatched. Generally, this is the
- * UI/main thread.
+ * {@link androidx.paging.PagedList.Callback} calls are dispatched. Generally,
+ * this is the UI/main thread.
* @return this
*/
@NonNull
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 57de3cc..bd3a648 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -895,6 +895,7 @@
dbVerifier = verifier
)
val parsedQuery = parser.process()
+ @Suppress("UNCHECKED_CAST")
handler(parsedQuery as T, invocation)
true
}
diff --git a/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt b/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
index 5687ced..e921567 100644
--- a/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
+++ b/room/integration-tests/incremental-annotation-processing/src/test/kotlin/androidx/room/gradle/RoomIncrementalAnnotationProcessingTest.kt
@@ -40,10 +40,9 @@
fun parameters() = listOf(true, false)
private const val SRC_DIR = "src/main/java"
- private const val GEN_SRC_DIR = "build/generated/source/apt/debug"
+ private const val GEN_SRC_DIR = "build/generated/ap_generated_sources/debug/out/"
private const val GEN_RES_DIR = "build/generated/resources"
- private const val CLASS_DIR =
- "build/intermediates/javac/debug/compileDebugJavaWithJavac/classes"
+ private const val CLASS_DIR = "build/intermediates/javac/debug/classes"
private const val CLEAN_TASK = ":clean"
private const val COMPILE_TASK = ":compileDebugJavaWithJavac"
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/JavaPojoConverter.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/JavaPojoConverter.kt
index 9629230..54945ca 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/JavaPojoConverter.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/JavaPojoConverter.kt
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-@file:Suppress("unused") // Not used by gen code, but tests an annotation processor case.
+// Not used by gen code, but tests an annotation processor case.
+@file:Suppress("unused", "UNUSED_PARAMETER")
package androidx.room.integration.kotlintestapp.vo
diff --git a/samples/BiometricDemos/lint-baseline.xml b/samples/BiometricDemos/lint-baseline.xml
index e631c46..2e8004c 100644
--- a/samples/BiometricDemos/lint-baseline.xml
+++ b/samples/BiometricDemos/lint-baseline.xml
@@ -1,13 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 3.4.0" client="gradle" variant="all" version="3.4.0">
+<issues format="5" by="lint 3.5.0-beta04" client="gradle" variant="debug" version="3.5.0-beta04">
<issue
- id="ObsoleteLintCustomCheck"
- message="Lint found an issue registry (`androidx.build.lint.AndroidXIssueRegistry`) which did not specify the Lint API version it was compiled with.

**This means that the lint checks are likely not compatible.**

If you are the author of this lint check, make your lint `IssueRegistry` class contain
 override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
or from Java,
 @Override public int getApi() { return com.android.tools.lint.detector.api.ApiKt.CURRENT_API; }

If you are just using lint checks from a third party library you have no control over, you can disable these lint checks (if they misbehave) like this:

 android {
 lintOptions {
 disable "BanKeepAnnotation",
 "BanParcelableUsage",
 "BanTargetApiAnnotation",
 "MissingTestSizeAnnotation",
 "ObsoleteBuildCompat"
 }
 }
"
- includedVariants="debug"
- excludedVariants="release">
+ id="NewApi"
+ message="Exception requires API level 23 (current min is 23): `android.security.keystore.KeyPermanentlyInvalidatedException`, and having a surrounding/preceding version check **does** not help since prior to API level 19, just **loading** the class will cause a crash. Consider marking the surrounding class with `RequiresApi(19)` to ensure that the class is never loaded except when on API 19 or higher."
+ errorLine1=" } catch (KeyPermanentlyInvalidatedException e) {"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
<location
- file="../../../../out/buildSrc/lint-checks/build/libs/lint-checks.jar"/>
+ file="src/main/java/com/example/android/biometric/BiometricPromptDemo.java"
+ line="314"
+ column="18"/>
</issue>
<issue
diff --git a/slices/core/src/main/java/androidx/slice/compat/CompatPermissionManager.java b/slices/core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
index f7fc1f2..99fff44 100644
--- a/slices/core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
+++ b/slices/core/src/main/java/androidx/slice/compat/CompatPermissionManager.java
@@ -19,6 +19,7 @@
import static androidx.core.content.PermissionChecker.PERMISSION_DENIED;
import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -61,6 +62,7 @@
return mContext.getSharedPreferences(mPrefsName, Context.MODE_PRIVATE);
}
+ @SuppressLint("WrongConstant")
public int checkSlicePermission(Uri uri, int pid, int uid) {
if (uid == mMyUid) {
return PERMISSION_GRANTED;
diff --git a/testutils/src/main/java/androidx/testutils/ActivityScenario.kt b/testutils/src/main/java/androidx/testutils/ActivityScenario.kt
new file mode 100644
index 0000000..f87776a
--- /dev/null
+++ b/testutils/src/main/java/androidx/testutils/ActivityScenario.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.testutils
+
+import android.app.Activity
+import androidx.test.core.app.ActivityScenario
+
+/**
+ * Run [block] using [ActivityScenario.onActivity], returning the result of the block.
+ */
+inline fun <reified A : Activity, T : Any> ActivityScenario<A>.withActivity(
+ crossinline block: A.() -> T
+): T {
+ lateinit var value: T
+ onActivity { activity ->
+ value = block(activity)
+ }
+ return value
+}
diff --git a/transition/src/main/java/androidx/transition/CanvasUtils.java b/transition/src/main/java/androidx/transition/CanvasUtils.java
index e225b33..9268e63 100644
--- a/transition/src/main/java/androidx/transition/CanvasUtils.java
+++ b/transition/src/main/java/androidx/transition/CanvasUtils.java
@@ -16,6 +16,7 @@
package androidx.transition;
+import android.annotation.SuppressLint;
import android.graphics.Canvas;
import android.os.Build;
@@ -35,6 +36,7 @@
*
* IMPORTANT: This method doesn't work on Pie! It will thrown an exception instead
*/
+ @SuppressLint("SoonBlockedPrivateApi")
static void enableZ(@NonNull Canvas canvas, boolean enable) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// no shadows yet added into a platform
diff --git a/ui/core/src/main/java/androidx/ui/input/GapBuffer.kt b/ui/core/src/main/java/androidx/ui/input/GapBuffer.kt
new file mode 100644
index 0000000..34771fa
--- /dev/null
+++ b/ui/core/src/main/java/androidx/ui/input/GapBuffer.kt
@@ -0,0 +1,276 @@
+/*
+ * 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.ui.input
+
+import android.util.Log
+
+/**
+ * The gap buffer implementation
+ *
+ * @param initBuffer An initial buffer. This class takes ownership of this object, so do not modify
+ * array after passing to this constructor
+ * @param initGapStart An initial inclusive gap start offset of the buffer
+ * @param initGapEnd An initial exclusive gap end offset of the buffer
+ */
+private class GapBuffer(initBuffer: CharArray, initGapStart: Int, initGapEnd: Int) {
+
+ /**
+ * The current capacity of the buffer
+ */
+ private var capacity = initBuffer.size
+
+ /**
+ * The buffer
+ */
+ private var buffer = initBuffer
+
+ /**
+ * The inclusive start offset of the gap
+ */
+ private var gapStart = initGapStart
+
+ /**
+ * The exclusive end offset of the gap
+ */
+ private var gapEnd = initGapEnd
+
+ /**
+ * The length of the gap.
+ */
+ private fun gapLength(): Int = gapEnd - gapStart
+
+ /**
+ * Check if the gap has a requested size, and allocate new buffer if there is enough space.
+ */
+ private fun makeSureAvailableSpace(requestSize: Int) {
+ if (requestSize <= gapLength()) {
+ return
+ }
+
+ // Allocating necessary memory space by doubling the array size.
+ val necessarySpace = requestSize - gapLength()
+ var newCapacity = capacity * 2
+ while ((newCapacity - capacity) < necessarySpace) {
+ newCapacity *= 2
+ }
+
+ //
+ if (newCapacity >= 512 * 1024) {
+ Log.w("GapBuffer",
+ "Allocating $newCapacity buffer. Consider alternative data structure.")
+ }
+
+ val newBuffer = CharArray(newCapacity)
+ System.arraycopy(buffer, 0, newBuffer, 0, gapStart)
+ val tailLength = capacity - gapEnd
+ val newEnd = newCapacity - tailLength
+ System.arraycopy(buffer, gapEnd, newBuffer, newEnd, tailLength)
+
+ buffer = newBuffer
+ capacity = newCapacity
+ gapEnd = newEnd
+ }
+
+ /**
+ * Delete the given range of the text.
+ */
+ private fun delete(start: Int, end: Int) {
+ if (start < gapStart && end <= gapStart) {
+ // The remove happens in the head buffer. Copy the tail part of the head buffer to the
+ // tail buffer.
+ //
+ // Example:
+ // Input:
+ // buffer: ABCDEFGHIJKLMNOPQ*************RSTUVWXYZ
+ // del region: |-----|
+ //
+ // First, move the remaining part of the head buffer to the tail buffer.
+ // buffer: ABCDEFGHIJKLMNOPQ*****KLKMNOPQRSTUVWXYZ
+ // move data: ^^^^^^^ => ^^^^^^^^
+ //
+ // Then, delete the given range. (just updating gap positions)
+ // buffer: ABCD******************KLKMNOPQRSTUVWXYZ
+ // del region: |-----|
+ //
+ // Output: ABCD******************KLKMNOPQRSTUVWXYZ
+ val copyLen = gapStart - end
+ System.arraycopy(buffer, end, buffer, gapEnd - copyLen, copyLen)
+ gapStart = start
+ gapEnd -= copyLen
+ } else if (start < gapStart && end >= gapStart) {
+ // The remove happens with accrossing the gap region. Just update the gap position
+ //
+ // Example:
+ // Input:
+ // buffer: ABCDEFGHIJKLMNOPQ************RSTUVWXYZ
+ // del region: |-------------------|
+ //
+ // Output: ABCDEFGHIJKL********************UVWXYZ
+ gapEnd = end + gapLength()
+ gapStart = start
+ } else { // start > gapStart && end > gapStart
+ // The remove happens in the tail buffer. Copy the head part of the tail buffer to the
+ // head buffer.
+ //
+ // Example:
+ // Input:
+ // buffer: ABCDEFGHIJKL************MNOPQRSTUVWXYZ
+ // del region: |-----|
+ //
+ // First, move the remaining part in the tail buffer to the head buffer.
+ // buffer: ABCDEFGHIJKLMNO*********MNOPQRSTUVWXYZ
+ // move dat: ^^^ <= ^^^
+ //
+ // Then, delete the given range. (just updating gap positions)
+ // buffer: ABCDEFGHIJKLMNO******************VWXYZ
+ // del region: |-----|
+ //
+ // Output: ABCDEFGHIJKLMNO******************VWXYZ
+ val startInBuffer = start + gapLength()
+ val endInBuffer = end + gapLength()
+ val copyLen = startInBuffer - gapEnd
+ System.arraycopy(buffer, gapEnd, buffer, gapStart, copyLen)
+ gapStart += copyLen
+ gapEnd = endInBuffer
+ }
+ }
+
+ /**
+ * Replace the certain region of text with given text
+ *
+ * @param start an inclusive start offset for replacement.
+ * @param end an exclusive end offset for replacement
+ * @param text a text to replace
+ */
+ fun replace(start: Int, end: Int, text: String) {
+ makeSureAvailableSpace(text.length - (end - start))
+
+ delete(start, end)
+
+ text.toCharArray(buffer, gapStart)
+ gapStart += text.length
+ }
+
+ /**
+ * Write the current text into outBuf.
+ * @param buiilder The output string builder
+ */
+ internal fun append(builder: StringBuilder) {
+ builder.append(buffer, 0, gapStart)
+ builder.append(buffer, gapEnd, capacity - gapEnd)
+ }
+
+ /**
+ * The lengh of this gap buffer.
+ *
+ * This doesn't include internal hidden gap length.
+ */
+ fun length() = capacity - gapLength()
+
+ override fun toString(): String = StringBuilder().apply { append(this) }.toString()
+}
+
+/**
+ * An editing buffer that uses Gap Buffer only around the cursor location.
+ *
+ * Different from the original gap buffer, this gap buffer doesn't convert all given text into
+ * mutable buffer. Instead, this gap buffer converts cursor around text into mutable gap buffer
+ * for saving construction time and memory space. If text modification outside of the gap buffer
+ * is requested, this class flush the buffer and create new String, then start new gap buffer.
+ *
+ * @param text The initial text
+ */
+internal class PartialGapBuffer(var text: String) {
+ internal companion object {
+ const val BUF_SIZE = 255
+ const val SURROUNDING_SIZE = 64
+ const val NOWHERE = -1
+ }
+
+ private var buffer: GapBuffer? = null
+ private var bufStart = NOWHERE
+ private var bufEnd = NOWHERE
+
+ /**
+ * The text length
+ */
+ val length: Int
+ get() {
+ val buffer = buffer ?: return text.length
+ return text.length - (bufEnd - bufStart) + buffer.length()
+ }
+
+ /**
+ * Replace the certain region of text with given text
+ *
+ * @param start an inclusive start offset for replacement.
+ * @param end an exclusive end offset for replacement
+ * @param text a text to replace
+ */
+ fun replace(start: Int, end: Int, buf: String) {
+ val buffer = buffer
+ if (buffer == null) { // First time to create gap buffer
+ val charArray = CharArray(Math.max(BUF_SIZE, buf.length + 2 * SURROUNDING_SIZE))
+
+ // Convert surrounding text into buffer.
+ val leftCopyCount = Math.min(start, SURROUNDING_SIZE)
+ val rightCopyCount = Math.min(text.length - end, SURROUNDING_SIZE)
+
+ // Copy left surrounding
+ text.toCharArray(charArray, 0, start - leftCopyCount, start)
+
+ // Copy right surrounding
+ text.toCharArray(charArray, charArray.size - rightCopyCount, end, end + rightCopyCount)
+
+ // Copy given text into buffer
+ buf.toCharArray(charArray, leftCopyCount)
+
+ this.buffer = GapBuffer(charArray,
+ leftCopyCount + buf.length, // gap start
+ charArray.size - rightCopyCount) // gap end
+ bufStart = start - leftCopyCount
+ bufEnd = end + rightCopyCount
+ return
+ }
+
+ // Convert user space offset into buffer space offset
+ val bufferStart = start - bufStart
+ val bufferEnd = end - bufStart
+ if (bufferStart < 0 || bufferEnd > buffer.length()) {
+ // Text modification outside of gap buffer has requested. Flush the buffer and try it
+ // again.
+ // TODO(nona): Performance: instead of flushing, reset the bufStart/bufEnd with copying
+ // additional surroundings.
+ text = toString()
+ this.buffer = null
+ bufStart = NOWHERE
+ bufEnd = NOWHERE
+ return replace(start, end, buf)
+ }
+
+ buffer.replace(bufferStart, bufferEnd, buf)
+ }
+
+ override fun toString(): String {
+ val b = buffer ?: return text
+ val sb = StringBuilder()
+ sb.append(text, 0, bufStart)
+ b.append(sb)
+ sb.append(text, bufEnd, text.length)
+ return sb.toString()
+ }
+}
\ No newline at end of file
diff --git a/ui/core/src/test/java/androidx/ui/input/GapBufferTest.kt b/ui/core/src/test/java/androidx/ui/input/GapBufferTest.kt
new file mode 100644
index 0000000..d781619
--- /dev/null
+++ b/ui/core/src/test/java/androidx/ui/input/GapBufferTest.kt
@@ -0,0 +1,552 @@
+/*
+ * 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.ui.input
+
+import androidx.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.random.Random
+
+@SmallTest
+@RunWith(JUnit4::class)
+class GapBufferTest {
+ @Test
+ fun insertTest_insert_to_empty_string() {
+ assertEquals("A", PartialGapBuffer("").apply {
+ replace(0, 0, "A")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_insert_and_append() {
+ assertEquals("BA", PartialGapBuffer("").apply {
+ replace(0, 0, "A")
+ replace(0, 0, "B")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_insert_and_prepend() {
+ assertEquals("AB", PartialGapBuffer("").apply {
+ replace(0, 0, "A")
+ replace(1, 1, "B")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_insert_and_insert_into_middle() {
+ assertEquals("ABA", PartialGapBuffer("").apply {
+ replace(0, 0, "AA")
+ replace(1, 1, "B")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_prepend() {
+ assertEquals("AXX", PartialGapBuffer("XX").apply {
+ replace(0, 0, "A")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_insert_into_middle() {
+ assertEquals("XAX", PartialGapBuffer("XX").apply {
+ replace(1, 1, "A")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_append() {
+ assertEquals("XXA", PartialGapBuffer("XX").apply {
+ replace(2, 2, "A")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_prepend_and_prepend() {
+ assertEquals("BAXX", PartialGapBuffer("XX").apply {
+ replace(0, 0, "A")
+ replace(0, 0, "B")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_prepend_and_append() {
+ assertEquals("ABXX", PartialGapBuffer("XX").apply {
+ replace(0, 0, "A")
+ replace(1, 1, "B")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_prepend_and_insert_middle() {
+ assertEquals("AXBX", PartialGapBuffer("XX").apply {
+ replace(0, 0, "A")
+ replace(2, 2, "B")
+ }.toString())
+ }
+
+ @Test
+ fun insertTest_intoExistingText_insert_two_chars_and_append() {
+ assertEquals("ABAXX", PartialGapBuffer("XX").apply {
+ replace(0, 0, "AA")
+ replace(1, 1, "B")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_from_head() {
+ assertEquals("BC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 1, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_middle() {
+ assertEquals("AC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_tail() {
+ assertEquals("AB", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(2, 3, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_two_head() {
+ assertEquals("C", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_two_tail() {
+ assertEquals("A", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 3, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_with_two_instruction_from_haed() {
+ assertEquals("C", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 1, "")
+ replace(0, 1, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delet_with_two_instruction_from_head_and_tail() {
+ assertEquals("B", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 1, "")
+ replace(1, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delet_with_two_instruction_from_tail() {
+ assertEquals("A", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 2, "")
+ replace(1, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_three_chars() {
+ assertEquals("", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 3, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_insert_and_delete_three_chars_with_three_instructions() {
+ assertEquals("", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 1, "")
+ replace(0, 1, "")
+ replace(0, 1, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_from_head() {
+ assertEquals("BC", PartialGapBuffer("ABC").apply {
+ replace(0, 1, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_from_middle() {
+ assertEquals("AC", PartialGapBuffer("ABC").apply {
+ replace(1, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_from_tail() {
+ assertEquals("AB", PartialGapBuffer("ABC").apply {
+ replace(2, 3, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_two_chars_from_head() {
+ assertEquals("C", PartialGapBuffer("ABC").apply {
+ replace(0, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_two_chars_from_tail() {
+ assertEquals("A", PartialGapBuffer("ABC").apply {
+ replace(1, 3, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_head() {
+ assertEquals("C", PartialGapBuffer("ABC").apply {
+ replace(0, 1, "")
+ replace(0, 1, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_head_and_tail() {
+ assertEquals("B", PartialGapBuffer("ABC").apply {
+ replace(0, 1, "")
+ replace(1, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_two_chars_with_two_instruction_from_tail() {
+ assertEquals("A", PartialGapBuffer("ABC").apply {
+ replace(1, 2, "")
+ replace(1, 2, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_three_chars() {
+ assertEquals("", PartialGapBuffer("ABC").apply {
+ replace(0, 3, "")
+ }.toString())
+ }
+
+ @Test
+ fun deleteTest_fromExistingText_delete_three_chars_with_three_instructions() {
+ assertEquals("", PartialGapBuffer("ABC").apply {
+ replace(0, 1, "")
+ replace(0, 1, "")
+ replace(0, 1, "")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_head() {
+ assertEquals("XBC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 1, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_middle() {
+ assertEquals("AXC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 2, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_tail() {
+ assertEquals("ABX", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(2, 3, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_head_two_chars() {
+ assertEquals("XC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 2, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_middle_two_chars() {
+ assertEquals("AX", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 3, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_three_chars() {
+ assertEquals("X", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 3, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_one_char_with_two_chars_from_head() {
+ assertEquals("XYBC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 1, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_one_char_with_two_chars_from_middle() {
+ assertEquals("AXYC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 2, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_one_char_with_two_chars_from_tail() {
+ assertEquals("ABXY", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(2, 3, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_two_chars_with_two_chars_from_head() {
+ assertEquals("XYC", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 2, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_two_chars_with_two_chars_from_tail() {
+ assertEquals("AXY", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(1, 3, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_three_chars_with_two_char() {
+ assertEquals("XY", PartialGapBuffer("").apply {
+ replace(0, 0, "ABC")
+ replace(0, 3, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_head() {
+ assertEquals("XBC", PartialGapBuffer("ABC").apply {
+ replace(0, 1, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_middle() {
+ assertEquals("AXC", PartialGapBuffer("ABC").apply {
+ replace(1, 2, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_tail() {
+ assertEquals("ABX", PartialGapBuffer("ABC").apply {
+ replace(2, 3, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_two_chars_with_one_char_from_head() {
+ assertEquals("XC", PartialGapBuffer("ABC").apply {
+ replace(0, 2, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_two_chars_with_one_char_from_tail() {
+ assertEquals("AX", PartialGapBuffer("ABC").apply {
+ replace(1, 3, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_three_chars() {
+ assertEquals("X", PartialGapBuffer("ABC").apply {
+ replace(0, 3, "X")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_one_char_with_two_chars_from_head() {
+ assertEquals("XYBC", PartialGapBuffer("ABC").apply {
+ replace(0, 1, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_one_char_with_two_chars_from_middle() {
+ assertEquals("AXYC", PartialGapBuffer("ABC").apply {
+ replace(1, 2, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_one_char_with_two_chars_from_tail() {
+ assertEquals("ABXY", PartialGapBuffer("ABC").apply {
+ replace(2, 3, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_two_chars_with_two_chars_from_head() {
+ assertEquals("XYC", PartialGapBuffer("ABC").apply {
+ replace(0, 2, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_two_chars_with_two_chars_from_tail() {
+ assertEquals("AXY", PartialGapBuffer("ABC").apply {
+ replace(1, 3, "XY")
+ }.toString())
+ }
+
+ @Test
+ fun replaceTest_fromExistingText_three_chars_with_three_chars() {
+ assertEquals("XY", PartialGapBuffer("ABC").apply {
+ replace(0, 3, "XY")
+ }.toString())
+ }
+
+ // Compare with the result of StringBuffer. We trust the StringBuffer works correctly
+ private fun assertReplace(
+ start: Int,
+ end: Int,
+ str: String,
+ sb: StringBuffer,
+ gb: PartialGapBuffer
+ ) {
+ sb.replace(start, end, str)
+ gb.replace(start, end, str)
+ assertEquals(sb.toString(), gb.toString())
+ }
+
+ private val LONG_INIT_TEXT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(256)
+ private val SHORT_TEXT = "A"
+ private val MEDIUM_TEXT = "Hello, World"
+ private val LONG_TEXT = "abcdefghijklmnopqrstuvwxyz".repeat(16)
+
+ @Test
+ fun longTextTest_keep_insertion() {
+ val sb = StringBuffer(LONG_INIT_TEXT)
+ val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+ var c = 256 // cursor
+ assertReplace(c, c, SHORT_TEXT, sb, gb)
+ c += SHORT_TEXT.length
+ assertReplace(c, c, MEDIUM_TEXT, sb, gb)
+ c += MEDIUM_TEXT.length
+ assertReplace(c, c, LONG_TEXT, sb, gb)
+ c += LONG_TEXT.length
+ assertReplace(c, c, MEDIUM_TEXT, sb, gb)
+ c += MEDIUM_TEXT.length
+ assertReplace(c, c, SHORT_TEXT, sb, gb)
+ }
+
+ @Test
+ fun longTextTest_keep_deletion() {
+ val sb = StringBuffer(LONG_INIT_TEXT)
+ val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+ var c = 2048 // cursor
+ // Forward deletion
+ assertReplace(c, c + 10, "", sb, gb)
+ assertReplace(c, c + 100, "", sb, gb)
+ assertReplace(c, c + 1000, "", sb, gb)
+
+ // Backspacing
+ assertReplace(c - 10, c, "", sb, gb)
+ c -= 10
+ assertReplace(c - 100, c, "", sb, gb)
+ c -= 100
+ assertReplace(c - 1000, c, "", sb, gb)
+ }
+
+ @Test
+ fun longTextTest_farInput() {
+ val sb = StringBuffer(LONG_INIT_TEXT)
+ val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+ assertReplace(1024, 1024, "Hello, World", sb, gb)
+ assertReplace(128, 128, LONG_TEXT, sb, gb)
+ }
+
+ @Test
+ fun randomInsertDeleteStressTest() {
+ val sb = StringBuffer(LONG_INIT_TEXT)
+ val gb = PartialGapBuffer(LONG_INIT_TEXT)
+
+ val r = Random(10 /* fix the seed for reproduction */)
+
+ val INSERT_TEXTS = arrayOf(SHORT_TEXT, MEDIUM_TEXT, LONG_TEXT)
+ val DEL_LENGTHS = arrayOf(1, 10, 100)
+
+ var c = LONG_INIT_TEXT.length / 2
+
+ for (i in 0..100) {
+ when (r.nextInt() % 4) {
+ 0 -> { // insert
+ val txt = INSERT_TEXTS.random(r)
+ assertReplace(c, c, txt, sb, gb)
+ c += txt.length
+ }
+ 1 -> { // forward delete
+ assertReplace(c, c + DEL_LENGTHS.random(r), "", sb, gb)
+ }
+ 2 -> { // backspacing
+ val len = DEL_LENGTHS.random(r)
+ assertReplace(c - len, c, "", sb, gb)
+ c -= len
+ }
+ 3 -> { // replacing
+ val txt = INSERT_TEXTS.random(r)
+ val len = DEL_LENGTHS.random(r)
+
+ assertReplace(c, c + len, txt, sb, gb)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/gradle/wrapper/gradle-wrapper.properties b/ui/gradle/wrapper/gradle-wrapper.properties
index 12da04b..8e0fbb5 100644
--- a/ui/gradle/wrapper/gradle-wrapper.properties
+++ b/ui/gradle/wrapper/gradle-wrapper.properties
@@ -5,4 +5,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../../../tools/external/gradle/gradle-5.4-bin.zip
+distributionUrl=../../../../../tools/external/gradle/gradle-5.4.1-bin.zip
diff --git a/ui/gradlew b/ui/gradlew
index b0ee710..f1f54b1 100755
--- a/ui/gradlew
+++ b/ui/gradlew
@@ -9,6 +9,10 @@
# Override Kotlin version needed for compose - see buildSrc/build_dependencies.gradle
export KOTLIN_OVERRIDE="1.3.30-compose-20190503"
+# Override AGP version until we have a 3.5* compatible build of Compose Studio
+# TODO: Remove after b/132355581
+export GRADLE_PLUGIN_VERSION="3.4.0"
+
# Path to the directory containing this script
DIR="`dirname \"$0\"`/"
diff --git a/ui/platform/src/androidTest/java/androidx/ui/core/input/RecordingInputConnectionTest.kt b/ui/platform/src/androidTest/java/androidx/ui/core/input/RecordingInputConnectionTest.kt
index 42f9ad9..42d6887 100644
--- a/ui/platform/src/androidTest/java/androidx/ui/core/input/RecordingInputConnectionTest.kt
+++ b/ui/platform/src/androidTest/java/androidx/ui/core/input/RecordingInputConnectionTest.kt
@@ -164,4 +164,276 @@
assertEquals(CommitTextEditOp("Hello, ", 1), editOps[0])
assertEquals(CommitTextEditOp("World.", 1), editOps[1])
}
+
+ @Test
+ fun setComposingRegion() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World.", selection = TextRange(0, 0))
+
+ // Mark first "H" as composition.
+ assertTrue(ic.setComposingRegion(0, 1))
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(1, editOps.size)
+ assertEquals(SetComposingRegionEditOp(0, 1), editOps[0])
+ }
+
+ @Test
+ fun setComposingRegion_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World", selection = TextRange(0, 0))
+
+ // Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.setComposingRegion(0, 1))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.setComposingRegion(1, 2))
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(2, editOps.size)
+ assertEquals(SetComposingRegionEditOp(0, 1), editOps[0])
+ assertEquals(SetComposingRegionEditOp(1, 2), editOps[1])
+ }
+
+ @Test
+ fun setComposingTextTest() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "", selection = TextRange(0, 0))
+
+ // Inserting "Hello, " into the empty text field.
+ assertTrue(ic.setComposingText("Hello, ", 1))
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(1, editOps.size)
+ assertEquals(SetComposingTextEditOp("Hello, ", 1), editOps[0])
+ }
+
+ @Test
+ fun setComposingTextTest_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "", selection = TextRange(0, 0))
+
+ // IME set text "Hello, World." with two setComposingText API within the single batch
+ // session. Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.setComposingText("Hello, ", 1))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.setComposingText("World.", 1))
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(2, editOps.size)
+ assertEquals(SetComposingTextEditOp("Hello, ", 1), editOps[0])
+ assertEquals(SetComposingTextEditOp("World.", 1), editOps[1])
+ }
+
+ @Test
+ fun deleteSurroundingText() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World.", selection = TextRange(0, 0))
+
+ // Delete first "Hello, " characters
+ assertTrue(ic.deleteSurroundingText(0, 6))
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(1, editOps.size)
+ assertEquals(DeleteSurroundingTextEditOp(0, 6), editOps[0])
+ }
+
+ @Test
+ fun deleteSurroundingText_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World", selection = TextRange(0, 0))
+
+ // Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.deleteSurroundingText(0, 6))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.deleteSurroundingText(0, 5))
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(2, editOps.size)
+ assertEquals(DeleteSurroundingTextEditOp(0, 6), editOps[0])
+ assertEquals(DeleteSurroundingTextEditOp(0, 5), editOps[1])
+ }
+
+ @Test
+ fun deleteSurroundingTextInCodePoints() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World.", selection = TextRange(0, 0))
+
+ // Delete first "Hello, " characters
+ assertTrue(ic.deleteSurroundingTextInCodePoints(0, 6))
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(1, editOps.size)
+ assertEquals(DeleteSurroundingTextInCodePointsEditOp(0, 6), editOps[0])
+ }
+
+ @Test
+ fun deleteSurroundingTextInCodePoints_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World", selection = TextRange(0, 0))
+
+ // Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.deleteSurroundingTextInCodePoints(0, 6))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.deleteSurroundingTextInCodePoints(0, 5))
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(2, editOps.size)
+ assertEquals(DeleteSurroundingTextInCodePointsEditOp(0, 6), editOps[0])
+ assertEquals(DeleteSurroundingTextInCodePointsEditOp(0, 5), editOps[1])
+ }
+
+ @Test
+ fun setSelection() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World.", selection = TextRange(0, 0))
+
+ // Select "Hello, "
+ assertTrue(ic.setSelection(0, 6))
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(1, editOps.size)
+ assertEquals(SetSelectionEditOp(0, 6), editOps[0])
+ }
+
+ @Test
+ fun setSelection_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World", selection = TextRange(0, 0))
+
+ // Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.setSelection(0, 6))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.setSelection(6, 11))
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(2, editOps.size)
+ assertEquals(SetSelectionEditOp(0, 6), editOps[0])
+ assertEquals(SetSelectionEditOp(6, 11), editOps[1])
+ }
+
+ @Test
+ fun finishComposingText() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World.", selection = TextRange(0, 0))
+
+ // Cancel any ongoing composition. In this example, there is no composition range, but
+ // should record the API call
+ assertTrue(ic.finishComposingText())
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(1, editOps.size)
+ assertEquals(FinishComposingTextEditOp(), editOps[0])
+ }
+
+ @Test
+ fun finishComposingText_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "Hello, World", selection = TextRange(0, 0))
+
+ // Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.finishComposingText())
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.finishComposingText())
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(2, editOps.size)
+ assertEquals(FinishComposingTextEditOp(), editOps[0])
+ assertEquals(FinishComposingTextEditOp(), editOps[1])
+ }
+
+ @Test
+ fun mixedAPICalls_batchSession() {
+ val captor = argumentCaptor<List<EditOperation>>()
+
+ ic.inputState = InputState(text = "", selection = TextRange(0, 0))
+
+ // Do not callback to listener during batch session.
+ ic.beginBatchEdit()
+
+ assertTrue(ic.setComposingText("Hello, ", 1))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.finishComposingText())
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.commitText("World.", 1))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.setSelection(0, 12))
+ verify(listener, never()).onEditOperations(any())
+
+ assertTrue(ic.commitText("", 1))
+ verify(listener, never()).onEditOperations(any())
+
+ ic.endBatchEdit()
+
+ verify(listener, times(1)).onEditOperations(captor.capture())
+ val editOps = captor.lastValue
+ assertEquals(5, editOps.size)
+ assertEquals(SetComposingTextEditOp("Hello, ", 1), editOps[0])
+ assertEquals(FinishComposingTextEditOp(), editOps[1])
+ assertEquals(CommitTextEditOp("World.", 1), editOps[2])
+ assertEquals(SetSelectionEditOp(0, 12), editOps[3])
+ assertEquals(CommitTextEditOp("", 1), editOps[4])
+ }
}
\ No newline at end of file
diff --git a/ui/platform/src/main/java/androidx/ui/core/input/EditOperation.kt b/ui/platform/src/main/java/androidx/ui/core/input/EditOperation.kt
index bbd1044..feb583c 100644
--- a/ui/platform/src/main/java/androidx/ui/core/input/EditOperation.kt
+++ b/ui/platform/src/main/java/androidx/ui/core/input/EditOperation.kt
@@ -16,12 +16,19 @@
package androidx.ui.core.input
+import java.util.Objects
+
/**
* An enum class for the type of edit operations.
*/
internal enum class OpType {
COMMIT_TEXT,
- // TODO(nona): Introduce other API callback, setComposingRange, etc.
+ SET_COMPOSING_REGION,
+ SET_COMPOSING_TEXT,
+ DELETE_SURROUNDING_TEXT,
+ DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
+ SET_SELECTION,
+ FINISH_COMPOSING_TEXT
}
/**
@@ -33,7 +40,7 @@
internal open class EditOperation(val type: OpType)
/**
- * An edit opration represent commitText callback from InputMethod.
+ * An edit operation represent commitText callback from InputMethod.
* @see https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#commitText(java.lang.CharSequence,%20int)
*/
internal data class CommitTextEditOp(
@@ -48,3 +55,97 @@
*/
val newCursorPostion: Int
) : EditOperation(OpType.COMMIT_TEXT)
+
+/**
+ * An edit operation represents setComposingRegion callback from InputMethod.
+ *
+ */
+internal data class SetComposingRegionEditOp(
+ /**
+ * The inclusive start offset of the composing region.
+ */
+ val start: Int,
+
+ /**
+ * The exclusive end offset of the composing region
+ */
+ val end: Int
+) : EditOperation(OpType.SET_COMPOSING_REGION)
+
+/**
+ * An edit operation represents setComposingText callback from InputMethod
+ *
+ * @see https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#setComposingText(java.lang.CharSequence,%2520int)
+ */
+internal data class SetComposingTextEditOp(
+ /**
+ * The composing text.
+ */
+ val text: String,
+ /**
+ * The cursor position after setting composing text.
+ * See original setComposingText API docs for more details.
+ */
+ val newCursorPosition: Int
+) : EditOperation(OpType.SET_COMPOSING_TEXT)
+
+/**
+ * An edit operation represents deleteSurroundingText callback from InputMethod
+ *
+ * @see https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#deleteSurroundingText(int,%2520int)
+ */
+internal data class DeleteSurroundingTextEditOp(
+ /**
+ * The number of characters in UTF-16 before the cursor to be deleted.
+ */
+ val beforeLength: Int,
+ /**
+ * The number of characters in UTF-16 after the cursor to be deleted.
+ */
+ val afterLength: Int
+) : EditOperation(OpType.DELETE_SURROUNDING_TEXT)
+
+/**
+ * An edit operation represents deleteSurroundingTextInCodePoitns callback from InputMethod
+ *
+ * @see https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#deleteSurroundingTextInCodePoints(int,%2520int)
+ */
+internal data class DeleteSurroundingTextInCodePointsEditOp(
+ /**
+ * The number oc characters in Unicode code points before the cursor to be deleted.
+ */
+ val beforeLength: Int,
+ /**
+ * The number oc characters in Unicode code points after the cursor to be deleted.
+ */
+ val afterLength: Int
+) : EditOperation(OpType.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS)
+
+/**
+ * An edit operation represents setSelection callback from InputMethod
+ *
+ * @see https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#setSelection(int,%2520int)
+ */
+internal data class SetSelectionEditOp(
+ /**
+ * The inclusive start offset of the selection region.
+ */
+ val start: Int,
+ /**
+ * The exclusive end offset of the selection region.
+ */
+ val end: Int
+) : EditOperation(OpType.SET_SELECTION)
+
+/**
+ * An edit operation represents finishComposingText callback from InputMEthod
+ *
+ * @see https://developer.android.com/reference/android/view/inputmethod/InputConnection.html#finishComposingText()
+ */
+internal class FinishComposingTextEditOp : EditOperation(OpType.FINISH_COMPOSING_TEXT) {
+
+ // Class with empty arguments default ctor cannot be data class.
+ // Treating all FinishComposingTextEditOp are equal object.
+ override fun equals(other: Any?): Boolean = other is FinishComposingTextEditOp
+ override fun hashCode(): Int = Objects.hashCode(this.javaClass)
+}
\ No newline at end of file
diff --git a/ui/platform/src/main/java/androidx/ui/core/input/RecordingInputConnection.kt b/ui/platform/src/main/java/androidx/ui/core/input/RecordingInputConnection.kt
index 6921510..027c83b 100644
--- a/ui/platform/src/main/java/androidx/ui/core/input/RecordingInputConnection.kt
+++ b/ui/platform/src/main/java/androidx/ui/core/input/RecordingInputConnection.kt
@@ -52,6 +52,16 @@
// The recoding editing ops.
private val editOps = mutableListOf<EditOperation>()
+ // Add edit op to internal list with wrapping batch edit.
+ private fun addEditOpWithBatch(editOp: EditOperation) {
+ beginBatchEdit()
+ try {
+ editOps.add(editOp)
+ } finally {
+ endBatchEdit()
+ }
+ }
+
// /////////////////////////////////////////////////////////////////////////////////////////////
// Callbacks for text editing session
// /////////////////////////////////////////////////////////////////////////////////////////////
@@ -83,43 +93,44 @@
override fun commitText(text: CharSequence?, newCursorPosition: Int): Boolean {
if (DEBUG) { Log.d(TAG, "commitText($text, $newCursorPosition)") }
- beginBatchEdit()
- try {
- editOps.add(CommitTextEditOp(text.toString(), newCursorPosition))
- } finally {
- endBatchEdit()
- }
+ addEditOpWithBatch(CommitTextEditOp(text.toString(), newCursorPosition))
return true
}
override fun setComposingRegion(start: Int, end: Int): Boolean {
if (DEBUG) { Log.d(TAG, "setComposingRegion($start, $end)") }
- TODO("not implemented")
+ addEditOpWithBatch(SetComposingRegionEditOp(start, end))
+ return true
}
override fun setComposingText(text: CharSequence?, newCursorPosition: Int): Boolean {
if (DEBUG) { Log.d(TAG, "setComposingText($text, $newCursorPosition)") }
- TODO("not implemented")
+ addEditOpWithBatch(SetComposingTextEditOp(text.toString(), newCursorPosition))
+ return true
}
override fun deleteSurroundingTextInCodePoints(beforeLength: Int, afterLength: Int): Boolean {
if (DEBUG) { Log.d(TAG, "deleteSurroundingTextInCodePoints($beforeLength, $afterLength)") }
- TODO("not implemented")
+ addEditOpWithBatch(DeleteSurroundingTextInCodePointsEditOp(beforeLength, afterLength))
+ return true
}
override fun deleteSurroundingText(beforeLength: Int, afterLength: Int): Boolean {
if (DEBUG) { Log.d(TAG, "deleteSurroundingText($beforeLength, $afterLength)") }
- TODO("not implemented")
+ addEditOpWithBatch(DeleteSurroundingTextEditOp(beforeLength, afterLength))
+ return true
}
override fun setSelection(start: Int, end: Int): Boolean {
if (DEBUG) { Log.d(TAG, "setSelection($start, $end)") }
- TODO("not implemented")
+ addEditOpWithBatch(SetSelectionEditOp(start, end))
+ return true
}
override fun finishComposingText(): Boolean {
if (DEBUG) { Log.d(TAG, "finishComposingText()") }
- TODO("not implemented")
+ addEditOpWithBatch(FinishComposingTextEditOp())
+ return true
}
override fun sendKeyEvent(event: KeyEvent?): Boolean {
diff --git a/ui/studio_versions.properties b/ui/studio_versions.properties
index dfa9802..e6cd2b3 100644
--- a/ui/studio_versions.properties
+++ b/ui/studio_versions.properties
@@ -1,8 +1,5 @@
# This file specifies the version of the Android Gradle Plugin and Android Studio to use
-# TODO: autogenerate this file based on the version of AGP
-# the version of the Android Gradle Plugin
-agp=3.4.0
# version properties for studiow, which should correspond to the version of AGP
studio_version=3.4.1.0
idea_major_version=183
diff --git a/ui/studiow b/ui/studiow
index 3524983..c9aec90 100755
--- a/ui/studiow
+++ b/ui/studiow
@@ -174,18 +174,24 @@
function runStudioLinux() {
studioPath="${studioUnzippedPath}/android-studio/bin/studio.sh"
echo "$studioPath &"
+ # Override AGP version until we have a 3.5* compatible build of Compose Studio
+ # TODO: Remove after b/132355581
env STUDIO_PROPERTIES="${projectDir}/idea.properties" \
STUDIO_VM_OPTIONS="${projectDir}/../development/studio/studio.vmoptions" \
KOTLIN_OVERRIDE="1.3.30-compose-20190503" \
+ GRADLE_PLUGIN_VERSION="3.4.0" \
"${studioPath}" "${projectDir}" &
}
function runStudioMac() {
appPath="$(findStudioMacAppPath)"
echo "open ${appPath}"
+ # Override AGP version until we have a 3.5* compatible build of Compose Studio
+ # TODO: Remove after b/132355581
env STUDIO_PROPERTIES="${projectDir}/idea.properties" \
STUDIO_VM_OPTIONS="${projectDir}/../development/studio/studio.vmoptions" \
KOTLIN_OVERRIDE="1.3.30-compose-20190503" \
+ GRADLE_PLUGIN_VERSION="3.4.0" \
open -a "${appPath}" "${projectDir}"
}
diff --git a/ui/text/api/1.0.0-alpha01.txt b/ui/text/api/1.0.0-alpha01.txt
index 0415261..c48cc92 100644
--- a/ui/text/api/1.0.0-alpha01.txt
+++ b/ui/text/api/1.0.0-alpha01.txt
@@ -230,27 +230,28 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, Float? fontSizeScale, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
ctor public TextStyle();
method public androidx.ui.graphics.Color? component1();
- method public androidx.ui.engine.text.BaselineShift? component10();
- method public androidx.ui.engine.text.TextGeometricTransform? component11();
- method public Float? component12();
- method public androidx.ui.engine.window.Locale? component13();
- method public androidx.ui.graphics.Color? component14();
- method public androidx.ui.engine.text.FontSynthesis? component15();
- method public androidx.ui.engine.text.TextIndent? component16();
- method public androidx.ui.engine.text.TextAlign? component17();
- method public androidx.ui.painting.Shadow? component18();
+ method public Float? component10();
+ method public androidx.ui.engine.text.BaselineShift? component11();
+ method public androidx.ui.engine.text.TextGeometricTransform? component12();
+ method public Float? component13();
+ method public androidx.ui.engine.window.Locale? component14();
+ method public androidx.ui.graphics.Color? component15();
+ method public androidx.ui.engine.text.FontSynthesis? component16();
+ method public androidx.ui.engine.text.TextIndent? component17();
+ method public androidx.ui.engine.text.TextAlign? component18();
+ method public androidx.ui.painting.Shadow? component19();
method public androidx.ui.engine.text.TextDecoration? component2();
method public androidx.ui.engine.text.FontWeight? component3();
method public androidx.ui.engine.text.FontStyle? component4();
method public androidx.ui.engine.text.font.FontFamily? component5();
method public Float? component6();
- method public String? component7();
- method public Float? component8();
+ method public Float? component7();
+ method public String? component8();
method public Float? component9();
- method public androidx.ui.engine.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
+ method public androidx.ui.engine.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, Float? fontSizeScale, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.engine.text.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -258,6 +259,7 @@
method public androidx.ui.engine.text.font.FontFamily? getFontFamily();
method public String? getFontFeatureSettings();
method public Float? getFontSize();
+ method public Float? getFontSizeScale();
method public androidx.ui.engine.text.FontStyle? getFontStyle();
method public androidx.ui.engine.text.FontSynthesis? getFontSynthesis();
method public androidx.ui.engine.text.FontWeight? getFontWeight();
@@ -484,29 +486,30 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, Float? fontSize, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, Float? fontSize, Float? fontSizeScale, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
ctor public TextStyle();
method public androidx.ui.painting.basictypes.RenderComparison compareTo(androidx.ui.painting.TextStyle other);
method public androidx.ui.graphics.Color? component1();
- method public androidx.ui.engine.text.TextGeometricTransform? component10();
- method public Float? component11();
- method public androidx.ui.engine.window.Locale? component12();
- method public androidx.ui.graphics.Color? component13();
- method public androidx.ui.engine.text.TextDecoration? component14();
- method public androidx.ui.engine.text.font.FontFamily? component15();
- method public androidx.ui.engine.text.TextIndent? component16();
- method public androidx.ui.engine.text.TextAlign? component17();
- method public androidx.ui.painting.Shadow? component18();
- method public String? component19();
+ method public androidx.ui.engine.text.BaselineShift? component10();
+ method public androidx.ui.engine.text.TextGeometricTransform? component11();
+ method public Float? component12();
+ method public androidx.ui.engine.window.Locale? component13();
+ method public androidx.ui.graphics.Color? component14();
+ method public androidx.ui.engine.text.TextDecoration? component15();
+ method public androidx.ui.engine.text.font.FontFamily? component16();
+ method public androidx.ui.engine.text.TextIndent? component17();
+ method public androidx.ui.engine.text.TextAlign? component18();
+ method public androidx.ui.painting.Shadow? component19();
method public Float? component2();
- method public androidx.ui.engine.text.FontWeight? component3();
- method public androidx.ui.engine.text.FontStyle? component4();
- method public androidx.ui.engine.text.FontSynthesis? component5();
- method public String? component6();
- method public Float? component7();
+ method public String? component20();
+ method public Float? component3();
+ method public androidx.ui.engine.text.FontWeight? component4();
+ method public androidx.ui.engine.text.FontStyle? component5();
+ method public androidx.ui.engine.text.FontSynthesis? component6();
+ method public String? component7();
method public Float? component8();
- method public androidx.ui.engine.text.BaselineShift? component9();
- method public androidx.ui.painting.TextStyle copy(androidx.ui.graphics.Color? color, Float? fontSize, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
+ method public Float? component9();
+ method public androidx.ui.painting.TextStyle copy(androidx.ui.graphics.Color? color, Float? fontSize, Float? fontSizeScale, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.engine.text.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -515,6 +518,7 @@
method public androidx.ui.engine.text.font.FontFamily? getFontFamily();
method public String? getFontFeatureSettings();
method public Float? getFontSize();
+ method public Float? getFontSizeScale();
method public androidx.ui.engine.text.FontStyle? getFontStyle();
method public androidx.ui.engine.text.FontSynthesis? getFontSynthesis();
method public androidx.ui.engine.text.FontWeight? getFontWeight();
diff --git a/ui/text/api/current.txt b/ui/text/api/current.txt
index 0415261..c48cc92 100644
--- a/ui/text/api/current.txt
+++ b/ui/text/api/current.txt
@@ -230,27 +230,28 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, Float? fontSizeScale, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
ctor public TextStyle();
method public androidx.ui.graphics.Color? component1();
- method public androidx.ui.engine.text.BaselineShift? component10();
- method public androidx.ui.engine.text.TextGeometricTransform? component11();
- method public Float? component12();
- method public androidx.ui.engine.window.Locale? component13();
- method public androidx.ui.graphics.Color? component14();
- method public androidx.ui.engine.text.FontSynthesis? component15();
- method public androidx.ui.engine.text.TextIndent? component16();
- method public androidx.ui.engine.text.TextAlign? component17();
- method public androidx.ui.painting.Shadow? component18();
+ method public Float? component10();
+ method public androidx.ui.engine.text.BaselineShift? component11();
+ method public androidx.ui.engine.text.TextGeometricTransform? component12();
+ method public Float? component13();
+ method public androidx.ui.engine.window.Locale? component14();
+ method public androidx.ui.graphics.Color? component15();
+ method public androidx.ui.engine.text.FontSynthesis? component16();
+ method public androidx.ui.engine.text.TextIndent? component17();
+ method public androidx.ui.engine.text.TextAlign? component18();
+ method public androidx.ui.painting.Shadow? component19();
method public androidx.ui.engine.text.TextDecoration? component2();
method public androidx.ui.engine.text.FontWeight? component3();
method public androidx.ui.engine.text.FontStyle? component4();
method public androidx.ui.engine.text.font.FontFamily? component5();
method public Float? component6();
- method public String? component7();
- method public Float? component8();
+ method public Float? component7();
+ method public String? component8();
method public Float? component9();
- method public androidx.ui.engine.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
+ method public androidx.ui.engine.text.TextStyle copy(androidx.ui.graphics.Color? color, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.font.FontFamily? fontFamily, Float? fontSize, Float? fontSizeScale, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.FontSynthesis? fontSynthesis, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.engine.text.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -258,6 +259,7 @@
method public androidx.ui.engine.text.font.FontFamily? getFontFamily();
method public String? getFontFeatureSettings();
method public Float? getFontSize();
+ method public Float? getFontSizeScale();
method public androidx.ui.engine.text.FontStyle? getFontStyle();
method public androidx.ui.engine.text.FontSynthesis? getFontSynthesis();
method public androidx.ui.engine.text.FontWeight? getFontWeight();
@@ -484,29 +486,30 @@
}
public final class TextStyle {
- ctor public TextStyle(androidx.ui.graphics.Color? color, Float? fontSize, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
+ ctor public TextStyle(androidx.ui.graphics.Color? color, Float? fontSize, Float? fontSizeScale, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
ctor public TextStyle();
method public androidx.ui.painting.basictypes.RenderComparison compareTo(androidx.ui.painting.TextStyle other);
method public androidx.ui.graphics.Color? component1();
- method public androidx.ui.engine.text.TextGeometricTransform? component10();
- method public Float? component11();
- method public androidx.ui.engine.window.Locale? component12();
- method public androidx.ui.graphics.Color? component13();
- method public androidx.ui.engine.text.TextDecoration? component14();
- method public androidx.ui.engine.text.font.FontFamily? component15();
- method public androidx.ui.engine.text.TextIndent? component16();
- method public androidx.ui.engine.text.TextAlign? component17();
- method public androidx.ui.painting.Shadow? component18();
- method public String? component19();
+ method public androidx.ui.engine.text.BaselineShift? component10();
+ method public androidx.ui.engine.text.TextGeometricTransform? component11();
+ method public Float? component12();
+ method public androidx.ui.engine.window.Locale? component13();
+ method public androidx.ui.graphics.Color? component14();
+ method public androidx.ui.engine.text.TextDecoration? component15();
+ method public androidx.ui.engine.text.font.FontFamily? component16();
+ method public androidx.ui.engine.text.TextIndent? component17();
+ method public androidx.ui.engine.text.TextAlign? component18();
+ method public androidx.ui.painting.Shadow? component19();
method public Float? component2();
- method public androidx.ui.engine.text.FontWeight? component3();
- method public androidx.ui.engine.text.FontStyle? component4();
- method public androidx.ui.engine.text.FontSynthesis? component5();
- method public String? component6();
- method public Float? component7();
+ method public String? component20();
+ method public Float? component3();
+ method public androidx.ui.engine.text.FontWeight? component4();
+ method public androidx.ui.engine.text.FontStyle? component5();
+ method public androidx.ui.engine.text.FontSynthesis? component6();
+ method public String? component7();
method public Float? component8();
- method public androidx.ui.engine.text.BaselineShift? component9();
- method public androidx.ui.painting.TextStyle copy(androidx.ui.graphics.Color? color, Float? fontSize, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
+ method public Float? component9();
+ method public androidx.ui.painting.TextStyle copy(androidx.ui.graphics.Color? color, Float? fontSize, Float? fontSizeScale, androidx.ui.engine.text.FontWeight? fontWeight, androidx.ui.engine.text.FontStyle? fontStyle, androidx.ui.engine.text.FontSynthesis? fontSynthesis, String? fontFeatureSettings, Float? letterSpacing, Float? wordSpacing, androidx.ui.engine.text.BaselineShift? baselineShift, androidx.ui.engine.text.TextGeometricTransform? textGeometricTransform, Float? height, androidx.ui.engine.window.Locale? locale, androidx.ui.graphics.Color? background, androidx.ui.engine.text.TextDecoration? decoration, androidx.ui.engine.text.font.FontFamily? fontFamily, androidx.ui.engine.text.TextIndent? textIndent, androidx.ui.engine.text.TextAlign? textAlign, androidx.ui.painting.Shadow? shadow, String? debugLabel);
method public androidx.ui.graphics.Color? getBackground();
method public androidx.ui.engine.text.BaselineShift? getBaselineShift();
method public androidx.ui.graphics.Color? getColor();
@@ -515,6 +518,7 @@
method public androidx.ui.engine.text.font.FontFamily? getFontFamily();
method public String? getFontFeatureSettings();
method public Float? getFontSize();
+ method public Float? getFontSizeScale();
method public androidx.ui.engine.text.FontStyle? getFontStyle();
method public androidx.ui.engine.text.FontSynthesis? getFontSynthesis();
method public androidx.ui.engine.text.FontWeight? getFontWeight();
diff --git a/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt b/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
index 3c786fa..9b91ef5 100644
--- a/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
+++ b/ui/text/integration-tests/text-demos/src/main/java/androidx/ui/text/demos/CraneText.kt
@@ -99,6 +99,8 @@
TextDemoSelection()
TagLine(tag = "composable textspan")
TextDemoComposableTextSpan()
+ TagLine(tag = "fontSizeScale")
+ TextDemoFontSizeScale()
}
}
}
@@ -593,3 +595,17 @@
}
}
}
+
+@Composable
+fun TextDemoFontSizeScale() {
+ CraneWrapper {
+ Text {
+ Span(style = TextStyle(fontSize = fontSize8)) {
+ for (i in 4..12 step 4) {
+ val scale = i * 0.1f
+ Span("fontSizeScale=$scale\n", style = TextStyle(fontSizeScale = scale))
+ }
+ }
+ }
+ }
+}
diff --git a/ui/text/src/androidTest/java/androidx/ui/engine/text/ParagraphIntegrationTest.kt b/ui/text/src/androidTest/java/androidx/ui/engine/text/ParagraphIntegrationTest.kt
index 9b7fa98..d8a13ca 100644
--- a/ui/text/src/androidTest/java/androidx/ui/engine/text/ParagraphIntegrationTest.kt
+++ b/ui/text/src/androidTest/java/androidx/ui/engine/text/ParagraphIntegrationTest.kt
@@ -1100,6 +1100,142 @@
}
@Test
+ fun textStyle_fontSizeScale() {
+ val text = "abcde"
+ val fontSize = 20f
+ val fontSizeScale = 0.5f
+ val textStyle = TextStyle(fontSizeScale = fontSizeScale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length)),
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ val paragraphImpl = paragraph.paragraphImpl
+
+ assertThat(
+ paragraphImpl.getLineRight(0),
+ equalTo(text.length * fontSize * fontSizeScale)
+ )
+ }
+
+ @Test
+ fun textStyle_fontSizeScaleNested() {
+ val text = "abcde"
+ val fontSize = 20f
+ val fontSizeScale = 0.5f
+ val textStyle = TextStyle(fontSizeScale = fontSizeScale)
+
+ val fontSizeScaleNested = 2f
+ val textStyleNested = TextStyle(fontSizeScale = fontSizeScaleNested)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(textStyleNested, 0, text.length)
+ ),
+ fontSize = fontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ val paragraphImpl = paragraph.paragraphImpl
+
+ assertThat(
+ paragraphImpl.getLineRight(0),
+ equalTo(text.length * fontSize * fontSizeScale * fontSizeScaleNested)
+ )
+ }
+
+ @Test
+ fun textStyle_fontSizeScaleWithFontSizeFirst() {
+ val text = "abcde"
+ val paragraphFontSize = 20f
+
+ val fontSize = 30f
+ val fontSizeStyle = TextStyle(fontSize = fontSize)
+
+ val fontSizeScale = 0.5f
+ val fontSizeScaleStyle = TextStyle(fontSizeScale = fontSizeScale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(fontSizeStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle, 0, text.length)
+ ),
+ fontSize = paragraphFontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ val paragraphImpl = paragraph.paragraphImpl
+
+ assertThat(
+ paragraphImpl.getLineRight(0),
+ equalTo(text.length * fontSize * fontSizeScale)
+ )
+ }
+
+ @Test
+ fun textStyle_fontSizeScaleWithFontSizeSecond() {
+ val text = "abcde"
+ val paragraphFontSize = 20f
+
+ val fontSize = 30f
+ val fontSizeStyle = TextStyle(fontSize = fontSize)
+
+ val fontSizeScale = 0.5f
+ val fontSizeScaleStyle = TextStyle(fontSizeScale = fontSizeScale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(fontSizeStyle, 0, text.length)
+ ),
+ fontSize = paragraphFontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ val paragraphImpl = paragraph.paragraphImpl
+
+ assertThat(
+ paragraphImpl.getLineRight(0),
+ equalTo(text.length * fontSize)
+ )
+ }
+
+ @Test
+ fun textStyle_fontSizeScaleWithFontSizeNested() {
+ val text = "abcde"
+ val paragraphFontSize = 20f
+
+ val fontSize = 30f
+ val fontSizeStyle = TextStyle(fontSize = fontSize)
+
+ val fontSizeScale1 = 0.5f
+ val fontSizeScaleStyle1 = TextStyle(fontSizeScale = fontSizeScale1)
+
+ val fontSizeScale2 = 2f
+ val fontSizeScaleStyle2 = TextStyle(fontSizeScale = fontSizeScale2)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(
+ ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle1, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(fontSizeStyle, 0, text.length),
+ ParagraphBuilder.TextStyleIndex(fontSizeScaleStyle2, 0, text.length)
+ ),
+ fontSize = paragraphFontSize
+ )
+ paragraph.layout(ParagraphConstraints(width = Float.MAX_VALUE))
+ val paragraphImpl = paragraph.paragraphImpl
+
+ assertThat(
+ paragraphImpl.getLineRight(0),
+ equalTo(text.length * fontSize * fontSizeScale2)
+ )
+ }
+
+ @Test
fun textStyle_setLetterSpacingOnWholeText() {
val text = "abcde"
val fontSize = 20.0f
diff --git a/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt b/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt
index dfd090d..3e0fbc2 100644
--- a/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt
+++ b/ui/text/src/androidTest/java/androidx/ui/engine/text/platform/ParagraphAndroidTest.kt
@@ -7,6 +7,7 @@
import android.text.style.ForegroundColorSpan
import android.text.style.LeadingMarginSpan
import android.text.style.LocaleSpan
+import android.text.style.RelativeSizeSpan
import android.text.style.ScaleXSpan
import android.text.style.StrikethroughSpan
import android.text.style.UnderlineSpan
@@ -296,6 +297,46 @@
}
@Test
+ fun textStyle_setFontSizeScaleOnWholeText() {
+ val text = "abcde"
+ val fontSizeScale = 2.0f
+ val textStyle = TextStyle(fontSizeScale = fontSizeScale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, text.length))
+ )
+ paragraph.layout(100f)
+
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(RelativeSizeSpan::class, 0, text.length) {
+ it.sizeChange == fontSizeScale
+ }
+ )
+ }
+
+ @Test
+ fun textStyle_setFontSizeScaleOnPartText() {
+ val text = "abcde"
+ val fontSizeScale = 2.0f
+ val textStyle = TextStyle(fontSizeScale = fontSizeScale)
+
+ val paragraph = simpleParagraph(
+ text = text,
+ textStyles = listOf(ParagraphBuilder.TextStyleIndex(textStyle, 0, "abc".length))
+ )
+ paragraph.layout(100f)
+
+ assertThat(
+ paragraph.underlyingText,
+ hasSpan(RelativeSizeSpan::class, 0, "abc".length) {
+ it.sizeChange == fontSizeScale
+ }
+ )
+ }
+
+ @Test
fun textStyle_setLetterSpacingOnWholeText() {
val text = "abcde"
val letterSpacing = 2.0f
diff --git a/ui/text/src/main/java/androidx/ui/engine/text/TextStyle.kt b/ui/text/src/main/java/androidx/ui/engine/text/TextStyle.kt
index 9d836e2..f313959 100644
--- a/ui/text/src/main/java/androidx/ui/engine/text/TextStyle.kt
+++ b/ui/text/src/main/java/androidx/ui/engine/text/TextStyle.kt
@@ -26,28 +26,31 @@
* Creates a new TextStyle object.
*
* @param color The color to use when painting the text. If this is specified, `foreground` must be
- * null.
+ * null.
* @param decoration The decorations to paint near the text (e.g., an underline).
* @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
* @param fontStyle The typeface variant to use when drawing the letters (e.g., italics).
* @param fontFamily The name of the font to use when painting the text (e.g., Roboto).
* @param fontSize The size of glyphs (in logical pixels) to use when painting the text.
+ * @param fontSizeScale The scale factor of the font size. When [fontSize] is also given in this
+ * TextStyle, the final fontSize will be the [fontSize] times this value.
+ * Otherwise, the final fontSize will be the current fontSize times this value.
* @param fontFeatureSettings The advanced typography settings provided by font. The format is the same as the CSS font-feature-settings attribute:
- * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
* @param letterSpacing The amount of space (in EM) to add between each letter.
* @param wordSpacing The amount of space (in logical pixels) to add at each sequence of white-space
- * (i.e. between each word). Only works on Android Q and above.
+ * (i.e. between each word). Only works on Android Q and above.
* @param textBaseline The common baseline that should be aligned between this text span and its
- * parent text span, or, for the root text spans, with the line box.
+ * parent text span, or, for the root text spans, with the line box.
* @param baselineShift This parameter specifies how much the baseline is shifted from the current position.
* @param height The height of this text span, as a multiple of the font size.
* @param textGeometricTransform The geometric transformation applied the text.
* @param locale The locale used to select region-specific glyphs.
* @param background The background color for the text.
* @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight or
- * style cannot be found in the provided custom font family.
+ * style cannot be found in the provided custom font family.
* @param textIndent The amount of indentation applied to the affected paragraph. A paragraph is affected
- * if any of its character is covered by the TextSpan.
+ * if any of its character is covered by the TextSpan.
* @param textAlign Specify how a paragraph is aligned.
* @param shadow The shadow effect applied on the text.
*/
@@ -58,6 +61,7 @@
val fontStyle: FontStyle? = null,
val fontFamily: FontFamily? = null,
val fontSize: Float? = null,
+ val fontSizeScale: Float? = null,
val fontFeatureSettings: String? = null,
val letterSpacing: Float? = null,
val wordSpacing: Float? = null,
diff --git a/ui/text/src/main/java/androidx/ui/engine/text/platform/ParagraphAndroid.kt b/ui/text/src/main/java/androidx/ui/engine/text/platform/ParagraphAndroid.kt
index e7a964e..ebe7230 100644
--- a/ui/text/src/main/java/androidx/ui/engine/text/platform/ParagraphAndroid.kt
+++ b/ui/text/src/main/java/androidx/ui/engine/text/platform/ParagraphAndroid.kt
@@ -26,6 +26,7 @@
import android.text.style.ForegroundColorSpan
import android.text.style.LeadingMarginSpan
import android.text.style.LocaleSpan
+import android.text.style.RelativeSizeSpan
import android.text.style.ScaleXSpan
import android.text.style.StrikethroughSpan
import android.text.style.UnderlineSpan
@@ -424,6 +425,16 @@
)
}
+ // Be aware that fontSizeScale must be applied after fontSize.
+ style.fontSizeScale?.let {
+ spannableString.setSpan(
+ RelativeSizeSpan(it),
+ start,
+ end,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ )
+ }
+
style.fontFeatureSettings?.let {
spannableString.setSpan(
FontFeatureSpan(it),
diff --git a/ui/text/src/main/java/androidx/ui/painting/TextStyle.kt b/ui/text/src/main/java/androidx/ui/painting/TextStyle.kt
index 944434a..4d4250a 100644
--- a/ui/text/src/main/java/androidx/ui/painting/TextStyle.kt
+++ b/ui/text/src/main/java/androidx/ui/painting/TextStyle.kt
@@ -45,12 +45,15 @@
*
* @param color The color to use when painting the text. If this is specified, `foreground` must be null.
* @param fontSize The size of glyphs (in logical pixels) to use when painting the text.
+ * @param fontSizeScale The scale factor of the font size. When [fontSize] is also given in this
+ * TextStyle, the final fontSize will be the [fontSize] times this value.
+ * Otherwise, the final fontSize will be the current fontSize times this value.
* @param fontWeight The typeface thickness to use when painting the text (e.g., bold).
* @param fontStyle The typeface variant to use when drawing the letters (e.g., italics).
* @param fontSynthesis Whether to synthesize font weight and/or style when the requested weight or
- * style cannot be found in the provided custom font family.
+ * style cannot be found in the provided custom font family.
* @param fontFeatureSettings The advanced typography settings provided by font. The format is the same as the CSS font-feature-settings attribute:
- * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
+ * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
* @param letterSpacing The amount of space (in logical pixels) to add between each letter.
* @param wordSpacing The amount of space (in logical pixels) to add at each sequence of white-space (i.e. between each word). Only works on Android Q and above.
* @param baselineShift This parameter specifies how much the baseline is shifted from the current position.
@@ -68,6 +71,7 @@
data class TextStyle(
val color: Color? = null,
val fontSize: Float? = null,
+ val fontSizeScale: Float? = null,
val fontWeight: FontWeight? = null,
val fontStyle: FontStyle? = null,
val fontSynthesis: FontSynthesis? = null,
@@ -114,6 +118,7 @@
color = other.color ?: this.color,
fontFamily = other.fontFamily ?: this.fontFamily,
fontSize = other.fontSize ?: this.fontSize,
+ fontSizeScale = other.fontSizeScale ?: this.fontSizeScale,
fontWeight = other.fontWeight ?: this.fontWeight,
fontStyle = other.fontStyle ?: this.fontStyle,
fontSynthesis = other.fontSynthesis ?: this.fontSynthesis,
@@ -215,6 +220,7 @@
color = lerpColor(a.color, b.color, t),
fontFamily = lerpDiscrete(a.fontFamily, b.fontFamily, t),
fontSize = lerpFloat(a.fontSize, b.fontSize, t),
+ fontSizeScale = lerpFloat(a.fontSizeScale, b.fontSizeScale, t, 1f),
fontWeight = FontWeight.lerp(a.fontWeight, b.fontWeight, t),
fontStyle = lerpDiscrete(a.fontStyle, b.fontStyle, t),
fontSynthesis = lerpDiscrete(a.fontSynthesis, b.fontSynthesis, t),
@@ -258,6 +264,7 @@
fontFeatureSettings = fontFeatureSettings,
fontFamily = fontFamily,
fontSize = if (fontSize == null) null else (fontSize * textScaleFactor),
+ fontSizeScale = fontSizeScale,
letterSpacing = letterSpacing,
wordSpacing = wordSpacing,
baselineShift = baselineShift,
diff --git a/ui/text/src/test/java/androidx/ui/painting/TextStyleTest.kt b/ui/text/src/test/java/androidx/ui/painting/TextStyleTest.kt
index 7d8120f..5c721ff 100644
--- a/ui/text/src/test/java/androidx/ui/painting/TextStyleTest.kt
+++ b/ui/text/src/test/java/androidx/ui/painting/TextStyleTest.kt
@@ -773,6 +773,59 @@
}
@Test
+ fun `lerp fontSizeScale with a and b are not Null`() {
+ val fontSizeScale1 = 2.0f
+ val fontSizeScale2 = 4.0f
+ val t = 0.8f
+ val textStyle1 = TextStyle(fontSizeScale = fontSizeScale1)
+ val textStyle2 = TextStyle(fontSizeScale = fontSizeScale2)
+
+ val newTextStyle = TextStyle.lerp(a = textStyle1, b = textStyle2, t = t)
+
+ // a + (b - a) * t = 2.0f + (4.0f - 2.0f) * 0.8f = 3.6f
+ assertThat(newTextStyle?.fontSizeScale).isEqualTo(3.6f)
+ }
+
+ @Test
+ fun `lerp fontSizeScale with a not Null and b is Null`() {
+ val fontSizeScale = 2.0f
+ val t = 0.8f
+ val textStyle1 = TextStyle(fontSizeScale = fontSizeScale)
+ val textStyle2 = TextStyle()
+
+ val newTextStyle = TextStyle.lerp(a = textStyle1, b = textStyle2, t = t)
+
+ // b is Null and is considered 1.0f
+ // a + (b - a) * t = 2.0f + (1.0f - 2.0f) * 0.8f = 1.2f
+ assertThat(newTextStyle?.fontSizeScale).isEqualTo(1.2f)
+ }
+
+ @Test
+ fun `lerp fontSizeScale with a is Null and b not Null`() {
+ val fontSizeScale = 2.0f
+ val t = 0.8f
+ val textStyle1 = TextStyle()
+ val textStyle2 = TextStyle(fontSizeScale = fontSizeScale)
+
+ val newTextStyle = TextStyle.lerp(a = textStyle1, b = textStyle2, t = t)
+
+ // a is Null and is considered 1.0f
+ // a + (b - a) * t = 1.0f + (2.0f - 1.0f) * 0.8f = 1.8f
+ assertThat(newTextStyle?.fontSizeScale).isEqualTo(1.8f)
+ }
+
+ @Test
+ fun `lerp fontSizeScale with a and b are Null`() {
+ val t = 0.8f
+ val textStyle1 = TextStyle()
+ val textStyle2 = TextStyle()
+
+ val newTextStyle = TextStyle.lerp(a = textStyle1, b = textStyle2, t = t)
+
+ assertThat(newTextStyle?.fontSizeScale).isNull()
+ }
+
+ @Test
fun `lerp fontWeight with a is Null and t is smaller than half`() {
val fontWeight = FontWeight.w700
val t = 0.3f