Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,19 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.espresso.Espresso
import com.example.nav3recipes.migration.start.StartMigrationActivity
import com.example.nav3recipes.migration.step2.Step2MigrationActivity
import com.example.nav3recipes.migration.step3.Step3MigrationActivity
import com.example.nav3recipes.migration.step4.Step4MigrationActivity
import com.example.nav3recipes.migration.step5.Step5MigrationActivity
import com.example.nav3recipes.migration.step6.Step6MigrationActivity
import com.example.nav3recipes.migration.step7.Step7MigrationActivity
import com.example.nav3recipes.migration.atomic.begin.BeginAtomicMigrationActivity
import com.example.nav3recipes.migration.atomic.end.EndAtomicMigrationActivity
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters

/**
* Instrumented navigation tests for each of the migration steps.
* Instrumented navigation tests for the start and end states of the atomic migration guide.
*/
@RunWith(Parameterized::class)
class MigrationActivityNavigationTest(activityClass: Class<out ComponentActivity>) {
class AtomicMigrationTest(activityClass: Class<out ComponentActivity>) {

@get:Rule(order = 0)
val composeTestRule = createAndroidComposeRule(activityClass)
Expand All @@ -35,13 +30,8 @@ class MigrationActivityNavigationTest(activityClass: Class<out ComponentActivity
@Parameters(name = "{0}")
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf(StartMigrationActivity::class.java),
arrayOf(Step2MigrationActivity::class.java),
arrayOf(Step3MigrationActivity::class.java),
arrayOf(Step4MigrationActivity::class.java),
arrayOf(Step5MigrationActivity::class.java),
arrayOf(Step6MigrationActivity::class.java),
arrayOf(Step7MigrationActivity::class.java)
arrayOf(BeginAtomicMigrationActivity::class.java),
arrayOf(EndAtomicMigrationActivity::class.java)
)
}
}
Expand Down
24 changes: 2 additions & 22 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -114,31 +114,11 @@
android:label="@string/app_name"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.start.StartMigrationActivity"
android:name=".migration.atomic.begin.BeginAtomicMigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.step2.Step2MigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.step3.Step3MigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.step4.Step4MigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.step5.Step5MigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.step6.Step6MigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
android:name=".migration.step7.Step7MigrationActivity"
android:name=".migration.atomic.end.EndAtomicMigrationActivity"
android:exported="true"
android:theme="@style/Theme.Nav3Recipes"/>
<activity
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Migration Recipe

This recipe demonstrates how to migrate Navigation 2 code to Navigation 3. It has two packages:

- `begin` - an app that is using Navigation 2
- `end` - the same app migrated to Navigation 3

Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,21 @@
* limitations under the License.
*/

package com.example.nav3recipes.migration.start
package com.example.nav3recipes.migration.atomic.begin

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Camera
import androidx.compose.material.icons.filled.Face
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
Expand All @@ -49,67 +41,34 @@ import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navOptions
import androidx.navigation.toRoute
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.content.ContentMauve
import com.example.nav3recipes.content.ContentPink
import com.example.nav3recipes.content.ContentPurple
import com.example.nav3recipes.content.ContentRed
import com.example.nav3recipes.migration.content.ScreenA
import com.example.nav3recipes.migration.content.ScreenA1
import com.example.nav3recipes.migration.content.ScreenB
import com.example.nav3recipes.migration.content.ScreenB1
import com.example.nav3recipes.migration.content.ScreenC
import com.example.nav3recipes.ui.setEdgeToEdgeConfig
import kotlinx.serialization.Serializable
import kotlin.reflect.KClass

/**
* Basic Navigation2 example with the following navigation graph:
*
* A -> A, A1, E
* B -> B, B1, E
* C -> C, E
* A -> A, A1
* B -> B, B1
* C -> C
* D
*
* - The starting destination (or home screen) is A.
* - A, B and C are top level destinations that appear in a navigation bar.
* - D is a dialog destination.
* - E is a shared destination that can appear under any of the top level destinations.
* - Navigating to a top level destination pops all other top level destinations off the stack,
* - Navigating to a top level destination pops all other top level destinations off the stack,
* except for the start destination.
* - Navigating back from the start destination exits the app.
*
* This will be the starting point for migration to Navigation 3.
*
* @see `MigrationActivityNavigationTest` for instrumented tests that verify this behavior.
* @see `AtomicMigrationTest` for instrumented tests that verify this behavior.
*/

// Feature module A
@Serializable private data object BaseRouteA
@Serializable private data object RouteA
@Serializable private data object RouteA1

// Feature module B
@Serializable private data object BaseRouteB
@Serializable private data object RouteB
@Serializable private data class RouteB1(val id: String)

// Feature module C
@Serializable private data object BaseRouteC
@Serializable private data object RouteC

// Common UI modules
@Serializable private data object RouteD
@Serializable private data object RouteE

