Merge "Infer args types" into flatfoot-navigation
diff --git a/app-toolkit/settings.gradle b/app-toolkit/settings.gradle
index 788da10..b6ec382 100644
--- a/app-toolkit/settings.gradle
+++ b/app-toolkit/settings.gradle
@@ -39,6 +39,9 @@
include ":paging:runtime"
project(':paging:runtime').projectDir = new File(supportRoot, "paging/runtime")
+include ":paging:rxjava2"
+project(':paging:rxjava2').projectDir = new File(supportRoot, "paging/rxjava2")
+
include ':paging:integration-tests:testapp'
project(':paging:integration-tests:testapp').projectDir = new File(supportRoot, "paging/integration-tests/testapp")
@@ -115,18 +118,33 @@
include ':navigation:common'
project(':navigation:common').projectDir = new File(supportRoot, "navigation/common")
+include ':navigation:common-ktx'
+project(':navigation:common-ktx').projectDir = new File(supportRoot, "navigation/common/ktx")
+
include ':navigation:runtime'
project(':navigation:runtime').projectDir = new File(supportRoot, "navigation/runtime")
+include ':navigation:runtime-ktx'
+project(':navigation:runtime-ktx').projectDir = new File(supportRoot, "navigation/runtime/ktx")
+
include ':navigation:testing'
project(':navigation:testing').projectDir = new File(supportRoot, "navigation/testing")
+include ':navigation:testing-ktx'
+project(':navigation:testing-ktx').projectDir = new File(supportRoot, "navigation/testing/ktx")
+
include ':navigation:fragment'
project(':navigation:fragment').projectDir = new File(supportRoot, "navigation/fragment")
+include ':navigation:fragment-ktx'
+project(':navigation:fragment-ktx').projectDir = new File(supportRoot, "navigation/fragment/ktx")
+
include ':navigation:ui'
project(':navigation:ui').projectDir = new File(supportRoot, "navigation/ui")
+include ':navigation:ui-ktx'
+project(':navigation:ui-ktx').projectDir = new File(supportRoot, "navigation/ui/ktx")
+
include ':navigation:integration-tests:testapp'
project(':navigation:integration-tests:testapp').projectDir = new File(supportRoot, "navigation/integration-tests/testapp")
diff --git a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
index 72564cc..b43369a 100644
--- a/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/android/support/DiffAndDocs.kt
@@ -405,6 +405,7 @@
if (!offline) {
addStringOption("toroot", "/")
addBooleanOption("devsite", true)
+ addBooleanOption("yamlV2", true)
addStringOption("dac_libraryroot", dacOptions.libraryroot)
addStringOption("dac_dataname", dacOptions.dataname)
}
diff --git a/buildSrc/src/main/kotlin/android/support/LibraryVersions.kt b/buildSrc/src/main/kotlin/android/support/LibraryVersions.kt
index abf1abf..d5b7fb0 100644
--- a/buildSrc/src/main/kotlin/android/support/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/android/support/LibraryVersions.kt
@@ -28,7 +28,7 @@
/**
* Version code for Room
*/
- val ROOM = Version("1.1.0-beta1")
+ val ROOM = Version("1.1.0-beta2")
/**
* Version code for Lifecycle extensions (ProcessLifecycleOwner, Fragment support)
@@ -48,7 +48,9 @@
/**
* Version code for Paging
*/
- val PAGING = Version("1.0.0-alpha7")
+ val PAGING = Version("1.0.0-beta1")
+
+ val PAGING_RX = Version("1.0.0-alpha1")
private val LIFECYCLES = Version("1.1.1")
diff --git a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
index 347c1e3..0f93744 100644
--- a/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/android/support/dependencies/Dependencies.kt
@@ -58,7 +58,7 @@
const val SUPPORT_RECYCLERVIEW_27 = "com.android.support:recyclerview-v7:$SUPPORT_VERSION_27"
const val SUPPORT_APPCOMPAT_27 = "com.android.support:appcompat-v7:$SUPPORT_VERSION_27"
-private const val SUPPORT_VERSION = "27.0.0"
+private const val SUPPORT_VERSION = "27.1.0"
const val SUPPORT_ANNOTATIONS = "com.android.support:support-annotations:$SUPPORT_VERSION"
const val SUPPORT_APPCOMPAT = "com.android.support:appcompat-v7:$SUPPORT_VERSION"
const val SUPPORT_CARDVIEW = "com.android.support:cardview-v7:$SUPPORT_VERSION"
diff --git a/navigation/common/ktx/build.gradle b/navigation/common/ktx/build.gradle
new file mode 100644
index 0000000..2c4556d
--- /dev/null
+++ b/navigation/common/ktx/build.gradle
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":navigation:common"))
+ api(KOTLIN_STDLIB)
+ testImplementation(project(":navigation:testing"))
+ testImplementation(JUNIT)
+ testImplementation(TEST_RUNNER)
+ androidTestImplementation(project(":navigation:testing"))
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+}
+
+supportLibrary {
+ name = "Android Navigation Common Kotlin Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.NAVIGATION
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2018"
+ description = "Android Navigation-Common-Ktx"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
diff --git a/navigation/common/ktx/src/androidTest/AndroidManifest.xml b/navigation/common/ktx/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..5b5eccf
--- /dev/null
+++ b/navigation/common/ktx/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.navigation.ktx.test">
+ <application>
+ </application>
+</manifest>
diff --git a/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
new file mode 100644
index 0000000..dd52934
--- /dev/null
+++ b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavDestinationBuilderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.navigation
+
+import android.os.Bundle
+import android.support.annotation.IdRes
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import androidx.navigation.testing.TestNavigator
+import androidx.navigation.testing.TestNavigatorProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavDestinationTest {
+ private val provider = TestNavigatorProvider(InstrumentationRegistry.getTargetContext())
+
+ @Test
+ fun navDestination() {
+ val destination = provider.navDestination(DESTINATION_ID) { }
+ assertEquals("NavDestination should have id set",
+ DESTINATION_ID, destination.id)
+ }
+
+ @Test
+ fun navDestinationLabel() {
+ val destination = provider.navDestination(DESTINATION_ID) {
+ label = LABEL
+ }
+ assertEquals("NavDestination should have label set",
+ LABEL, destination.label)
+ }
+
+ @Test
+ fun navDestinationDefaultArguments() {
+ val arguments = Bundle()
+ val destination = provider.navDestination(DESTINATION_ID) {
+ defaultArguments = arguments
+ }
+ assertEquals("NavDestination should have default arguments set",
+ arguments, destination.defaultArguments)
+ }
+
+ @Test
+ fun navDestinationAction() {
+ val destination = provider.navDestination(DESTINATION_ID) {
+ action(ACTION_ID) {
+ destinationId = DESTINATION_ID
+ navOptions {
+ popUpTo = DESTINATION_ID
+ }
+ }
+ }
+ val action = destination.getAction(ACTION_ID)
+ assertNotNull("NavDestination should have action that was added", action)
+ assertEquals("NavAction should have NavOptions set",
+ DESTINATION_ID, action?.navOptions?.popUpTo)
+ }
+}
+
+private const val DESTINATION_ID = 1
+private const val LABEL = "TEST"
+private const val ACTION_ID = 1
+
+/**
+ * Instead of constructing a NavGraph from the NavigatorProvider, construct
+ * a NavDestination directly to allow for testing NavDestinationBuilder in
+ * isolation.
+ */
+fun NavigatorProvider.navDestination(
+ @IdRes id: Int,
+ block: NavDestinationBuilder<NavDestination>.() -> Unit
+): NavDestination = NavDestinationBuilder(this[TestNavigator::class], id).apply(block).build()
diff --git a/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
new file mode 100644
index 0000000..9ecffe9
--- /dev/null
+++ b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavGraphBuilderTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.navigation
+
+import android.support.annotation.IdRes
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import androidx.navigation.testing.TestNavigator
+import androidx.navigation.testing.TestNavigatorProvider
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavGraphBuilderTest {
+ private val provider = TestNavigatorProvider(InstrumentationRegistry.getTargetContext())
+
+ @Test
+ fun navigation() {
+ val graph = provider.navigation(startDestination = DESTINATION_ID) {
+ navDestination(DESTINATION_ID) {}
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+
+ @Test
+ fun navigationUnaryPlus() {
+ val graph = provider.navigation(startDestination = DESTINATION_ID) {
+ +NavDestination(provider[TestNavigator::class]).apply {
+ id = DESTINATION_ID
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+
+ @Test
+ fun navigationAddDestination() {
+ val graph = provider.navigation(startDestination = DESTINATION_ID) {
+ val destination = NavDestination(provider[TestNavigator::class]).apply {
+ id = DESTINATION_ID
+ }
+ addDestination(destination)
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun navigationMissingStartDestination() {
+ provider.navigation(startDestination = 0) {
+ navDestination(DESTINATION_ID) {}
+ }
+ fail("NavGraph should throw IllegalStateException if startDestination is zero")
+ }
+
+ @Test
+ fun navigationNested() {
+ val graph = provider.navigation(startDestination = DESTINATION_ID) {
+ navigation(DESTINATION_ID, startDestination = SECOND_DESTINATION_ID) {
+ navDestination(SECOND_DESTINATION_ID) {}
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+}
+
+private const val DESTINATION_ID = 1
+private const val SECOND_DESTINATION_ID = 2
+
+/**
+ * Create a base NavDestination. Generally, only subtypes of NavDestination should be
+ * added to a NavGraph (hence why this is not in the common-ktx library)
+ */
+fun NavGraphBuilder.navDestination(
+ @IdRes id: Int,
+ block: NavDestinationBuilder<NavDestination>.() -> Unit
+) = destination(NavDestinationBuilder(provider[TestNavigator::class], id).apply(block))
diff --git a/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavGraphTest.kt b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavGraphTest.kt
new file mode 100644
index 0000000..ebc11a7
--- /dev/null
+++ b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavGraphTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.navigation
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import androidx.navigation.testing.TestNavigator
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavGraphTest {
+ private val navGraphNavigator = NavGraphNavigator(InstrumentationRegistry.getTargetContext())
+ private val navigator = TestNavigator()
+
+ @Test
+ fun plusAssign() {
+ val graph = NavGraph(navGraphNavigator)
+ val destination = NavDestination(navigator).apply { id = DESTINATION_ID }
+ graph += destination
+ assertSame("plusAssign destination should be retrieved with get", destination,
+ graph[DESTINATION_ID])
+ }
+
+ @Test
+ fun minusAssign() {
+ val graph = NavGraph(navGraphNavigator)
+ val destination = NavDestination(navigator).apply { id = DESTINATION_ID }
+ graph += destination
+ assertSame("plusAssign destination should be retrieved with get", destination,
+ graph[DESTINATION_ID])
+ graph -= destination
+ assertFalse("Destination should be removed after minusAssign",
+ DESTINATION_ID in graph)
+ }
+
+ @Test
+ fun plusAssignGraph() {
+ val graph = NavGraph(navGraphNavigator)
+ val other = NavGraph(navGraphNavigator)
+ other += NavDestination(navigator).apply { id = DESTINATION_ID }
+ other += NavDestination(navigator).apply { id = SECOND_DESTINATION_ID }
+ graph += other
+ assertTrue("NavGraph should have destination1 from other",
+ DESTINATION_ID in graph)
+ assertFalse("other nav graph should not have destination1",
+ DESTINATION_ID in other)
+
+ assertTrue("NavGraph should have destination2 from other",
+ SECOND_DESTINATION_ID in graph)
+ assertFalse("other nav graph should not have destination2",
+ SECOND_DESTINATION_ID in other)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun getIllegalArgumentException() {
+ val graph = NavGraph(navGraphNavigator)
+ graph[DESTINATION_ID]
+ }
+}
+
+private const val DESTINATION_ID = 1
+private const val SECOND_DESTINATION_ID = 2
diff --git a/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
new file mode 100644
index 0000000..c33afe8
--- /dev/null
+++ b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.navigation
+
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NavOptionsTest {
+
+ @Test
+ fun launchSingleTop() {
+ val navOptions = navOptions {
+ launchSingleTop = true
+ }
+ assertTrue("NavOptions should have launchSingleTop set",
+ navOptions.shouldLaunchSingleTop())
+ }
+
+ @Test
+ fun launchDocument() {
+ val navOptions = navOptions {
+ launchDocument = true
+ }
+ assertTrue("NavOptions should have launchDocument set",
+ navOptions.shouldLaunchDocument())
+ }
+
+ @Test
+ fun clearTask() {
+ val navOptions = navOptions {
+ clearTask = true
+ }
+ assertTrue("NavOptions should have clearTask set",
+ navOptions.shouldClearTask())
+ }
+
+ @Test
+ fun popUpTo() {
+ val navOptions = navOptions {
+ popUpTo = DESTINATION_ID
+ }
+ assertEquals("NavOptions should have popUpTo destination id set",
+ DESTINATION_ID, navOptions.popUpTo)
+ assertFalse("NavOptions should have isPopUpToInclusive false by default",
+ navOptions.isPopUpToInclusive)
+ }
+
+ @Test
+ fun popUpToInclusive() {
+ val navOptions = navOptions {
+ popUpTo(DESTINATION_ID) {
+ inclusive = true
+ }
+ }
+ assertEquals("NavOptions should have popUpTo destination id set",
+ DESTINATION_ID, navOptions.popUpTo)
+ assertTrue("NavOptions should have isPopUpToInclusive set",
+ navOptions.isPopUpToInclusive)
+ }
+
+ @Test
+ fun anim() {
+ val navOptions = navOptions {
+ anim {
+ enter = ENTER_ANIM_ID
+ exit = EXIT_ANIM_ID
+ popEnter = POP_ENTER_ANIM_ID
+ popExit = POP_EXIT_ANIM_ID
+ }
+ }
+ assertEquals("NavOptions should have enter animation set",
+ ENTER_ANIM_ID, navOptions.enterAnim)
+ assertEquals("NavOptions should have exit animation set",
+ EXIT_ANIM_ID, navOptions.exitAnim)
+ assertEquals("NavOptions should have pop enter animation set",
+ POP_ENTER_ANIM_ID, navOptions.popEnterAnim)
+ assertEquals("NavOptions should have pop exit animation set",
+ POP_EXIT_ANIM_ID, navOptions.popExitAnim)
+ }
+}
+
+private const val DESTINATION_ID = 1
+
+private const val ENTER_ANIM_ID = 10
+private const val EXIT_ANIM_ID = 11
+private const val POP_ENTER_ANIM_ID = 12
+private const val POP_EXIT_ANIM_ID = 13
diff --git a/navigation/common/ktx/src/main/AndroidManifest.xml b/navigation/common/ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..799fc93
--- /dev/null
+++ b/navigation/common/ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.navigation.ktx"/>
diff --git a/navigation/common/ktx/src/main/java/androidx/navigation/NavDestinationBuilder.kt b/navigation/common/ktx/src/main/java/androidx/navigation/NavDestinationBuilder.kt
new file mode 100644
index 0000000..55cc97f
--- /dev/null
+++ b/navigation/common/ktx/src/main/java/androidx/navigation/NavDestinationBuilder.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.navigation
+
+import android.os.Bundle
+import android.support.annotation.IdRes
+
+@DslMarker
+annotation class NavDestinationDsl
+
+/**
+ * DSL for constructing a new [NavDestination]
+ */
+@NavDestinationDsl
+open class NavDestinationBuilder<out D : NavDestination>(
+ protected val navigator: Navigator<out D>,
+ @IdRes val id: Int
+) {
+ /**
+ * The descriptive label of the destination
+ */
+ var label: CharSequence? = null
+
+ /**
+ * The default arguments that should be passed to the destination
+ */
+ var defaultArguments: Bundle? = null
+
+ private var deepLinks = mutableListOf<String>()
+
+ /**
+ * Add a deep link to this destination.
+ *
+ * In addition to a direct Uri match, the following features are supported:
+ *
+ * * Uris without a scheme are assumed as http and https. For example,
+ * `www.example.com` will match `http://www.example.com` and
+ * `https://www.example.com`.
+ * * Placeholders in the form of `{placeholder_name}` matches 1 or more
+ * characters. The String value of the placeholder will be available in the arguments
+ * [Bundle] with a key of the same name. For example,
+ * `http://www.example.com/users/{id}` will match
+ * `http://www.example.com/users/4`.
+ * * The `.*` wildcard can be used to match 0 or more characters.
+ *
+ * @param uriPattern The uri pattern to add as a deep link
+ */
+ fun deepLink(uriPattern: String) {
+ deepLinks.add(uriPattern)
+ }
+
+ private var actions = mutableMapOf<Int, NavAction>()
+
+ /**
+ * Adds a new [NavAction] to the destination
+ */
+ fun action(actionId: Int, block: NavActionBuilder.() -> Unit) {
+ actions[actionId] = NavActionBuilder().apply(block).build()
+ }
+
+ /**
+ * Build the NavDestination by calling [Navigator.createDestination].
+ */
+ open fun build(): D {
+ return navigator.createDestination().also { destination ->
+ destination.id = id
+ destination.label = label
+ destination.setDefaultArguments(defaultArguments)
+ deepLinks.forEach { deepLink ->
+ destination.addDeepLink(deepLink)
+ }
+ actions.forEach { actionId, action ->
+ destination.putAction(actionId, action)
+ }
+ }
+ }
+}
+
+/**
+ * DSL for building a [NavAction].
+ */
+@NavDestinationDsl
+class NavActionBuilder {
+ /**
+ * The ID of the destination that should be navigated to when this action is used
+ */
+ var destinationId: Int = 0
+
+ private var navOptions: NavOptions? = null
+
+ /**
+ * Sets the [NavOptions] for this action that should be used by default
+ */
+ fun navOptions(block: NavOptionsBuilder.() -> Unit) {
+ navOptions = NavOptionsBuilder().apply(block).build()
+ }
+
+ internal fun build() = NavAction(destinationId, navOptions)
+}
diff --git a/navigation/common/ktx/src/main/java/androidx/navigation/NavGraph.kt b/navigation/common/ktx/src/main/java/androidx/navigation/NavGraph.kt
new file mode 100644
index 0000000..eaf2775
--- /dev/null
+++ b/navigation/common/ktx/src/main/java/androidx/navigation/NavGraph.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package androidx.navigation
+
+import android.support.annotation.IdRes
+
+/**
+ * Returns the destination with `id`.
+ *
+ * @throws IllegalArgumentException if no destination is found with that id.
+ */
+inline operator fun NavGraph.get(@IdRes id: Int): NavDestination =
+ findNode(id) ?: throw IllegalArgumentException("No destination for $id was found in $this")
+
+/** Returns `true` if a destination with `id` is found in this navigation graph. */
+operator fun NavGraph.contains(@IdRes id: Int): Boolean = findNode(id) != null
+
+/**
+ * Adds a destination to this NavGraph. The destination must have an
+ * [id][NavDestination.getId] set.
+ *
+ * The destination must not have a [parent][NavDestination.getParent] set. If
+ * the destination is already part of a [NavGraph], call
+ * [NavGraph.remove] before calling this method.</p>
+ *
+ * @param node destination to add
+ */
+inline operator fun NavGraph.plusAssign(node: NavDestination) {
+ addDestination(node)
+}
+
+/**
+ * Add all destinations from another collection to this one. As each destination has at most
+ * one parent, the destinations will be removed from the given NavGraph.
+ *
+ * @param other collection of destinations to add. All destinations will be removed from the
+ * parameter graph after being added to this graph.
+ */
+inline operator fun NavGraph.plusAssign(other: NavGraph) {
+ addAll(other)
+}
+
+/** Removes `node` from this navigation graph. */
+inline operator fun NavGraph.minusAssign(node: NavDestination) {
+ remove(node)
+}
diff --git a/navigation/common/ktx/src/main/java/androidx/navigation/NavGraphBuilder.kt b/navigation/common/ktx/src/main/java/androidx/navigation/NavGraphBuilder.kt
new file mode 100644
index 0000000..97579ec
--- /dev/null
+++ b/navigation/common/ktx/src/main/java/androidx/navigation/NavGraphBuilder.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.navigation
+
+import android.support.annotation.IdRes
+
+/**
+ * Construct a new [NavGraph]
+ */
+inline fun NavigatorProvider.navigation(
+ @IdRes id: Int = 0,
+ @IdRes startDestination: Int,
+ block: NavGraphBuilder.() -> Unit
+) = NavGraphBuilder(this, id, startDestination).apply(block).build()
+
+/**
+ * Construct a nested [NavGraph]
+ */
+inline fun NavGraphBuilder.navigation(
+ @IdRes id: Int,
+ @IdRes startDestination: Int,
+ block: NavGraphBuilder.() -> Unit
+) = destination(NavGraphBuilder(provider, id, startDestination).apply(block))
+
+/**
+ * DSL for constructing a new [NavGraph]
+ */
+@NavDestinationDsl
+class NavGraphBuilder(
+ val provider: NavigatorProvider,
+ @IdRes id: Int,
+ @IdRes private var startDestination: Int
+) : NavDestinationBuilder<NavGraph>(provider[NavGraphNavigator::class], id) {
+ private val destinations = mutableListOf<NavDestination>()
+
+ /**
+ * Build and add a new destination to the [NavGraphBuilder]
+ */
+ fun <D : NavDestination> destination(navDestination: NavDestinationBuilder<D>) {
+ destinations += navDestination.build()
+ }
+
+ /**
+ * Adds this destination to the [NavGraphBuilder]
+ */
+ operator fun NavDestination.unaryPlus() {
+ addDestination(this)
+ }
+
+ /**
+ * Add the destination to the [NavGraphBuilder]
+ */
+ fun addDestination(destination: NavDestination) {
+ destinations += destination
+ }
+
+ override fun build(): NavGraph = super.build().also { navGraph ->
+ navGraph.addDestinations(destinations)
+ if (startDestination == 0) {
+ throw IllegalStateException("You must set a startDestination")
+ }
+ navGraph.startDestination = startDestination
+ }
+}
diff --git a/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt b/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt
new file mode 100644
index 0000000..5b4781d
--- /dev/null
+++ b/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.navigation
+
+import android.support.annotation.AnimRes
+import android.support.annotation.AnimatorRes
+import android.support.annotation.IdRes
+
+@DslMarker
+annotation class NavOptionsDsl
+
+/**
+ * Construct a new [NavOptions]
+ */
+fun navOptions(block: NavOptionsBuilder.() -> Unit): NavOptions =
+ NavOptionsBuilder().apply(block).build()
+
+/**
+ * DSL for constructing a new [NavOptions]
+ */
+@NavOptionsDsl
+class NavOptionsBuilder {
+ private val builder = NavOptions.Builder()
+
+ /**
+ * Whether this navigation action should launch as single-top (i.e., there will be at most
+ * one copy of a given destination on the top of the back stack).
+ *
+ * This functions similarly to how [android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP]
+ * works with activites.
+ */
+ var launchSingleTop = false
+
+ /**
+ * Whether this navigation action should launch the destination in a new document.
+ *
+ * This functions similarly to how [android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT]
+ * works with activites.
+ */
+ var launchDocument = false
+
+ /**
+ * Whether this navigation action should clear the entire back stack
+ *
+ * This functions similarly to how [android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK]
+ * works with activites.
+ */
+ var clearTask = false
+
+ /**
+ * Pop up to a given destination before navigating. This pops all non-matching destinations
+ * from the back stack until this destination is found.
+ */
+ @IdRes
+ var popUpTo: Int = 0
+ set(value) {
+ field = value
+ inclusive = false
+ }
+ private var inclusive = false
+
+ /**
+ * Pop up to a given destination before navigating. This pops all non-matching destinations
+ * from the back stack until this destination is found.
+ */
+ fun popUpTo(@IdRes id: Int, block: PopUpToBuilder.() -> Unit) {
+ popUpTo = id
+ inclusive = PopUpToBuilder().apply(block).inclusive
+ }
+
+ /**
+ * Sets any custom Animation or Animator resources that should be used.
+ *
+ * Note: Animator resources are not supported for navigating to a new Activity
+ */
+ fun anim(block: AnimBuilder.() -> Unit) {
+ AnimBuilder().apply(block).run {
+ this@NavOptionsBuilder.builder.setEnterAnim(enter)
+ .setExitAnim(exit)
+ .setPopEnterAnim(popEnter)
+ .setPopExitAnim(popExit)
+ }
+ }
+
+ internal fun build() = builder.apply {
+ setLaunchSingleTop(launchSingleTop)
+ setLaunchDocument(launchDocument)
+ setClearTask(clearTask)
+ setPopUpTo(popUpTo, inclusive)
+ }.build()
+}
+
+/**
+ * DSL for customizing [NavOptionsBuilder.popUpTo] operations.
+ */
+@NavOptionsDsl
+class PopUpToBuilder {
+ /**
+ * Whether the `popUpTo` destination should be popped from the back stack.
+ */
+ var inclusive: Boolean = false
+}
+
+/**
+ * DSL for setting custom Animation or Animator resources on a [NavOptionsBuilder]
+ */
+@NavOptionsDsl
+class AnimBuilder {
+ /**
+ * The custom Animation or Animator resource for the enter animation.
+ *
+ * Note: Animator resources are not supported for navigating to a new Activity
+ */
+ @AnimRes
+ @AnimatorRes
+ var enter = -1
+
+ /**
+ * The custom Animation or Animator resource for the exit animation.
+ *
+ * Note: Animator resources are not supported for navigating to a new Activity
+ */
+ @AnimRes
+ @AnimatorRes
+ var exit = -1
+
+ /**
+ * The custom Animation or Animator resource for the enter animation
+ * when popping off the back stack.
+ *
+ * Note: Animator resources are not supported for navigating to a new Activity
+ */
+ @AnimRes
+ @AnimatorRes
+ var popEnter = -1
+
+ /**
+ * The custom Animation or Animator resource for the exit animation
+ * when popping off the back stack.
+ *
+ * Note: Animator resources are not supported for navigating to a new Activity
+ */
+ @AnimRes
+ @AnimatorRes
+ var popExit = -1
+}
diff --git a/navigation/common/ktx/src/main/java/androidx/navigation/NavigatorProvider.kt b/navigation/common/ktx/src/main/java/androidx/navigation/NavigatorProvider.kt
new file mode 100644
index 0000000..ec43d45
--- /dev/null
+++ b/navigation/common/ktx/src/main/java/androidx/navigation/NavigatorProvider.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package androidx.navigation
+
+import kotlin.reflect.KClass
+
+/**
+ * Retrieves a registered [Navigator] by name.
+ *
+ * @throws IllegalStateException if the Navigator has not been added
+ */
+inline operator fun <D : NavDestination, T : Navigator<D>> NavigatorProvider.get(name: String): T =
+ getNavigator(name)
+
+/**
+ * Retrieves a registered [Navigator] using the name provided by the
+ * [Navigator.Name annotation][Navigator.Name].
+ *
+ * @throws IllegalStateException if the Navigator has not been added
+ */
+inline operator fun <D : NavDestination, T : Navigator<D>> NavigatorProvider.get(
+ clazz: KClass<T>
+): T = getNavigator(clazz.java)
+
+/**
+ * Register a [Navigator] by name. If a navigator by this name is already
+ * registered, this new navigator will replace it.
+ *
+ * @return the previously added [Navigator] for the given name, if any
+ */
+inline operator fun <D : NavDestination> NavigatorProvider.set(
+ name: String,
+ navigator: Navigator<D>
+) = addNavigator(name, navigator)
+
+/**
+ * Register a navigator using the name provided by the
+ * [Navigator.Name annotation][Navigator.Name].
+ */
+inline operator fun <D : NavDestination> NavigatorProvider.plusAssign(navigator: Navigator<D>) {
+ addNavigator(navigator)
+}
diff --git a/navigation/common/ktx/src/test/java/androidx/navigation/NavigatorProviderTest.kt b/navigation/common/ktx/src/test/java/androidx/navigation/NavigatorProviderTest.kt
new file mode 100644
index 0000000..95d7324
--- /dev/null
+++ b/navigation/common/ktx/src/test/java/androidx/navigation/NavigatorProviderTest.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.navigation
+
+import android.support.test.filters.SmallTest
+import androidx.navigation.testing.TestNavigator
+import org.junit.Assert.assertSame
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class NavigatorProviderTest {
+ private val provider = SimpleNavigatorProvider()
+
+ @Test
+ fun set() {
+ val navigator = TestNavigator()
+ provider[NAME] = navigator
+ val foundNavigator: Navigator<NavDestination> = provider[NAME]
+ assertSame("Set destination should be retrieved with get", navigator,
+ foundNavigator)
+ }
+
+ @Test
+ fun plusAssign() {
+ val navigator = TestNavigator()
+ provider += navigator
+ assertSame("Set destination should be retrieved with get", navigator,
+ provider[TestNavigator::class])
+ }
+}
+
+private const val NAME = "TEST"
diff --git a/navigation/fragment/ktx/build.gradle b/navigation/fragment/ktx/build.gradle
new file mode 100644
index 0000000..9c03b83
--- /dev/null
+++ b/navigation/fragment/ktx/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":navigation:fragment"))
+ // Ensure that the -ktx dependency graph mirrors the Java dependency graph
+ api(project(":navigation:runtime-ktx"))
+ api(KOTLIN_STDLIB)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+}
+
+supportLibrary {
+ name = "Android Navigation Fragment Kotlin Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.NAVIGATION
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2018"
+ description = "Android Navigation-Fragment-Ktx"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
+
diff --git a/navigation/fragment/ktx/src/androidTest/AndroidManifest.xml b/navigation/fragment/ktx/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..54df6d4
--- /dev/null
+++ b/navigation/fragment/ktx/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.navigation.fragment.ktx.test">
+ <application>
+ <activity android:name="androidx.navigation.fragment.TestActivity" />
+ </application>
+</manifest>
diff --git a/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorDestinationBuilderTest.kt b/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorDestinationBuilderTest.kt
new file mode 100644
index 0000000..5c2a9c1
--- /dev/null
+++ b/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorDestinationBuilderTest.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.navigation.fragment
+
+import android.support.test.annotation.UiThreadTest
+import android.support.test.filters.SmallTest
+import android.support.test.rule.ActivityTestRule
+import android.support.test.runner.AndroidJUnit4
+import android.support.v4.app.Fragment
+import androidx.navigation.contains
+import androidx.navigation.createGraph
+import androidx.navigation.get
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TestNavigatorDestinationBuilderTest {
+ @get:Rule
+ val activityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
+ private val fragmentManager get() = activityRule.activity.supportFragmentManager
+
+ @UiThreadTest
+ @Test fun fragment() {
+ val navHostFragment = NavHostFragment()
+ fragmentManager.beginTransaction()
+ .add(android.R.id.content, navHostFragment)
+ .commitNow()
+ val graph = navHostFragment.createGraph(startDestination = DESTINATION_ID) {
+ fragment<BuilderTestFragment>(DESTINATION_ID)
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Fragment class should be set to BuilderTestFragment",
+ BuilderTestFragment::class.java,
+ (graph[DESTINATION_ID] as FragmentNavigator.Destination).fragmentClass)
+ }
+
+ @UiThreadTest
+ @Test fun fragmentWithBody() {
+ val navHostFragment = NavHostFragment()
+ fragmentManager.beginTransaction()
+ .add(android.R.id.content, navHostFragment)
+ .commitNow()
+ val graph = navHostFragment.createGraph(startDestination = DESTINATION_ID) {
+ fragment<BuilderTestFragment>(DESTINATION_ID) {
+ label = LABEL
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Fragment class should be set to BuilderTestFragment",
+ BuilderTestFragment::class.java,
+ (graph[DESTINATION_ID] as FragmentNavigator.Destination).fragmentClass)
+ assertEquals("Fragment should have label set",
+ LABEL, graph[DESTINATION_ID].label)
+ }
+}
+
+private const val DESTINATION_ID = 1
+private const val LABEL = "Test"
+class BuilderTestFragment : Fragment()
diff --git a/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/FragmentTest.kt b/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/FragmentTest.kt
new file mode 100644
index 0000000..2540852
--- /dev/null
+++ b/navigation/fragment/ktx/src/androidTest/java/androidx/navigation/fragment/FragmentTest.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.navigation.fragment
+
+import android.support.test.annotation.UiThreadTest
+import android.support.test.filters.SmallTest
+import android.support.test.rule.ActivityTestRule
+import android.support.v4.app.Fragment
+import android.support.v4.app.FragmentActivity
+import androidx.navigation.fragment.ktx.test.R
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+
+@SmallTest
+class ActivityTest {
+ @get:Rule val activityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
+ private val fragmentManager get() = activityRule.activity.supportFragmentManager
+ private val contentFragment get() = fragmentManager.findFragmentById(android.R.id.content)
+
+ @UiThreadTest
+ @Test fun navController() {
+ val navHostFragment = NavHostFragment.create(R.navigation.test_graph)
+ fragmentManager.beginTransaction()
+ .add(android.R.id.content, navHostFragment)
+ .commitNow()
+ assertTrue("Fragment should have NavController set",
+ contentFragment.navController == navHostFragment.navController)
+ }
+
+ @UiThreadTest
+ @Test fun navControllerNull() {
+ fragmentManager.beginTransaction()
+ .add(android.R.id.content, TestFragment())
+ .commitNow()
+ try {
+ contentFragment.navController
+ fail("navController should throw IllegalStateException if a NavController was not set")
+ } catch (e: IllegalStateException) {
+ // Expected
+ }
+ }
+
+ @UiThreadTest
+ @Test fun findNavController() {
+ val navHostFragment = NavHostFragment.create(R.navigation.test_graph)
+ fragmentManager.beginTransaction()
+ .add(android.R.id.content, navHostFragment)
+ .commitNow()
+
+ val foundNavController = contentFragment.findNavController()
+ assertNotNull("findNavController should return non-null if a NavController was set",
+ foundNavController)
+ assertTrue("Fragment should have NavController set",
+ foundNavController == navHostFragment.navController)
+ }
+
+ @UiThreadTest
+ @Test fun findNavControllerNull() {
+ fragmentManager.beginTransaction()
+ .add(android.R.id.content, TestFragment())
+ .commitNow()
+ assertNull("findNavController should return null if a NavController was never set",
+ contentFragment.findNavController())
+ }
+}
+
+class TestActivity : FragmentActivity()
+class TestFragment : Fragment()
diff --git a/navigation/fragment/ktx/src/androidTest/res/navigation/test_graph.xml b/navigation/fragment/ktx/src/androidTest/res/navigation/test_graph.xml
new file mode 100644
index 0000000..5f1268a
--- /dev/null
+++ b/navigation/fragment/ktx/src/androidTest/res/navigation/test_graph.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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.
+ -->
+<navigation
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ app:startDestination="@+id/start">
+ <fragment
+ android:id="@+id/start"
+ android:name="androidx.navigation.fragment.TestFragment"/>
+</navigation>
diff --git a/navigation/fragment/ktx/src/main/AndroidManifest.xml b/navigation/fragment/ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..bd32886
--- /dev/null
+++ b/navigation/fragment/ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest package="androidx.navigation.fragment.ktx"/>
diff --git a/navigation/fragment/ktx/src/main/java/androidx/navigation/fragment/Fragment.kt b/navigation/fragment/ktx/src/main/java/androidx/navigation/fragment/Fragment.kt
new file mode 100644
index 0000000..41e6e43
--- /dev/null
+++ b/navigation/fragment/ktx/src/main/java/androidx/navigation/fragment/Fragment.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.navigation.fragment
+
+import android.support.v4.app.Fragment
+import androidx.navigation.NavController
+
+/**
+ * Find a [NavController] given a [Fragment]
+ */
+fun Fragment.findNavController(): NavController? =
+ NavHostFragment.findNavController(this)
+
+/**
+ * Gets the [NavController] given a [Fragment].
+ *
+ * Calling this on a Fragment that is not a [NavHostFragment] or within a [NavHostFragment]
+ * will result in an [IllegalStateException]
+ */
+val Fragment.navController: NavController
+ get() = NavHostFragment.getNavController(this)
diff --git a/navigation/fragment/ktx/src/main/java/androidx/navigation/fragment/FragmentNavigatorDestinationBuilder.kt b/navigation/fragment/ktx/src/main/java/androidx/navigation/fragment/FragmentNavigatorDestinationBuilder.kt
new file mode 100644
index 0000000..a188bf0
--- /dev/null
+++ b/navigation/fragment/ktx/src/main/java/androidx/navigation/fragment/FragmentNavigatorDestinationBuilder.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.navigation.fragment
+
+import android.support.annotation.IdRes
+import android.support.v4.app.Fragment
+import androidx.navigation.NavDestinationBuilder
+import androidx.navigation.NavDestinationDsl
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.get
+import kotlin.reflect.KClass
+
+/**
+ * Construct a new [FragmentNavigator.Destination]
+ */
+inline fun <reified F : Fragment> NavGraphBuilder.fragment(@IdRes id: Int) = fragment<F>(id) {}
+
+/**
+ * Construct a new [FragmentNavigator.Destination]
+ */
+inline fun <reified F : Fragment> NavGraphBuilder.fragment(
+ @IdRes id: Int,
+ block: FragmentNavigatorDestinationBuilder.() -> Unit
+) = destination(FragmentNavigatorDestinationBuilder(
+ provider[FragmentNavigator::class],
+ id,
+ F::class
+).apply(block))
+
+/**
+ * DSL for constructing a new [FragmentNavigator.Destination]
+ */
+@NavDestinationDsl
+class FragmentNavigatorDestinationBuilder(
+ navigator: FragmentNavigator,
+ @IdRes id: Int,
+ private val fragmentClass: KClass<out Fragment>
+) : NavDestinationBuilder<FragmentNavigator.Destination>(navigator, id) {
+
+ override fun build(): FragmentNavigator.Destination =
+ super.build().also { destination ->
+ destination.fragmentClass = fragmentClass.java
+ }
+}
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/BaseNavigationActivity.java b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/BaseNavigationActivity.java
index 7be585f..456b47a 100644
--- a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/BaseNavigationActivity.java
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/BaseNavigationActivity.java
@@ -17,6 +17,7 @@
package androidx.navigation.fragment.test;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
@@ -36,7 +37,8 @@
super.onCreate(savedInstanceState);
}
+ @NonNull
public NavController getNavController() {
- return Navigation.findNavController(this, R.id.nav_host);
+ return Navigation.getNavController(this, R.id.nav_host);
}
}
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index a1783a6..8f6dfe5 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -91,6 +91,7 @@
* @param fragment the locally scoped Fragment for navigation
* @return the locally scoped {@link NavController} for navigating from this {@link Fragment}
*/
+ @Nullable
public static NavController findNavController(@Nullable Fragment fragment) {
if (fragment == null) {
return null;
@@ -110,7 +111,31 @@
}
// Try looking for one associated with the view instead, if applicable
- return Navigation.findNavController(fragment.getView());
+ View view = fragment.getView();
+ return view != null ? Navigation.findNavController(view) : null;
+ }
+
+ /**
+ * Gets the {@link NavController} given a local {@link Fragment}.
+ *
+ * <p>This method will locate the {@link NavController} associated with this Fragment,
+ * looking first for a {@link NavHostFragment} along the given Fragment's parent chain.
+ * If a {@link NavController} is not found, this method will look for one along this
+ * Fragment's {@link Fragment#getView() view hierarchy} as specified by
+ * {@link Navigation#findNavController(View)}.</p>
+ *
+ * @param fragment the locally scoped Fragment for navigation
+ * @return the locally scoped {@link NavController} for navigating from this {@link Fragment}
+ * @throws IllegalStateException if the given Fragment does not correspond with a
+ * {@link NavHost} or is not within a NavHost.
+ */
+ public static NavController getNavController(@Nullable Fragment fragment) {
+ NavController navController = findNavController(fragment);
+ if (navController == null) {
+ throw new IllegalStateException("Fragment " + fragment
+ + " does not have a NavController set");
+ }
+ return navController;
}
private NavController mNavController;
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java
index 985645c..1c8e768 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java
@@ -58,7 +58,7 @@
EditText editArgs = view.findViewById(R.id.edit_args);
Bundle args = new Bundle();
args.putString("myarg", editArgs.getText().toString());
- PendingIntent deeplink = Navigation.findNavController(v).createDeepLink()
+ PendingIntent deeplink = Navigation.getNavController(v).createDeepLink()
.setDestination(R.id.android)
.setArguments(args)
.createPendingIntent();
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java
index 3a78c30..039af32 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java
@@ -98,14 +98,14 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return NavigationUI.onNavDestinationSelected(
- item, Navigation.findNavController(this, R.id.my_nav_host_fragment))
+ item, Navigation.getNavController(this, R.id.my_nav_host_fragment))
|| super.onOptionsItemSelected(item);
}
@Override
public boolean onSupportNavigateUp() {
return NavigationUI.navigateUp(
- mDrawerLayout, Navigation.findNavController(this, R.id.my_nav_host_fragment)
+ mDrawerLayout, Navigation.getNavController(this, R.id.my_nav_host_fragment)
);
}
}
diff --git a/navigation/runtime/ktx/build.gradle b/navigation/runtime/ktx/build.gradle
new file mode 100644
index 0000000..f9e6eaa
--- /dev/null
+++ b/navigation/runtime/ktx/build.gradle
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":navigation:runtime"))
+ // Ensure that the -ktx dependency graph mirrors the Java dependency graph
+ api(project(":navigation:common-ktx"))
+ api(KOTLIN_STDLIB)
+ androidTestImplementation(project(":navigation:testing-ktx"))
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+}
+
+supportLibrary {
+ name = "Android Navigation Runtime Kotlin Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.NAVIGATION
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2018"
+ description = "Android Navigation-Runtime-Ktx"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
diff --git a/navigation/runtime/ktx/src/androidTest/AndroidManifest.xml b/navigation/runtime/ktx/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..a1a7c59
--- /dev/null
+++ b/navigation/runtime/ktx/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.navigation.ktx.test">
+ <application>
+ <activity android:name="androidx.navigation.TestActivity"/>
+ </application>
+</manifest>
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
new file mode 100644
index 0000000..53704eb
--- /dev/null
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.navigation
+
+import android.net.Uri
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import junit.framework.Assert.assertTrue
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActivityNavigatorDestinationBuilderTest {
+ private val navController = NavController(InstrumentationRegistry.getTargetContext())
+
+ @Test
+ fun activity() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ activity(DESTINATION_ID) {
+ label = LABEL
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Destination should have label set",
+ LABEL,
+ graph[DESTINATION_ID].label)
+ }
+
+ @Test
+ fun activityClass() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ activity(DESTINATION_ID) {
+ activityClass = TestActivity::class
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Destination should have ComponentName set",
+ TestActivity::class.java.name,
+ (graph[DESTINATION_ID] as ActivityNavigator.Destination).component.className)
+ }
+
+ @Test
+ fun action() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ activity(DESTINATION_ID) {
+ action = ACTION
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Destination should have action set",
+ ACTION,
+ (graph[DESTINATION_ID] as ActivityNavigator.Destination).action)
+ }
+
+ @Test
+ fun data() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ activity(DESTINATION_ID) {
+ data = DATA
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Destination should have data set",
+ DATA,
+ (graph[DESTINATION_ID] as ActivityNavigator.Destination).data)
+ }
+
+ @Test
+ fun dataPattern() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ activity(DESTINATION_ID) {
+ dataPattern = DATA_PATTERN
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Destination should have data pattern set",
+ DATA_PATTERN,
+ (graph[DESTINATION_ID] as ActivityNavigator.Destination).dataPattern)
+ }
+}
+
+private const val DESTINATION_ID = 1
+private const val LABEL = "Test"
+private const val ACTION = "ACTION_TEST"
+private val DATA = Uri.parse("http://www.example.com")
+private const val DATA_PATTERN = "http://www.example.com/{id}"
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityTest.kt
new file mode 100644
index 0000000..6468d9a
--- /dev/null
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.navigation
+
+import android.app.Activity
+import android.os.Bundle
+import android.support.test.filters.SmallTest
+import android.support.test.rule.ActivityTestRule
+import android.view.View
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.Test
+
+@SmallTest
+class ActivityTest {
+ @get:Rule val activityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
+ private val view get() = activityRule.activity.findViewById<View>(VIEW_ID)
+
+ @Test fun navController() {
+ val navController = NavController(activityRule.activity)
+ view.navController = navController
+ assertTrue("View should have NavController set",
+ activityRule.activity.navController(VIEW_ID) == navController)
+ }
+
+ @Test fun navControllerNull() {
+ try {
+ activityRule.activity.navController(VIEW_ID)
+ fail("navController should throw IllegalStateException if a NavController was not set")
+ } catch (e: IllegalStateException) {
+ // Expected
+ }
+ }
+
+ @Test fun findNavController() {
+ val navController = NavController(activityRule.activity)
+ view.navController = navController
+
+ val foundNavController = activityRule.activity.findNavController(VIEW_ID)
+ assertNotNull("findNavController should return non-null if a NavController was set",
+ foundNavController)
+ assertTrue("View should have NavController set",
+ foundNavController == navController)
+ }
+
+ @Test fun findNavControllerNull() {
+ assertNull("findNavController should return null if a NavController was never set",
+ activityRule.activity.findNavController(VIEW_ID))
+ }
+}
+
+private const val VIEW_ID = 1
+
+class TestActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(View(this).apply {
+ id = VIEW_ID
+ })
+ }
+}
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/NavControllerTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/NavControllerTest.kt
new file mode 100644
index 0000000..bf6f332
--- /dev/null
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/NavControllerTest.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.navigation
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import androidx.navigation.testing.TestNavigator
+import androidx.navigation.testing.test
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class NavControllerTest {
+ private val navController = NavController(InstrumentationRegistry.getTargetContext()).apply {
+ navigatorProvider += TestNavigator()
+ }
+
+ @Test
+ fun createGraph() {
+ val graph = navController.createGraph(startDestination = DESTINATION_ID) {
+ test(DESTINATION_ID)
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+}
+
+private const val DESTINATION_ID = 1
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/NavHostTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/NavHostTest.kt
new file mode 100644
index 0000000..7a74ab2
--- /dev/null
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/NavHostTest.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.navigation
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import androidx.navigation.testing.TestNavigator
+import androidx.navigation.testing.test
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@SmallTest
+class NavHostTest {
+ private val navController = NavController(InstrumentationRegistry.getTargetContext()).apply {
+ navigatorProvider += TestNavigator()
+ }
+ private val navHost = NavHost { this@NavHostTest.navController }
+
+ @Test
+ fun createGraph() {
+ val graph = navHost.createGraph(startDestination = DESTINATION_ID) {
+ test(DESTINATION_ID)
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+}
+
+private const val DESTINATION_ID = 1
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ViewTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ViewTest.kt
new file mode 100644
index 0000000..f6d1978
--- /dev/null
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ViewTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.navigation
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import android.view.View
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ViewTest {
+
+ @Test fun navController() {
+ val view = View(InstrumentationRegistry.getTargetContext())
+ val navController = NavController(InstrumentationRegistry.getTargetContext())
+ view.navController = navController
+ assertTrue("View should have NavController set",
+ view.navController == navController)
+ }
+
+ @Test fun navControllerNull() {
+ val view = View(InstrumentationRegistry.getTargetContext())
+ try {
+ view.navController
+ fail("navController should throw IllegalStateException if a NavController was not set")
+ } catch (e: IllegalStateException) {
+ // Expected
+ }
+ }
+
+ @Test fun findNavController() {
+ val view = View(InstrumentationRegistry.getTargetContext())
+ val navController = NavController(InstrumentationRegistry.getTargetContext())
+ view.navController = navController
+
+ val foundNavController = view.findNavController()
+ assertNotNull("findNavController should return non-null if a NavController was set",
+ foundNavController)
+ assertTrue("View should have NavController set",
+ foundNavController == navController)
+ }
+
+ @Test fun findNavControllerNull() {
+ val view = View(InstrumentationRegistry.getTargetContext())
+ assertNull("findNavController should return null if a NavController was never set",
+ view.findNavController())
+ }
+}
diff --git a/navigation/runtime/ktx/src/main/AndroidManifest.xml b/navigation/runtime/ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..799fc93
--- /dev/null
+++ b/navigation/runtime/ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.navigation.ktx"/>
diff --git a/navigation/runtime/ktx/src/main/java/androidx/navigation/Activity.kt b/navigation/runtime/ktx/src/main/java/androidx/navigation/Activity.kt
new file mode 100644
index 0000000..dff618a
--- /dev/null
+++ b/navigation/runtime/ktx/src/main/java/androidx/navigation/Activity.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.navigation
+
+import android.app.Activity
+import android.support.annotation.IdRes
+
+/**
+ * Find a [NavController] given the id of a View and its containing
+ * [Activity].
+ */
+fun Activity.findNavController(@IdRes viewId: Int): NavController? =
+ Navigation.findNavController(this, viewId)
+
+/**
+ * Gets the [NavController] given the id of a View and its containing
+ * [Activity].
+ *
+ * Calling this on a View that is not a [NavHost] or within a [NavHost]
+ * will result in an [IllegalStateException]
+ */
+fun Activity.navController(@IdRes viewId: Int): NavController =
+ Navigation.getNavController(this, viewId)
diff --git a/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt b/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt
new file mode 100644
index 0000000..6de930d
--- /dev/null
+++ b/navigation/runtime/ktx/src/main/java/androidx/navigation/ActivityNavigatorDestinationBuilder.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package androidx.navigation
+
+import android.app.Activity
+import android.content.ComponentName
+import android.net.Uri
+import android.support.annotation.IdRes
+import kotlin.reflect.KClass
+
+/**
+ * Construct a new [ActivityNavigator.Destination]
+ */
+inline fun NavGraphBuilder.activity(
+ @IdRes id: Int,
+ block: ActivityNavigatorDestinationBuilder.() -> Unit
+) = destination(ActivityNavigatorDestinationBuilder(
+ provider[ActivityNavigator::class],
+ id
+).apply(block))
+
+/**
+ * DSL for constructing a new [ActivityNavigator.Destination]
+ */
+@NavDestinationDsl
+class ActivityNavigatorDestinationBuilder(
+ navigator: ActivityNavigator,
+ @IdRes id: Int
+) : NavDestinationBuilder<ActivityNavigator.Destination>(navigator, id) {
+ private val context = navigator.context
+
+ var activityClass: KClass<out Activity>? = null
+
+ var action: String? = null
+
+ var data: Uri? = null
+
+ var dataPattern: String? = null
+
+ override fun build(): ActivityNavigator.Destination =
+ super.build().also { destination ->
+ activityClass?.let { clazz ->
+ destination.setComponentName(ComponentName(context, clazz.java))
+ }
+ destination.action = action
+ destination.data = data
+ destination.dataPattern = dataPattern
+ }
+}
diff --git a/navigation/runtime/ktx/src/main/java/androidx/navigation/NavController.kt b/navigation/runtime/ktx/src/main/java/androidx/navigation/NavController.kt
new file mode 100644
index 0000000..76fb4dd
--- /dev/null
+++ b/navigation/runtime/ktx/src/main/java/androidx/navigation/NavController.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.navigation
+
+import android.support.annotation.IdRes
+
+/**
+ * Construct a new [NavGraph]
+ */
+inline fun NavController.createGraph(
+ @IdRes id: Int = 0,
+ @IdRes startDestination: Int,
+ block: NavGraphBuilder.() -> Unit
+): NavGraph = navigatorProvider.navigation(id, startDestination, block)
diff --git a/navigation/runtime/ktx/src/main/java/androidx/navigation/NavHost.kt b/navigation/runtime/ktx/src/main/java/androidx/navigation/NavHost.kt
new file mode 100644
index 0000000..1a3bc05
--- /dev/null
+++ b/navigation/runtime/ktx/src/main/java/androidx/navigation/NavHost.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.navigation
+
+import android.support.annotation.IdRes
+
+/**
+ * Construct a new [NavGraph]
+ */
+inline fun NavHost.createGraph(
+ @IdRes id: Int = 0,
+ @IdRes startDestination: Int,
+ block: NavGraphBuilder.() -> Unit
+): NavGraph = navController.createGraph(id, startDestination, block)
diff --git a/navigation/runtime/ktx/src/main/java/androidx/navigation/View.kt b/navigation/runtime/ktx/src/main/java/androidx/navigation/View.kt
new file mode 100644
index 0000000..2f5cd6c
--- /dev/null
+++ b/navigation/runtime/ktx/src/main/java/androidx/navigation/View.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.navigation
+
+import android.view.View
+
+/**
+ * Find a [NavController] associated with a [View].
+ */
+fun View.findNavController(): NavController? =
+ Navigation.findNavController(this)
+
+/**
+ * Property for the [NavController] associated with a [View].
+ *
+ * Calling view.navController on a View not within a [NavHost] will result in an
+ * [IllegalStateException]
+ */
+var View.navController: NavController
+ get() = Navigation.getNavController(this)
+ set(value) {
+ Navigation.setViewNavController(this, value)
+ }
diff --git a/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java b/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
index 6e36f2a..f8f9092 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
@@ -58,6 +58,11 @@
}
@NonNull
+ Context getContext() {
+ return mContext;
+ }
+
+ @NonNull
@Override
public Destination createDestination() {
return new Destination(this);
diff --git a/navigation/runtime/src/main/java/androidx/navigation/Navigation.java b/navigation/runtime/src/main/java/androidx/navigation/Navigation.java
index b1e4fd9..f1e3683 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/Navigation.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/Navigation.java
@@ -55,6 +55,29 @@
}
/**
+ * Gets the {@link NavController} given the id of a View and its containing
+ * {@link Activity}. This is a convenience wrapper around {@link #getNavController(View)}.
+ *
+ * <p>This method will locate the {@link NavController} associated with this view.
+ * This is automatically populated for the id of a {@link NavHost} and its children.</p>
+ *
+ * @param activity The Activity hosting the view
+ * @param viewId The id of the view to search from
+ * @return the {@link NavController} associated with the view referenced by id
+ * @throws IllegalStateException if the given viewId does not correspond with a
+ * {@link NavHost} or is not within a NavHost.
+ */
+ @NonNull
+ public static NavController getNavController(@NonNull Activity activity, @IdRes int viewId) {
+ NavController navController = findNavController(activity, viewId);
+ if (navController == null) {
+ throw new IllegalStateException("Activity " + activity
+ + " does not have a NavController set on " + viewId);
+ }
+ return navController;
+ }
+
+ /**
* Find a {@link NavController} given a local {@link View}.
*
* <p>This method will locate the {@link NavController} associated with this view.
@@ -65,10 +88,8 @@
* @param view the view to search from
* @return the locally scoped {@link NavController} to the given view
*/
- public static NavController findNavController(@Nullable View view) {
- if (view == null) {
- return null;
- }
+ @Nullable
+ public static NavController findNavController(@NonNull View view) {
while (view != null) {
NavController controller = getViewNavController(view);
if (controller != null) {
@@ -81,6 +102,28 @@
}
/**
+ * Gets the {@link NavController} given a local {@link View}.
+ *
+ * <p>This method will locate the {@link NavController} associated with this view.
+ * This is automatically populated for views that are managed by a {@link NavHost}
+ * and is intended for use by various {@link android.view.View.OnClickListener listener}
+ * interfaces.</p>
+ *
+ * @param view the view to search from
+ * @return the locally scoped {@link NavController} to the given view
+ * @throws IllegalStateException if the given view does not correspond with a
+ * {@link NavHost} or is not within a NavHost.
+ */
+ @NonNull
+ public static NavController getNavController(@NonNull View view) {
+ NavController navController = findNavController(view);
+ if (navController == null) {
+ throw new IllegalStateException("View " + view + " does not have a NavController set");
+ }
+ return navController;
+ }
+
+ /**
* Create an {@link android.view.View.OnClickListener} for navigating
* to a destination. This supports both navigating via an
* {@link NavDestination#getAction(int) action} and directly navigating to a destination.
@@ -110,27 +153,20 @@
return new View.OnClickListener() {
@Override
public void onClick(View view) {
- final NavController controller = findNavController(view);
- if (controller != null) {
- controller.navigate(resId, args);
- } else {
- throw new IllegalStateException(
- "OnClickListener#onClick could not find NavController for view "
- + view);
- }
+ getNavController(view).navigate(resId, args);
}
};
}
/**
* Associates a NavController with the given View, allowing developers to use
- * {@link #findNavController(View)} and {@link #findNavController(Activity, int)} with that
+ * {@link #getNavController(View)} and {@link #getNavController(Activity, int)} with that
* View or any of its children to retrieve the NavController.
* <p>
* This is generally called for you by the hosting {@link NavHost}.
* @param view View that should be associated with the given NavController
* @param controller The controller you wish to later retrieve via
- * {@link #findNavController(View)}
+ * {@link #getNavController(View)}
*/
public static void setViewNavController(@NonNull View view,
@Nullable NavController controller) {
diff --git a/navigation/testing/ktx/build.gradle b/navigation/testing/ktx/build.gradle
new file mode 100644
index 0000000..9a96e77
--- /dev/null
+++ b/navigation/testing/ktx/build.gradle
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":navigation:testing"))
+ // Ensure that the -ktx dependency graph mirrors the Java dependency graph
+ api(project(":navigation:common-ktx"))
+ api(KOTLIN_STDLIB)
+
+ testImplementation(JUNIT)
+ testImplementation(TEST_RUNNER)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+}
+
+supportLibrary {
+ name = "Android Navigation Testing Kotlin Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.NAVIGATION
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2018"
+ description = "Android Navigation-Testing-Ktx"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
diff --git a/navigation/testing/ktx/src/androidTest/AndroidManifest.xml b/navigation/testing/ktx/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..d963bb6
--- /dev/null
+++ b/navigation/testing/ktx/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.navigation.testing.ktx.test">
+
+</manifest>
diff --git a/navigation/testing/ktx/src/androidTest/java/androidx/navigation/testing/TestNavigatorDestinationBuilderTest.kt b/navigation/testing/ktx/src/androidTest/java/androidx/navigation/testing/TestNavigatorDestinationBuilderTest.kt
new file mode 100644
index 0000000..20bba5a
--- /dev/null
+++ b/navigation/testing/ktx/src/androidTest/java/androidx/navigation/testing/TestNavigatorDestinationBuilderTest.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.navigation.testing
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.filters.SmallTest
+import android.support.test.runner.AndroidJUnit4
+import androidx.navigation.contains
+import androidx.navigation.get
+import androidx.navigation.navigation
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TestNavigatorDestinationBuilderTest {
+ private val provider = TestNavigatorProvider(InstrumentationRegistry.getTargetContext())
+
+ @Test
+ fun test() {
+ val graph = provider.navigation(startDestination = DESTINATION_ID) {
+ test(DESTINATION_ID)
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ }
+
+ @Test
+ fun testWithBody() {
+ val graph = provider.navigation(startDestination = DESTINATION_ID) {
+ test(DESTINATION_ID) {
+ label = LABEL
+ }
+ }
+ assertTrue("Destination should be added to the graph",
+ DESTINATION_ID in graph)
+ assertEquals("Destination should have label set",
+ LABEL, graph[DESTINATION_ID].label)
+ }
+}
+
+private const val DESTINATION_ID = 1
+private const val LABEL = "Test"
diff --git a/navigation/testing/ktx/src/main/AndroidManifest.xml b/navigation/testing/ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..35a5d64
--- /dev/null
+++ b/navigation/testing/ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.navigation.testing.ktx"/>
diff --git a/navigation/testing/ktx/src/main/java/androidx/navigation/testing/TestNavigator.kt b/navigation/testing/ktx/src/main/java/androidx/navigation/testing/TestNavigator.kt
new file mode 100644
index 0000000..fc46653
--- /dev/null
+++ b/navigation/testing/ktx/src/main/java/androidx/navigation/testing/TestNavigator.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.navigation.testing
+
+import android.os.Bundle
+
+/**
+ * Get the [TestNavigator] back stack as a [List] of [destination and argument pairs][Pair].
+ */
+val TestNavigator.backStack: List<Pair<TestNavigator.Destination, Bundle?>>
+ get() = mBackStack.map { Pair(it.first!!, it.second) }
diff --git a/navigation/testing/ktx/src/main/java/androidx/navigation/testing/TestNavigatorDestinationBuilder.kt b/navigation/testing/ktx/src/main/java/androidx/navigation/testing/TestNavigatorDestinationBuilder.kt
new file mode 100644
index 0000000..ce8ae41
--- /dev/null
+++ b/navigation/testing/ktx/src/main/java/androidx/navigation/testing/TestNavigatorDestinationBuilder.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+
+package androidx.navigation.testing
+
+import android.support.annotation.IdRes
+import androidx.navigation.NavDestinationBuilder
+import androidx.navigation.NavDestinationDsl
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.get
+
+/**
+ * Construct a new [TestNavigator.Destination]
+ */
+inline fun NavGraphBuilder.test(@IdRes id: Int) = test(id) {}
+
+/**
+ * Construct a new [TestNavigator.Destination]
+ */
+inline fun NavGraphBuilder.test(
+ @IdRes id: Int,
+ block: TestNavigatorDestinationBuilder.() -> Unit
+) = destination(TestNavigatorDestinationBuilder(provider[TestNavigator::class], id).apply(block))
+
+/**
+ * DSL for constructing a new [TestNavigator.Destination]
+ */
+@NavDestinationDsl
+class TestNavigatorDestinationBuilder(
+ navigator: TestNavigator,
+ @IdRes id: Int
+) : NavDestinationBuilder<TestNavigator.Destination>(navigator, id)
diff --git a/navigation/testing/ktx/src/test/java/androidx/navigation/testing/TestNavigatorTest.kt b/navigation/testing/ktx/src/test/java/androidx/navigation/testing/TestNavigatorTest.kt
new file mode 100644
index 0000000..80cdb4f
--- /dev/null
+++ b/navigation/testing/ktx/src/test/java/androidx/navigation/testing/TestNavigatorTest.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.navigation.testing
+
+import android.os.Bundle
+import android.support.test.filters.SmallTest
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+@SmallTest
+class TestNavigatorTest {
+
+ @Test
+ fun backStack() {
+ val testNavigator = TestNavigator()
+ val destination = testNavigator.createDestination()
+ val args = Bundle()
+ testNavigator.navigate(destination, args, null)
+ assertEquals("TestNavigator back stack size is 1 after navigate",
+ 1,
+ testNavigator.backStack.size)
+ val (foundDestination, foundArgs) = testNavigator.backStack.last()
+ assertEquals("last() returns last destination navigated to",
+ destination, foundDestination)
+ assertEquals("last() returns arguments Bundle",
+ args, foundArgs)
+ }
+}
diff --git a/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java b/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java
index 4ba5fd9..68a4994 100644
--- a/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java
+++ b/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java
@@ -20,12 +20,12 @@
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
-import java.util.ArrayDeque;
-
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
+import java.util.ArrayDeque;
+
/**
* A simple Navigator that doesn't actually navigate anywhere, but does dispatch correctly
*/
@@ -66,7 +66,10 @@
return popped;
}
- static class Destination extends NavDestination {
+ /**
+ * A simple Test destination
+ */
+ public static class Destination extends NavDestination {
/**
* NavDestinations should be created via {@link Navigator#createDestination}.
*/
diff --git a/navigation/ui/ktx/build.gradle b/navigation/ui/ktx/build.gradle
new file mode 100644
index 0000000..93d4966
--- /dev/null
+++ b/navigation/ui/ktx/build.gradle
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ buildTypes {
+ debug {
+ testCoverageEnabled = false // Breaks Kotlin compiler.
+ }
+ }
+}
+
+dependencies {
+ api(project(":navigation:ui"))
+ // Ensure that the -ktx dependency graph mirrors the Java dependency graph
+ api(project(":navigation:runtime-ktx"))
+ api(KOTLIN_STDLIB)
+ androidTestImplementation(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+}
+
+supportLibrary {
+ name = "Android Navigation UI Kotlin Extensions"
+ publish = true
+ mavenVersion = LibraryVersions.NAVIGATION
+ mavenGroup = LibraryGroups.NAVIGATION
+ inceptionYear = "2018"
+ description = "Android Navigation-UI-Ktx"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
diff --git a/navigation/ui/ktx/src/main/AndroidManifest.xml b/navigation/ui/ktx/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..120cc3f
--- /dev/null
+++ b/navigation/ui/ktx/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="androidx.navigation.ui.ktx"/>
diff --git a/navigation/ui/ktx/src/main/java/androidx/navigation/ui/Activity.kt b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/Activity.kt
new file mode 100644
index 0000000..6e530c6
--- /dev/null
+++ b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/Activity.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.navigation.ui
+
+import android.support.v4.widget.DrawerLayout
+import android.support.v7.app.AppCompatActivity
+import androidx.navigation.NavController
+
+/**
+ * Sets up the ActionBar returned by [AppCompatActivity.getSupportActionBar] for use
+ * with a [NavController].
+ *
+ * By calling this method, the title in the action bar will automatically be updated when
+ * the destination changes (assuming there is a valid
+ * [label][androidx.navigation.NavDestination.getLabel]).
+ *
+ * The action bar will also display the Up button when you are on a non-root destination and
+ * the drawer icon when on the root destination, automatically animating between them.
+ * Call [DrawerLayout.navigateUp] to handle the Up button.
+ *
+ * @param navController The NavController whose navigation actions will be reflected
+ * in the title of the action bar.
+ * @param drawerLayout The DrawerLayout that should be toggled from the home button
+ */
+fun AppCompatActivity.setupActionBarWithNavController(
+ navController: NavController,
+ drawerLayout: DrawerLayout? = null
+) {
+ NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
+}
diff --git a/navigation/ui/ktx/src/main/java/androidx/navigation/ui/BottomNavigationView.kt b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/BottomNavigationView.kt
new file mode 100644
index 0000000..a23285b
--- /dev/null
+++ b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/BottomNavigationView.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.navigation.ui
+
+import android.support.design.widget.BottomNavigationView
+import androidx.navigation.NavController
+
+/**
+ * Sets up a [BottomNavigationView] for use with a [NavController]. This will call
+ * [android.view.MenuItem.onNavDestinationSelected] when a menu item is selected.
+ *
+ * The selected item in the NavigationView will automatically be updated when the destination
+ * changes.
+ */
+fun BottomNavigationView.setupWithNavController(navController: NavController) {
+ NavigationUI.setupWithNavController(this, navController)
+}
diff --git a/navigation/ui/ktx/src/main/java/androidx/navigation/ui/DrawerLayout.kt b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/DrawerLayout.kt
new file mode 100644
index 0000000..c87acdc
--- /dev/null
+++ b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/DrawerLayout.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.navigation.ui
+
+import android.support.v4.widget.DrawerLayout
+import androidx.navigation.NavController
+
+/**
+ * Handles the Up button by delegating its behavior to the given [NavController].
+ *
+ * This is equivalent to calling [NavController.navigateUp] if the [DrawerLayout] is null.
+ *
+ * @return True if the [NavController] was able to navigate up.
+ */
+fun DrawerLayout?.navigateUp(navController: NavController): Boolean =
+ NavigationUI.navigateUp(this, navController)
diff --git a/navigation/ui/ktx/src/main/java/androidx/navigation/ui/MenuItem.kt b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/MenuItem.kt
new file mode 100644
index 0000000..a989ee3
--- /dev/null
+++ b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/MenuItem.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.navigation.ui
+
+import android.view.MenuItem
+import androidx.navigation.NavController
+
+/**
+ * Attempt to navigate to the [NavDestination] associated with this [MenuItem].
+ *
+ * Importantly, it assumes the [menu item id][getItemId] matches a valid
+ * [action id][androidx.navigation.NavDestination.getAction] or
+ * [destination id][androidx.navigation.NavDestination.getId] to be navigated to.
+ *
+ * @return True if the [NavController] was able to navigate to the destination.
+ */
+fun MenuItem.onNavDestinationSelected(navController: NavController): Boolean =
+ NavigationUI.onNavDestinationSelected(this, navController)
diff --git a/navigation/ui/ktx/src/main/java/androidx/navigation/ui/NavigationView.kt b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/NavigationView.kt
new file mode 100644
index 0000000..71e1db5
--- /dev/null
+++ b/navigation/ui/ktx/src/main/java/androidx/navigation/ui/NavigationView.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.navigation.ui
+
+import android.support.design.widget.NavigationView
+import androidx.navigation.NavController
+
+/**
+ * Sets up a [NavigationView] for use with a [NavController]. This will call
+ * [android.view.MenuItem.onNavDestinationSelected] when a menu item is selected.
+ *
+ * The selected item in the NavigationView will automatically be updated when the destination
+ * changes.
+ */
+fun NavigationView.setupWithNavController(navController: NavController) {
+ NavigationUI.setupWithNavController(this, navController)
+}
diff --git a/paging/common/build.gradle b/paging/common/build.gradle
index dbe6b06..cc3b185 100644
--- a/paging/common/build.gradle
+++ b/paging/common/build.gradle
@@ -41,4 +41,4 @@
inceptionYear = "2017"
description = "Android Paging-Common"
url = SupportLibraryExtension.ARCHITECTURE_URL
-}
\ No newline at end of file
+}
diff --git a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
index 1fe6725..e5cd46c 100644
--- a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
@@ -459,13 +459,34 @@
@SuppressWarnings("deprecation")
static class ContiguousWithoutPlaceholdersWrapper<Value>
extends ContiguousDataSource<Integer, Value> {
-
@NonNull
- final PositionalDataSource<Value> mPositionalDataSource;
+ final PositionalDataSource<Value> mSource;
ContiguousWithoutPlaceholdersWrapper(
- @NonNull PositionalDataSource<Value> positionalDataSource) {
- mPositionalDataSource = positionalDataSource;
+ @NonNull PositionalDataSource<Value> source) {
+ mSource = source;
+ }
+
+ @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();
}
@NonNull
@@ -493,7 +514,7 @@
// Note enablePlaceholders will be false here, but we don't have a way to communicate
// this to PositionalDataSource. This is fine, because only the list and its position
// offset will be consumed by the LoadInitialCallback.
- mPositionalDataSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
+ mSource.dispatchLoadInitial(false, convertPosition, initialLoadSize,
pageSize, mainThreadExecutor, receiver);
}
@@ -502,7 +523,7 @@
@NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
int startIndex = currentEndIndex + 1;
- mPositionalDataSource.dispatchLoadRange(
+ mSource.dispatchLoadRange(
PageResult.APPEND, startIndex, pageSize, mainThreadExecutor, receiver);
}
@@ -514,12 +535,12 @@
int startIndex = currentBeginIndex - 1;
if (startIndex < 0) {
// trigger empty list load
- mPositionalDataSource.dispatchLoadRange(
+ mSource.dispatchLoadRange(
PageResult.PREPEND, startIndex, 0, mainThreadExecutor, receiver);
} else {
int loadSize = Math.min(pageSize, startIndex + 1);
startIndex = startIndex - loadSize + 1;
- mPositionalDataSource.dispatchLoadRange(
+ mSource.dispatchLoadRange(
PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver);
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java
index 93520ca..6d112a8 100644
--- a/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/WrapperItemKeyedDataSource.java
@@ -25,13 +25,6 @@
class WrapperItemKeyedDataSource<K, A, B> extends ItemKeyedDataSource<K, B> {
private final ItemKeyedDataSource<K, A> mSource;
private final Function<List<A>, List<B>> mListFunction;
- private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
- @Override
- public void onInvalidated() {
- invalidate();
- removeCallback();
- }
- };
private final IdentityHashMap<B, K> mKeyMap = new IdentityHashMap<>();
@@ -39,11 +32,26 @@
Function<List<A>, List<B>> listFunction) {
mSource = source;
mListFunction = listFunction;
- mSource.addInvalidatedCallback(mInvalidatedCallback);
}
- private void removeCallback() {
- mSource.removeInvalidatedCallback(mInvalidatedCallback);
+ @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();
}
private List<B> convertWithStashedKeys(List<A> source) {
diff --git a/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java
index e6fa274..003e154 100644
--- a/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/WrapperPageKeyedDataSource.java
@@ -25,23 +25,31 @@
class WrapperPageKeyedDataSource<K, A, B> extends PageKeyedDataSource<K, B> {
private final PageKeyedDataSource<K, A> mSource;
private final Function<List<A>, List<B>> mListFunction;
- private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
- @Override
- public void onInvalidated() {
- invalidate();
- removeCallback();
- }
- };
WrapperPageKeyedDataSource(PageKeyedDataSource<K, A> source,
Function<List<A>, List<B>> listFunction) {
mSource = source;
mListFunction = listFunction;
- mSource.addInvalidatedCallback(mInvalidatedCallback);
}
- private void removeCallback() {
- mSource.removeInvalidatedCallback(mInvalidatedCallback);
+ @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
diff --git a/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java
index 0626f87..1478c87 100644
--- a/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/WrapperPositionalDataSource.java
@@ -25,23 +25,30 @@
private final PositionalDataSource<A> mSource;
private final Function<List<A>, List<B>> mListFunction;
- private final InvalidatedCallback mInvalidatedCallback = new DataSource.InvalidatedCallback() {
- @Override
- public void onInvalidated() {
- invalidate();
- removeCallback();
- }
- };
-
WrapperPositionalDataSource(PositionalDataSource<A> source,
Function<List<A>, List<B>> listFunction) {
mSource = source;
mListFunction = listFunction;
- mSource.addInvalidatedCallback(mInvalidatedCallback);
}
- private void removeCallback() {
- mSource.removeInvalidatedCallback(mInvalidatedCallback);
+ @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
diff --git a/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
index 060094a..6550c50 100644
--- a/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/ItemKeyedDataSourceTest.kt
@@ -337,17 +337,20 @@
private abstract class WrapperDataSource<K, A, B>(private val source: ItemKeyedDataSource<K, A>)
: ItemKeyedDataSource<K, B>() {
- private val invalidatedCallback = DataSource.InvalidatedCallback {
- invalidate()
- removeCallback()
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.addInvalidatedCallback(onInvalidatedCallback)
}
- init {
- source.addInvalidatedCallback(invalidatedCallback)
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.removeInvalidatedCallback(onInvalidatedCallback)
}
- private fun removeCallback() {
- removeInvalidatedCallback(invalidatedCallback)
+ override fun invalidate() {
+ source.invalidate()
+ }
+
+ override fun isInvalid(): Boolean {
+ return source.isInvalid
}
override fun loadInitial(params: LoadInitialParams<K>, callback: LoadInitialCallback<B>) {
@@ -399,7 +402,7 @@
// verify that it's possible to wrap an ItemKeyedDataSource, and add info to its data
val orig = ItemDataSource(items = ITEMS_BY_NAME_ID)
- val wrapper = DecoratedWrapperDataSource(orig)
+ val wrapper = createWrapper(orig)
// load initial
@Suppress("UNCHECKED_CAST")
@@ -425,6 +428,10 @@
wrapper.loadBefore(ItemKeyedDataSource.LoadParams(key, 10), loadCallback)
verify(loadCallback).onResult(ITEMS_BY_NAME_ID.subList(10, 20).map { DecoratedItem(it) })
verifyNoMoreInteractions(loadCallback)
+
+ // verify invalidation
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
}
@Test
@@ -442,6 +449,24 @@
it.map { DecoratedItem(it) }
}
+ @Test
+ fun testInvalidateToWrapper() {
+ val orig = ItemDataSource()
+ val wrapper = orig.map { DecoratedItem(it) }
+
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
+ }
+
+ @Test
+ fun testInvalidateFromWrapper() {
+ val orig = ItemDataSource()
+ val wrapper = orig.map { DecoratedItem(it) }
+
+ wrapper.invalidate()
+ assertTrue(orig.isInvalid)
+ }
+
companion object {
private val ITEM_COMPARATOR = compareBy<Item>({ it.name }).thenByDescending({ it.id })
private val KEY_COMPARATOR = compareBy<Key>({ it.name }).thenByDescending({ it.id })
diff --git a/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
index f7ea4b6..7c8667c 100644
--- a/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/PageKeyedDataSourceTest.kt
@@ -17,6 +17,7 @@
package android.arch.paging
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
@@ -160,17 +161,20 @@
private abstract class WrapperDataSource<K, A, B>(private val source: PageKeyedDataSource<K, A>)
: PageKeyedDataSource<K, B>() {
- private val invalidatedCallback = DataSource.InvalidatedCallback {
- invalidate()
- removeCallback()
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.addInvalidatedCallback(onInvalidatedCallback)
}
- init {
- source.addInvalidatedCallback(invalidatedCallback)
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.removeInvalidatedCallback(onInvalidatedCallback)
}
- private fun removeCallback() {
- removeInvalidatedCallback(invalidatedCallback)
+ override fun invalidate() {
+ source.invalidate()
+ }
+
+ override fun isInvalid(): Boolean {
+ return source.isInvalid
}
override fun loadInitial(params: LoadInitialParams<K>,
@@ -247,6 +251,10 @@
verify(loadCallback).onResult(expectedInitial.data.map { it.toString() },
expectedInitial.prev)
verifyNoMoreInteractions(loadCallback)
+
+ // verify invalidation
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
}
@Test
@@ -258,11 +266,30 @@
fun testListConverterWrappedDataSource() = verifyWrappedDataSource {
it.mapByPage { it.map { it.toString() } }
}
+
@Test
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
it.map { it.toString() }
}
+ @Test
+ fun testInvalidateToWrapper() {
+ val orig = ItemDataSource()
+ val wrapper = orig.map { it.toString() }
+
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
+ }
+
+ @Test
+ fun testInvalidateFromWrapper() {
+ val orig = ItemDataSource()
+ val wrapper = orig.map { it.toString() }
+
+ wrapper.invalidate()
+ assertTrue(orig.isInvalid)
+ }
+
companion object {
// first load is 2nd page to ensure we test prepend as well as append behavior
private val INIT_KEY: String = "key 2"
diff --git a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
index eab778c..2b3c770 100644
--- a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
@@ -17,6 +17,7 @@
package android.arch.paging
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
@@ -219,17 +220,20 @@
private abstract class WrapperDataSource<in A, B>(private val source: PositionalDataSource<A>)
: PositionalDataSource<B>() {
- private val invalidatedCallback = DataSource.InvalidatedCallback {
- invalidate()
- removeCallback()
+ override fun addInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.addInvalidatedCallback(onInvalidatedCallback)
}
- init {
- source.addInvalidatedCallback(invalidatedCallback)
+ override fun removeInvalidatedCallback(onInvalidatedCallback: InvalidatedCallback) {
+ source.removeInvalidatedCallback(onInvalidatedCallback)
}
- private fun removeCallback() {
- removeInvalidatedCallback(invalidatedCallback)
+ override fun invalidate() {
+ source.invalidate()
+ }
+
+ override fun isInvalid(): Boolean {
+ return source.isInvalid
}
override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<B>) {
@@ -292,6 +296,10 @@
orig.invalidate()
verify(invalCallback).onInvalidated()
verifyNoMoreInteractions(invalCallback)
+
+ // verify invalidation
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
}
@Test
@@ -308,4 +316,40 @@
fun testItemConverterWrappedDataSource() = verifyWrappedDataSource {
it.map { it.toString() }
}
+
+ @Test
+ fun testInvalidateToWrapper() {
+ val orig = ListDataSource(listOf(0, 1, 2))
+ val wrapper = orig.map { it.toString() }
+
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
+ }
+
+ @Test
+ fun testInvalidateFromWrapper() {
+ val orig = ListDataSource(listOf(0, 1, 2))
+ val wrapper = orig.map { it.toString() }
+
+ wrapper.invalidate()
+ assertTrue(orig.isInvalid)
+ }
+
+ @Test
+ fun testInvalidateToWrapper_contiguous() {
+ val orig = ListDataSource(listOf(0, 1, 2))
+ val wrapper = orig.wrapAsContiguousWithoutPlaceholders()
+
+ orig.invalidate()
+ assertTrue(wrapper.isInvalid)
+ }
+
+ @Test
+ fun testInvalidateFromWrapper_contiguous() {
+ val orig = ListDataSource(listOf(0, 1, 2))
+ val wrapper = orig.wrapAsContiguousWithoutPlaceholders()
+
+ wrapper.invalidate()
+ assertTrue(orig.isInvalid)
+ }
}
diff --git a/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java b/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
index b82e1f9..7fd6621 100644
--- a/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
+++ b/paging/runtime/src/main/java/android/arch/paging/LivePagedListBuilder.java
@@ -30,7 +30,7 @@
* {@link 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.
+ * 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.
diff --git a/paging/rxjava2/build.gradle b/paging/rxjava2/build.gradle
new file mode 100644
index 0000000..f96bc70
--- /dev/null
+++ b/paging/rxjava2/build.gradle
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import static android.support.dependencies.DependenciesKt.*
+import android.support.LibraryGroups
+import android.support.LibraryVersions
+import android.support.SupportLibraryExtension
+
+plugins {
+ id("SupportAndroidLibraryPlugin")
+ id("kotlin-android")
+}
+
+dependencies {
+ api(project(":arch:runtime"))
+ api(project(":paging:common"))
+
+ api(RX_JAVA)
+ androidTestImplementation(JUNIT)
+ 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(TEST_RUNNER)
+ androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(KOTLIN_STDLIB)
+}
+
+supportLibrary {
+ name = "Android Paging RXJava2"
+ publish = true
+ mavenVersion = LibraryVersions.PAGING_RX
+ mavenGroup = LibraryGroups.PAGING
+ inceptionYear = "2018"
+ description = "Android Paging RXJava2"
+ url = SupportLibraryExtension.ARCHITECTURE_URL
+}
diff --git a/paging/rxjava2/src/androidTest/java/android/arch/paging/RxPagedListBuilderTest.kt b/paging/rxjava2/src/androidTest/java/android/arch/paging/RxPagedListBuilderTest.kt
new file mode 100644
index 0000000..33598fc
--- /dev/null
+++ b/paging/rxjava2/src/androidTest/java/android/arch/paging/RxPagedListBuilderTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.paging
+
+import android.support.test.filters.SmallTest
+import io.reactivex.Observable
+import io.reactivex.observers.TestObserver
+import io.reactivex.schedulers.TestScheduler
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RxPagedListBuilderTest {
+
+ /**
+ * Creates a data source that will sequentially supply the passed lists
+ */
+ private fun testDataSourceSequence(data: List<List<String>>): DataSource.Factory<Int, String> {
+ return object : DataSource.Factory<Int, String>() {
+ var localData = data
+ override fun create(): DataSource<Int, String> {
+ val currentList = localData.first()
+ localData = localData.drop(1)
+ return ListDataSource<String>(currentList)
+ }
+ }
+ }
+
+ @Test
+ fun basic() {
+ val factory = testDataSourceSequence(listOf(listOf("a", "b"), listOf("c", "d")))
+ val scheduler = TestScheduler()
+ val observable = RxPagedListBuilder(factory, 10)
+ .setFetchScheduler(scheduler)
+ .setNotifyScheduler(scheduler)
+ .buildObservable()
+ val observer = TestObserver<PagedList<String>>()
+
+ observable.subscribe(observer)
+
+ // initial state
+ observer.assertNotComplete()
+ observer.assertValueCount(0)
+
+ // load first item
+ scheduler.triggerActions()
+ observer.assertValueCount(1)
+ assertEquals(listOf("a", "b"), observer.values().first())
+
+ // invalidate triggers second load
+ observer.values().first().dataSource.invalidate()
+ scheduler.triggerActions()
+ observer.assertValueCount(2)
+ assertEquals(listOf("c", "d"), observer.values().last())
+ }
+
+ @Test
+ fun checkSchedulers() {
+ val factory = testDataSourceSequence(listOf(listOf("a", "b"), listOf("c", "d")))
+ val notifyScheduler = TestScheduler()
+ val fetchScheduler = TestScheduler()
+ val observable: Observable<PagedList<String>> = RxPagedListBuilder(
+ factory, 10)
+ .setFetchScheduler(fetchScheduler)
+ .setNotifyScheduler(notifyScheduler)
+ .buildObservable()
+ val observer = TestObserver<PagedList<String>>()
+ observable.subscribe(observer)
+
+ // notify has nothing to do
+ notifyScheduler.triggerActions()
+ observer.assertValueCount(0)
+
+ // fetch creates list, but observer doesn't see
+ fetchScheduler.triggerActions()
+ observer.assertValueCount(0)
+
+ // now notify reveals item
+ notifyScheduler.triggerActions()
+ observer.assertValueCount(1)
+ }
+}
diff --git a/paging/rxjava2/src/main/AndroidManifest.xml b/paging/rxjava2/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6329afe
--- /dev/null
+++ b/paging/rxjava2/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.arch.paging.rxjava2">
+</manifest>
diff --git a/paging/rxjava2/src/main/java/android/arch/paging/RxPagedListBuilder.java b/paging/rxjava2/src/main/java/android/arch/paging/RxPagedListBuilder.java
new file mode 100644
index 0000000..aa7fd33
--- /dev/null
+++ b/paging/rxjava2/src/main/java/android/arch/paging/RxPagedListBuilder.java
@@ -0,0 +1,330 @@
+/*
+ * 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 android.arch.paging;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.concurrent.Executor;
+
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+import io.reactivex.Observable;
+import io.reactivex.ObservableEmitter;
+import io.reactivex.ObservableOnSubscribe;
+import io.reactivex.Scheduler;
+import io.reactivex.functions.Cancellable;
+import io.reactivex.schedulers.Schedulers;
+
+/**
+ * Builder for {@code Observable<PagedList>} or {@code Flowable<PagedList>}, given a
+ * {@link DataSource.Factory} and a {@link 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).
+ * <p>
+ * The returned observable/flowable will already be subscribed on the
+ * {@link #setFetchScheduler(Scheduler)}, and will perform all loading on that scheduler. It will
+ * already be observed on {@link #setNotifyScheduler(Scheduler)}, and will dispatch new PagedLists,
+ * as well as their updates to that scheduler.
+ *
+ * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
+ * you're using PositionalDataSource.
+ * @param <Value> Item type being presented.
+ */
+public final class RxPagedListBuilder<Key, Value> {
+ private Key mInitialLoadKey;
+ private PagedList.Config mConfig;
+ private DataSource.Factory<Key, Value> mDataSourceFactory;
+ private PagedList.BoundaryCallback mBoundaryCallback;
+ private Executor mNotifyExecutor;
+ private Executor mFetchExecutor;
+ private Scheduler mFetchScheduler;
+ private Scheduler mNotifyScheduler;
+
+ /**
+ * Creates a RxPagedListBuilder with required parameters.
+ *
+ * @param dataSourceFactory DataSource factory providing DataSource generations.
+ * @param config Paging configuration.
+ */
+ public RxPagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ @NonNull PagedList.Config config) {
+ //noinspection ConstantConditions
+ if (config == null) {
+ throw new IllegalArgumentException("PagedList.Config must be provided");
+ }
+ //noinspection ConstantConditions
+ if (dataSourceFactory == null) {
+ throw new IllegalArgumentException("DataSource.Factory must be provided");
+ }
+ mDataSourceFactory = dataSourceFactory;
+ mConfig = config;
+ }
+
+ /**
+ * Creates a RxPagedListBuilder with required parameters.
+ * <p>
+ * This method is a convenience for:
+ * <pre>
+ * RxPagedListBuilder(dataSourceFactory,
+ * new PagedList.Config.Builder().setPageSize(pageSize).build())
+ * </pre>
+ *
+ * @param dataSourceFactory DataSource.Factory providing DataSource generations.
+ * @param pageSize Size of pages to load.
+ */
+ @SuppressWarnings("unused")
+ public RxPagedListBuilder(@NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ int pageSize) {
+ this(dataSourceFactory, new PagedList.Config.Builder().setPageSize(pageSize).build());
+ }
+
+ /**
+ * First loading key passed to the first PagedList/DataSource.
+ * <p>
+ * When a new PagedList/DataSource pair is created after the first, it acquires a load key from
+ * the previous generation so that data is loaded around the position already being observed.
+ *
+ * @param key Initial load key passed to the first PagedList/DataSource.
+ * @return this
+ */
+ @SuppressWarnings("unused")
+ @NonNull
+ public RxPagedListBuilder<Key, Value> setInitialLoadKey(@Nullable Key key) {
+ mInitialLoadKey = key;
+ return this;
+ }
+
+ /**
+ * Sets a {@link 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
+ * DataSource runs out of data to provide to its PagedList.
+ * <p>
+ * If you are paging from a DataSource.Factory backed by local storage, you can set a
+ * BoundaryCallback to know when there is no more information to page from local storage.
+ * This is useful to page from the network when local storage is a cache of network data.
+ * <p>
+ * Note that when using a BoundaryCallback with a {@code Observable<PagedList>}, method calls
+ * on the callback may be dispatched multiple times - one for each PagedList/DataSource
+ * pair. If loading network data from a BoundaryCallback, you should prevent multiple
+ * dispatches of the same method from triggering multiple simultaneous network loads.
+ *
+ * @param boundaryCallback The boundary callback for listening to PagedList load state.
+ * @return this
+ */
+ @SuppressWarnings("unused")
+ @NonNull
+ public RxPagedListBuilder<Key, Value> setBoundaryCallback(
+ @Nullable PagedList.BoundaryCallback<Value> boundaryCallback) {
+ mBoundaryCallback = boundaryCallback;
+ return this;
+ }
+
+ /**
+ * Sets scheduler which will be used for observing new PagedLists, as well as loading updates
+ * within the PagedLists.
+ * <p>
+ * The built observable will be {@link Observable#observeOn(Scheduler) observed on} this
+ * scheduler, so that the thread receiving PagedLists will also receive the internal updates to
+ * the PagedList.
+ *
+ * @param scheduler Scheduler for background DataSource loading.
+ * @return this
+ */
+ public RxPagedListBuilder<Key, Value> setNotifyScheduler(
+ final @NonNull Scheduler scheduler) {
+ mNotifyScheduler = scheduler;
+ final Scheduler.Worker worker = scheduler.createWorker();
+ mNotifyExecutor = new Executor() {
+ @Override
+ public void execute(@NonNull Runnable command) {
+ // We use a worker here since the page load notifications
+ // should not be dispatched in parallel
+ worker.schedule(command);
+ }
+ };
+ return this;
+ }
+
+ /**
+ * Sets scheduler which will be used for background fetching of PagedLists, as well as on-demand
+ * fetching of pages inside.
+ *
+ * @param scheduler Scheduler for background DataSource loading.
+ * @return this
+ */
+ @SuppressWarnings({"unused", "WeakerAccess"})
+ @NonNull
+ public RxPagedListBuilder<Key, Value> setFetchScheduler(
+ final @NonNull Scheduler scheduler) {
+ mFetchExecutor = new Executor() {
+ @Override
+ public void execute(@NonNull Runnable command) {
+ // We use scheduleDirect since the page loads that use
+ // executor are intentionally parallel.
+ scheduler.scheduleDirect(command);
+ }
+ };
+ mFetchScheduler = scheduler;
+ return this;
+ }
+
+ /**
+ * Constructs a {@code Observable<PagedList>}.
+ * <p>
+ * The returned Observable will already be observed on the
+ * {@link #setNotifyScheduler(Scheduler) notify scheduler}, and subscribed on the
+ * {@link #setFetchScheduler(Scheduler) fetch scheduler}.
+ *
+ * @return The Observable of PagedLists
+ */
+ @NonNull
+ public Observable<PagedList<Value>> buildObservable() {
+ if (mNotifyExecutor == null) {
+ mNotifyExecutor = ArchTaskExecutor.getMainThreadExecutor();
+ mNotifyScheduler = Schedulers.from(mNotifyExecutor);
+ }
+ if (mFetchExecutor == null) {
+ mFetchExecutor = ArchTaskExecutor.getIOThreadExecutor();
+ mFetchScheduler = Schedulers.from(mFetchExecutor);
+ }
+ return Observable.create(new PagingObservableOnSubscribe<>(
+ mInitialLoadKey,
+ mConfig,
+ mBoundaryCallback,
+ mDataSourceFactory,
+ mNotifyExecutor,
+ mFetchExecutor))
+ .observeOn(mNotifyScheduler)
+ .subscribeOn(mFetchScheduler);
+ }
+
+ /**
+ * Constructs a {@code Flowable<PagedList>}.
+ *
+ * The returned Observable will already be observed on the
+ * {@link #setNotifyScheduler(Scheduler) notify scheduler}, and subscribed on the
+ * {@link #setFetchScheduler(Scheduler) fetch scheduler}.
+ *
+ * @param backpressureStrategy BackpressureStrategy for the Flowable to use.
+ * @return The Flowable of PagedLists
+ */
+ @NonNull
+ public Flowable<PagedList<Value>> buildFlowable(BackpressureStrategy backpressureStrategy) {
+ return buildObservable()
+ .toFlowable(backpressureStrategy);
+ }
+
+ static class PagingObservableOnSubscribe<Key, Value>
+ implements ObservableOnSubscribe<PagedList<Value>>, DataSource.InvalidatedCallback,
+ Cancellable,
+ Runnable {
+
+ @Nullable
+ private final Key mInitialLoadKey;
+ @NonNull
+ private final PagedList.Config mConfig;
+ @Nullable
+ private final PagedList.BoundaryCallback mBoundaryCallback;
+ @NonNull
+ private final DataSource.Factory<Key, Value> mDataSourceFactory;
+ @NonNull
+ private final Executor mNotifyExecutor;
+ @NonNull
+ private final Executor mFetchExecutor;
+
+ @Nullable
+ private PagedList<Value> mList;
+ @Nullable
+ private DataSource<Key, Value> mDataSource;
+
+ private ObservableEmitter<PagedList<Value>> mEmitter;
+
+ private PagingObservableOnSubscribe(@Nullable Key initialLoadKey,
+ @NonNull PagedList.Config config,
+ @Nullable PagedList.BoundaryCallback boundaryCallback,
+ @NonNull DataSource.Factory<Key, Value> dataSourceFactory,
+ @NonNull Executor notifyExecutor,
+ @NonNull Executor fetchExecutor) {
+ mInitialLoadKey = initialLoadKey;
+ mConfig = config;
+ mBoundaryCallback = boundaryCallback;
+ mDataSourceFactory = dataSourceFactory;
+ mNotifyExecutor = notifyExecutor;
+ mFetchExecutor = fetchExecutor;
+ }
+
+ @Override
+ public void subscribe(ObservableEmitter<PagedList<Value>> emitter)
+ throws Exception {
+ mEmitter = emitter;
+ mEmitter.setCancellable(this);
+
+ // known that subscribe is already on fetchScheduler
+ mEmitter.onNext(createPagedList());
+ }
+
+ @Override
+ public void cancel() throws Exception {
+ if (mDataSource != null) {
+ mDataSource.removeInvalidatedCallback(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ // fetch data, run on fetchExecutor
+ mEmitter.onNext(createPagedList());
+ }
+
+ @Override
+ public void onInvalidated() {
+ if (!mEmitter.isDisposed()) {
+ mFetchExecutor.execute(this);
+ }
+ }
+
+ private PagedList<Value> createPagedList() {
+ @Nullable Key initializeKey = mInitialLoadKey;
+ if (mList != null) {
+ //noinspection unchecked
+ initializeKey = (Key) mList.getLastKey();
+ }
+
+ do {
+ if (mDataSource != null) {
+ mDataSource.removeInvalidatedCallback(this);
+ }
+ mDataSource = mDataSourceFactory.create();
+ mDataSource.addInvalidatedCallback(this);
+
+ mList = new PagedList.Builder<>(mDataSource, mConfig)
+ .setNotifyExecutor(mNotifyExecutor)
+ .setFetchExecutor(mFetchExecutor)
+ .setBoundaryCallback(mBoundaryCallback)
+ .setInitialKey(initializeKey)
+ .build();
+ } while (mList.isDetached());
+ return mList;
+ }
+ }
+}
diff --git a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
index 0d99743..e64b50d 100644
--- a/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
+++ b/room/compiler/src/main/kotlin/android/arch/persistence/room/writer/DatabaseWriter.kt
@@ -59,6 +59,7 @@
private fun createClearAllTables(): MethodSpec {
val scope = CodeGenScope(this)
return MethodSpec.methodBuilder("clearAllTables").apply {
+ addStatement("super.assertNotMainThread()")
val dbVar = scope.getTmpVar("_db")
addStatement("final $T $L = super.getOpenHelper().getWritableDatabase()",
SupportDbTypeNames.DB, dbVar)
@@ -98,7 +99,10 @@
endControlFlow()
}
addStatement("$L.query($S).close()", dbVar, "PRAGMA wal_checkpoint(FULL)")
- addStatement("$L.execSQL($S)", dbVar, "VACUUM")
+ beginControlFlow("if (!$L.inTransaction())", dbVar).apply {
+ addStatement("$L.execSQL($S)", dbVar, "VACUUM")
+ }
+ endControlFlow()
}
endControlFlow()
}.build()
diff --git a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
index e03a0e1..9d9dbf3 100644
--- a/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
+++ b/room/compiler/src/test/data/databasewriter/output/ComplexDatabase.java
@@ -93,6 +93,7 @@
@Override
public void clearAllTables() {
+ super.assertNotMainThread();
final SupportSQLiteDatabase _db = super.getOpenHelper().getWritableDatabase();
try {
super.beginTransaction();
@@ -101,7 +102,9 @@
} finally {
super.endTransaction();
_db.query("PRAGMA wal_checkpoint(FULL)").close();
- _db.execSQL("VACUUM");
+ if (!_db.inTransaction()) {
+ _db.execSQL("VACUUM");
+ }
}
}
diff --git a/room/integration-tests/testapp/build.gradle b/room/integration-tests/testapp/build.gradle
index 45ec9c6..47564dd 100644
--- a/room/integration-tests/testapp/build.gradle
+++ b/room/integration-tests/testapp/build.gradle
@@ -66,6 +66,7 @@
androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has it's own MockMaker
testImplementation(JUNIT)
+ compile project(path: ':paging:rxjava2')
}
tasks['check'].dependsOn(tasks['connectedCheck'])
diff --git a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java
index 0eff707..5b5b9e7 100644
--- a/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/android/arch/persistence/room/integration/testapp/test/ClearAllTablesTest.java
@@ -16,11 +16,13 @@
package android.arch.persistence.room.integration.testapp.test;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Database;
@@ -132,6 +134,29 @@
@Test
@SmallTest
+ public void inTransaction() {
+ mDao.insertParent(new Parent(1, "A"));
+ assertThat(mDao.countParent(), is(1));
+ // Running clearAllTables in a transaction is not recommended, but we should not crash.
+ mDatabase.runInTransaction(() -> mDatabase.clearAllTables());
+ assertThat(mDao.countParent(), is(0));
+ }
+
+ @Test
+ @SmallTest
+ public void inMainThread() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ try {
+ mDatabase.clearAllTables();
+ fail("Was expecting an exception");
+ } catch (IllegalStateException e) {
+ assertThat(e.getMessage(), containsString("main thread"));
+ }
+ });
+ }
+
+ @Test
+ @SmallTest
public void foreignKey() {
mDao.insertParent(new Parent(1, "A"));
mDao.insertChild(new Child(1, "a", 1));
@@ -186,12 +211,12 @@
db.dao().insertParent(new Parent(1, uuid));
assertThat(queryEncoding(db), is(equalTo("UTF-8")));
db.close();
- assertThat(containsString(file, uuid), is(true));
+ assertThat(fileContainsString(file, uuid), is(true));
db = Room.databaseBuilder(context, ClearAllTablesDatabase.class, dbName)
.setJournalMode(journalMode).build();
db.clearAllTables();
db.close();
- assertThat(containsString(file, uuid), is(false));
+ assertThat(fileContainsString(file, uuid), is(false));
}
private String queryEncoding(RoomDatabase db) {
@@ -207,7 +232,7 @@
}
}
- private boolean containsString(File file, String s) throws IOException {
+ private boolean fileContainsString(File file, String s) throws IOException {
final byte[] content = new byte[(int) file.length()];
final FileInputStream stream = new FileInputStream(file);
//noinspection TryFinallyCanBeTryWithResources
diff --git a/room/integration-tests/testapp/src/main/AndroidManifest.xml b/room/integration-tests/testapp/src/main/AndroidManifest.xml
index 02c1975..0bb827f 100644
--- a/room/integration-tests/testapp/src/main/AndroidManifest.xml
+++ b/room/integration-tests/testapp/src/main/AndroidManifest.xml
@@ -37,5 +37,14 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <activity
+ android:name=".RoomPagedListRxActivity"
+ android:label="PagedList Observable"
+ android:theme="@style/Theme.AppCompat">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
- </manifest>
+</manifest>
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
index d8cd8d4..762e0a2 100644
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/CustomerViewModel.java
@@ -23,6 +23,7 @@
import android.arch.paging.DataSource;
import android.arch.paging.LivePagedListBuilder;
import android.arch.paging.PagedList;
+import android.arch.paging.RxPagedListBuilder;
import android.arch.persistence.room.Room;
import android.arch.persistence.room.integration.testapp.database.Customer;
import android.arch.persistence.room.integration.testapp.database.LastNameAscCustomerDataSource;
@@ -31,6 +32,9 @@
import java.util.UUID;
+import io.reactivex.BackpressureStrategy;
+import io.reactivex.Flowable;
+
/**
* Sample database-backed view model of Customers
*/
@@ -47,20 +51,17 @@
mDatabase = Room.databaseBuilder(this.getApplication(),
SampleDatabase.class, "customerDatabase").build();
- ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
- @Override
- public void run() {
- // fill with some simple data
- int customerCount = mDatabase.getCustomerDao().countCustomers();
- if (customerCount == 0) {
- Customer[] initialCustomers = new Customer[10];
- for (int i = 0; i < 10; i++) {
- initialCustomers[i] = createCustomer();
- }
- mDatabase.getCustomerDao().insertAll(initialCustomers);
+ ArchTaskExecutor.getInstance().executeOnDiskIO(() -> {
+ // fill with some simple data
+ int customerCount = mDatabase.getCustomerDao().countCustomers();
+ if (customerCount == 0) {
+ Customer[] initialCustomers = new Customer[10];
+ for (int i = 0; i < 10; i++) {
+ initialCustomers[i] = createCustomer();
}
-
+ mDatabase.getCustomerDao().insertAll(initialCustomers);
}
+
});
}
@@ -73,12 +74,13 @@
}
void insertCustomer() {
- ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
- @Override
- public void run() {
- mDatabase.getCustomerDao().insert(createCustomer());
- }
- });
+ ArchTaskExecutor.getInstance().executeOnDiskIO(
+ () -> mDatabase.getCustomerDao().insert(createCustomer()));
+ }
+
+ void clearAllCustomers() {
+ ArchTaskExecutor.getInstance().executeOnDiskIO(
+ () -> mDatabase.getCustomerDao().removeAll());
}
private static <K> LiveData<PagedList<Customer>> getLivePagedList(
@@ -92,6 +94,20 @@
.build();
}
+ private static <K> Flowable<PagedList<Customer>> getPagedListFlowable(
+ DataSource.Factory<K, Customer> dataSourceFactory) {
+ PagedList.Config config = new PagedList.Config.Builder()
+ .setPageSize(10)
+ .setEnablePlaceholders(false)
+ .build();
+ return new RxPagedListBuilder<>(dataSourceFactory, config)
+ .buildFlowable(BackpressureStrategy.LATEST);
+ }
+
+ Flowable<PagedList<Customer>> getPagedListFlowable() {
+ return getPagedListFlowable(mDatabase.getCustomerDao().loadPagedAgeOrder());
+ }
+
LiveData<PagedList<Customer>> getLivePagedList(int position) {
if (mLiveCustomerList == null) {
mLiveCustomerList =
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListRxActivity.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListRxActivity.java
new file mode 100644
index 0000000..df2adf2
--- /dev/null
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/RoomPagedListRxActivity.java
@@ -0,0 +1,68 @@
+/*
+ * 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 android.arch.persistence.room.integration.testapp;
+
+import android.arch.lifecycle.ViewModelProviders;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.RecyclerView;
+import android.widget.Button;
+
+import io.reactivex.disposables.CompositeDisposable;
+
+/**
+ * Sample {@code Flowable<PagedList>} activity which uses Room.
+ */
+public class RoomPagedListRxActivity extends AppCompatActivity {
+
+ private PagedListCustomerAdapter mAdapter;
+
+ private final CompositeDisposable mDisposable = new CompositeDisposable();
+ private CustomerViewModel mViewModel;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_recycler_view);
+ mViewModel = ViewModelProviders.of(this)
+ .get(CustomerViewModel.class);
+
+ RecyclerView recyclerView = findViewById(R.id.recyclerview);
+ mAdapter = new PagedListCustomerAdapter();
+ recyclerView.setAdapter(mAdapter);
+
+ final Button addButton = findViewById(R.id.addButton);
+ addButton.setOnClickListener(v -> mViewModel.insertCustomer());
+
+ final Button clearButton = findViewById(R.id.clearButton);
+ clearButton.setOnClickListener(v -> mViewModel.clearAllCustomers());
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+
+ mDisposable.add(mViewModel.getPagedListFlowable()
+ .subscribe(list -> mAdapter.submitList(list)));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mDisposable.clear();
+ }
+}
diff --git a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
index db45dc4..98d2bab 100644
--- a/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
+++ b/room/integration-tests/testapp/src/main/java/android/arch/persistence/room/integration/testapp/database/CustomerDao.java
@@ -44,6 +44,12 @@
void insertAll(Customer[] customers);
/**
+ * Delete all customers
+ */
+ @Query("DELETE FROM customer")
+ void removeAll();
+
+ /**
* @return DataSource.Factory of customers, ordered by last name. Use
* {@link android.arch.paging.LivePagedListBuilder} to get a LiveData of PagedLists.
*/
diff --git a/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml b/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
index 34e4491..49208f6 100644
--- a/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
+++ b/room/integration-tests/testapp/src/main/res/layout/activity_recycler_view.xml
@@ -35,6 +35,14 @@
android:paddingTop="@dimen/activity_vertical_margin"
/>
<Button
+ android:id="@+id/clearButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:text="@string/clear"/>
+ <Button
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/room/integration-tests/testapp/src/main/res/values/strings.xml b/room/integration-tests/testapp/src/main/res/values/strings.xml
index a18723e..2bba589 100644
--- a/room/integration-tests/testapp/src/main/res/values/strings.xml
+++ b/room/integration-tests/testapp/src/main/res/values/strings.xml
@@ -15,6 +15,7 @@
-->
<resources>
+ <string name="clear">Clear</string>
<string name="insert">Insert</string>
<string name="loading">loading</string>
</resources>