private val TOP_LEVEL_ROUTES = mapOf(
BaseRouteA to NavBarItem(icon = Icons.Default.Home, description = "Route A"),
BaseRouteB to NavBarItem(icon = Icons.Default.Face, description = "Route B"),
BaseRouteC to NavBarItem(icon = Icons.Default.Camera, description = "Route C"),
)

class NavBarItem(
val icon: ImageVector,
val description: String
)

class StartMigrationActivity : ComponentActivity() {
class BeginAtomicMigrationActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
setEdgeToEdgeConfig()
Expand Down Expand Up @@ -150,16 +109,13 @@ class StartMigrationActivity : ComponentActivity() {
featureASection(
onSubRouteClick = { navController.navigate(RouteA1) },
onDialogClick = { navController.navigate(RouteD) },
onOtherClick = { navController.navigate(RouteE) }
)
featureBSection(
onDetailClick = { id -> navController.navigate(RouteB1(id)) },
onDialogClick = { navController.navigate(RouteD) },
onOtherClick = { navController.navigate(RouteE) }
)
featureCSection(
onDialogClick = { navController.navigate(RouteD) },
onOtherClick = { navController.navigate(RouteE) }
)
dialog<RouteD> { key ->
Text(modifier = Modifier.background(Color.White), text = "Route D title (dialog)")
Expand All @@ -173,78 +129,32 @@ class StartMigrationActivity : ComponentActivity() {
// Feature module A
private fun NavGraphBuilder.featureASection(
onSubRouteClick: () -> Unit,
onDialogClick: () -> Unit,
onOtherClick: () -> Unit,
onDialogClick: () -> Unit
) {
navigation<BaseRouteA>(startDestination = RouteA) {
composable<RouteA> {
ContentRed("Route A title") {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = onSubRouteClick) {
Text("Go to A1")
}
Button(onClick = onDialogClick) {
Text("Open dialog D")
}
Button(onClick = onOtherClick) {
Text("Go to E")
}
}
}
}
composable<RouteA1> { ContentPink("Route A1 title") }
composable<RouteE> { ContentBlue("Route E title") }
composable<RouteA> { ScreenA(onSubRouteClick, onDialogClick) }
composable<RouteA1> { ScreenA1() }
}
}


// Feature module B
private fun NavGraphBuilder.featureBSection(
onDetailClick: (id: String) -> Unit,
onDialogClick: () -> Unit,
onOtherClick: () -> Unit
onDialogClick: () -> Unit
) {
navigation<BaseRouteB>(startDestination = RouteB) {
composable<RouteB> {
ContentGreen("Route B title") {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { onDetailClick("ABC") }) {
Text("Go to B1")
}
Button(onClick = onDialogClick) {
Text("Open dialog D")
}
Button(onClick = onOtherClick) {
Text("Go to E")
}
}
}
}
composable<RouteB1> { key ->
ContentPurple("Route B1 title. ID: ${key.toRoute<RouteB1>().id}")
}
composable<RouteE> { ContentBlue("Route E title") }
composable<RouteB> { ScreenB(onDetailClick, onDialogClick) }
composable<RouteB1> { key -> ScreenB1(id = key.toRoute<RouteB1>().id) }
}
}

// Feature module C
private fun NavGraphBuilder.featureCSection(
onDialogClick: () -> Unit,
onOtherClick: () -> Unit,
onDialogClick: () -> Unit
) {
navigation<BaseRouteC>(startDestination = RouteC) {
composable<RouteC> {
ContentMauve("Route C title") {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = onDialogClick) {
Text("Open dialog D")
}
Button(onClick = onOtherClick) {
Text("Go to E")
}
}
}
}
composable<RouteE> { ContentBlue("Route E title") }
composable<RouteC> { ScreenC(onDialogClick) }
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2025 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 com.example.nav3recipes.migration.atomic.begin

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Camera
import androidx.compose.material.icons.filled.Face
import androidx.compose.material.icons.filled.Home
import androidx.compose.ui.graphics.vector.ImageVector
import kotlinx.serialization.Serializable

// Feature module A
@Serializable data object BaseRouteA
@Serializable data object RouteA
@Serializable data object RouteA1

// Feature module B
@Serializable data object BaseRouteB
@Serializable data object RouteB
@Serializable data class RouteB1(val id: String)

// Feature module C
@Serializable data object BaseRouteC
@Serializable data object RouteC

// Common UI modules
@Serializable data object RouteD


val TOP_LEVEL_ROUTES = mapOf(
BaseRouteA to NavBarItem(icon = Icons.Default.Home, description = "Route A"),
BaseRouteB to NavBarItem(icon = Icons.Default.Face, description = "Route B"),
BaseRouteC to NavBarItem(icon = Icons.Default.Camera, description = "Route C"),
)

class NavBarItem(
val icon: ImageVector,
val description: String
)
Loading