Skip to content

Commit 081baba

Browse files
authored
Merge pull request #134 from android/dt/migration
Simplify migration recipe
2 parents 5bbc211 + f79bb91 commit 081baba

File tree

23 files changed

+811
-2766
lines changed

23 files changed

+811
-2766
lines changed

app/src/androidTest/java/com/example/nav3recipes/MigrationActivityNavigationTest.kt renamed to app/src/androidTest/java/com/example/nav3recipes/AtomicMigrationTest.kt

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,19 @@ import androidx.compose.ui.test.junit4.createAndroidComposeRule
88
import androidx.compose.ui.test.onNodeWithText
99
import androidx.compose.ui.test.performClick
1010
import androidx.test.espresso.Espresso
11-
import com.example.nav3recipes.migration.start.StartMigrationActivity
12-
import com.example.nav3recipes.migration.step2.Step2MigrationActivity
13-
import com.example.nav3recipes.migration.step3.Step3MigrationActivity
14-
import com.example.nav3recipes.migration.step4.Step4MigrationActivity
15-
import com.example.nav3recipes.migration.step5.Step5MigrationActivity
16-
import com.example.nav3recipes.migration.step6.Step6MigrationActivity
17-
import com.example.nav3recipes.migration.step7.Step7MigrationActivity
11+
import com.example.nav3recipes.migration.atomic.begin.BeginAtomicMigrationActivity
12+
import com.example.nav3recipes.migration.atomic.end.EndAtomicMigrationActivity
1813
import org.junit.Rule
1914
import org.junit.Test
2015
import org.junit.runner.RunWith
2116
import org.junit.runners.Parameterized
2217
import org.junit.runners.Parameterized.Parameters
2318

2419
/**
25-
* Instrumented navigation tests for each of the migration steps.
20+
* Instrumented navigation tests for the start and end states of the atomic migration guide.
2621
*/
2722
@RunWith(Parameterized::class)
28-
class MigrationActivityNavigationTest(activityClass: Class<out ComponentActivity>) {
23+
class AtomicMigrationTest(activityClass: Class<out ComponentActivity>) {
2924

3025
@get:Rule(order = 0)
3126
val composeTestRule = createAndroidComposeRule(activityClass)
@@ -35,13 +30,8 @@ class MigrationActivityNavigationTest(activityClass: Class<out ComponentActivity
3530
@Parameters(name = "{0}")
3631
fun data(): Collection<Array<Any>> {
3732
return listOf(
38-
arrayOf(StartMigrationActivity::class.java),
39-
arrayOf(Step2MigrationActivity::class.java),
40-
arrayOf(Step3MigrationActivity::class.java),
41-
arrayOf(Step4MigrationActivity::class.java),
42-
arrayOf(Step5MigrationActivity::class.java),
43-
arrayOf(Step6MigrationActivity::class.java),
44-
arrayOf(Step7MigrationActivity::class.java)
33+
arrayOf(BeginAtomicMigrationActivity::class.java),
34+
arrayOf(EndAtomicMigrationActivity::class.java)
4535
)
4636
}
4737
}

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -119,31 +119,11 @@
119119
android:label="@string/app_name"
120120
android:theme="@style/Theme.Nav3Recipes"/>
121121
<activity
122-
android:name=".migration.start.StartMigrationActivity"
122+
android:name=".migration.atomic.begin.BeginAtomicMigrationActivity"
123123
android:exported="true"
124124
android:theme="@style/Theme.Nav3Recipes"/>
125125
<activity
126-
android:name=".migration.step2.Step2MigrationActivity"
127-
android:exported="true"
128-
android:theme="@style/Theme.Nav3Recipes"/>
129-
<activity
130-
android:name=".migration.step3.Step3MigrationActivity"
131-
android:exported="true"
132-
android:theme="@style/Theme.Nav3Recipes"/>
133-
<activity
134-
android:name=".migration.step4.Step4MigrationActivity"
135-
android:exported="true"
136-
android:theme="@style/Theme.Nav3Recipes"/>
137-
<activity
138-
android:name=".migration.step5.Step5MigrationActivity"
139-
android:exported="true"
140-
android:theme="@style/Theme.Nav3Recipes"/>
141-
<activity
142-
android:name=".migration.step6.Step6MigrationActivity"
143-
android:exported="true"
144-
android:theme="@style/Theme.Nav3Recipes"/>
145-
<activity
146-
android:name=".migration.step7.Step7MigrationActivity"
126+
android:name=".migration.atomic.end.EndAtomicMigrationActivity"
147127
android:exported="true"
148128
android:theme="@style/Theme.Nav3Recipes"/>
149129
<activity

app/src/main/java/com/example/nav3recipes/migration/README.md

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Migration Recipe
2+
3+
This recipe demonstrates how to migrate Navigation 2 code to Navigation 3. It has two packages:
4+
5+
- `begin` - an app that is using Navigation 2
6+
- `end` - the same app migrated to Navigation 3
7+

app/src/main/java/com/example/nav3recipes/migration/start/StartMigrationActivity.kt renamed to app/src/main/java/com/example/nav3recipes/migration/atomic/begin/BeginAtomicMigrationActivity.kt

Lines changed: 21 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,21 @@
1414
* limitations under the License.
1515
*/
1616

17-
package com.example.nav3recipes.migration.start
17+
package com.example.nav3recipes.migration.atomic.begin
1818

1919
import android.os.Bundle
2020
import androidx.activity.ComponentActivity
2121
import androidx.activity.compose.setContent
2222
import androidx.compose.foundation.background
23-
import androidx.compose.foundation.layout.Column
2423
import androidx.compose.foundation.layout.padding
25-
import androidx.compose.material.icons.Icons
26-
import androidx.compose.material.icons.filled.Camera
27-
import androidx.compose.material.icons.filled.Face
28-
import androidx.compose.material.icons.filled.Home
29-
import androidx.compose.material3.Button
3024
import androidx.compose.material3.Icon
3125
import androidx.compose.material3.NavigationBar
3226
import androidx.compose.material3.NavigationBarItem
3327
import androidx.compose.material3.Scaffold
3428
import androidx.compose.material3.Text
3529
import androidx.compose.runtime.getValue
36-
import androidx.compose.ui.Alignment
3730
import androidx.compose.ui.Modifier
3831
import androidx.compose.ui.graphics.Color
39-
import androidx.compose.ui.graphics.vector.ImageVector
4032
import androidx.navigation.NavDestination
4133
import androidx.navigation.NavDestination.Companion.hasRoute
4234
import androidx.navigation.NavDestination.Companion.hierarchy
@@ -49,67 +41,34 @@ import androidx.navigation.compose.navigation
4941
import androidx.navigation.compose.rememberNavController
5042
import androidx.navigation.navOptions
5143
import androidx.navigation.toRoute
52-
import com.example.nav3recipes.content.ContentBlue
53-
import com.example.nav3recipes.content.ContentGreen
54-
import com.example.nav3recipes.content.ContentMauve
55-
import com.example.nav3recipes.content.ContentPink
56-
import com.example.nav3recipes.content.ContentPurple
57-
import com.example.nav3recipes.content.ContentRed
44+
import com.example.nav3recipes.migration.content.ScreenA
45+
import com.example.nav3recipes.migration.content.ScreenA1
46+
import com.example.nav3recipes.migration.content.ScreenB
47+
import com.example.nav3recipes.migration.content.ScreenB1
48+
import com.example.nav3recipes.migration.content.ScreenC
5849
import com.example.nav3recipes.ui.setEdgeToEdgeConfig
59-
import kotlinx.serialization.Serializable
6050
import kotlin.reflect.KClass
6151

6252
/**
6353
* Basic Navigation2 example with the following navigation graph:
6454
*
65-
* A -> A, A1, E
66-
* B -> B, B1, E
67-
* C -> C, E
55+
* A -> A, A1
56+
* B -> B, B1
57+
* C -> C
6858
* D
6959
*
7060
* - The starting destination (or home screen) is A.
7161
* - A, B and C are top level destinations that appear in a navigation bar.
7262
* - D is a dialog destination.
73-
* - E is a shared destination that can appear under any of the top level destinations.
74-
* - Navigating to a top level destination pops all other top level destinations off the stack,
63+
* - Navigating to a top level destination pops all other top level destinations off the stack,
7564
* except for the start destination.
7665
* - Navigating back from the start destination exits the app.
7766
*
7867
* This will be the starting point for migration to Navigation 3.
7968
*
80-
* @see `MigrationActivityNavigationTest` for instrumented tests that verify this behavior.
69+
* @see `AtomicMigrationTest` for instrumented tests that verify this behavior.
8170
*/
82-
83-
// Feature module A
84-
@Serializable private data object BaseRouteA
85-
@Serializable private data object RouteA
86-
@Serializable private data object RouteA1
87-
88-
// Feature module B
89-
@Serializable private data object BaseRouteB
90-
@Serializable private data object RouteB
91-
@Serializable private data class RouteB1(val id: String)
92-
93-
// Feature module C
94-
@Serializable private data object BaseRouteC
95-
@Serializable private data object RouteC
96-
97-
// Common UI modules
98-
@Serializable private data object RouteD
99-
@Serializable private data object RouteE
100-
101-
private val TOP_LEVEL_ROUTES = mapOf(
102-
BaseRouteA to NavBarItem(icon = Icons.Default.Home, description = "Route A"),
103-
BaseRouteB to NavBarItem(icon = Icons.Default.Face, description = "Route B"),
104-
BaseRouteC to NavBarItem(icon = Icons.Default.Camera, description = "Route C"),
105-
)
106-
107-
class NavBarItem(
108-
val icon: ImageVector,
109-
val description: String
110-
)
111-
112-
class StartMigrationActivity : ComponentActivity() {
71+
class BeginAtomicMigrationActivity : ComponentActivity() {
11372

11473
override fun onCreate(savedInstanceState: Bundle?) {
11574
setEdgeToEdgeConfig()
@@ -150,16 +109,13 @@ class StartMigrationActivity : ComponentActivity() {
150109
featureASection(
151110
onSubRouteClick = { navController.navigate(RouteA1) },
152111
onDialogClick = { navController.navigate(RouteD) },
153-
onOtherClick = { navController.navigate(RouteE) }
154112
)
155113
featureBSection(
156114
onDetailClick = { id -> navController.navigate(RouteB1(id)) },
157115
onDialogClick = { navController.navigate(RouteD) },
158-
onOtherClick = { navController.navigate(RouteE) }
159116
)
160117
featureCSection(
161118
onDialogClick = { navController.navigate(RouteD) },
162-
onOtherClick = { navController.navigate(RouteE) }
163119
)
164120
dialog<RouteD> { key ->
165121
Text(modifier = Modifier.background(Color.White), text = "Route D title (dialog)")
@@ -173,78 +129,32 @@ class StartMigrationActivity : ComponentActivity() {
173129
// Feature module A
174130
private fun NavGraphBuilder.featureASection(
175131
onSubRouteClick: () -> Unit,
176-
onDialogClick: () -> Unit,
177-
onOtherClick: () -> Unit,
132+
onDialogClick: () -> Unit
178133
) {
179134
navigation<BaseRouteA>(startDestination = RouteA) {
180-
composable<RouteA> {
181-
ContentRed("Route A title") {
182-
Column(horizontalAlignment = Alignment.CenterHorizontally) {
183-
Button(onClick = onSubRouteClick) {
184-
Text("Go to A1")
185-
}
186-
Button(onClick = onDialogClick) {
187-
Text("Open dialog D")
188-
}
189-
Button(onClick = onOtherClick) {
190-
Text("Go to E")
191-
}
192-
}
193-
}
194-
}
195-
composable<RouteA1> { ContentPink("Route A1 title") }
196-
composable<RouteE> { ContentBlue("Route E title") }
135+
composable<RouteA> { ScreenA(onSubRouteClick, onDialogClick) }
136+
composable<RouteA1> { ScreenA1() }
197137
}
198138
}
199139

140+
200141
// Feature module B
201142
private fun NavGraphBuilder.featureBSection(
202143
onDetailClick: (id: String) -> Unit,
203-
onDialogClick: () -> Unit,
204-
onOtherClick: () -> Unit
144+
onDialogClick: () -> Unit
205145
) {
206146
navigation<BaseRouteB>(startDestination = RouteB) {
207-
composable<RouteB> {
208-
ContentGreen("Route B title") {
209-
Column(horizontalAlignment = Alignment.CenterHorizontally) {
210-
Button(onClick = { onDetailClick("ABC") }) {
211-
Text("Go to B1")
212-
}
213-
Button(onClick = onDialogClick) {
214-
Text("Open dialog D")
215-
}
216-
Button(onClick = onOtherClick) {
217-
Text("Go to E")
218-
}
219-
}
220-
}
221-
}
222-
composable<RouteB1> { key ->
223-
ContentPurple("Route B1 title. ID: ${key.toRoute<RouteB1>().id}")
224-
}
225-
composable<RouteE> { ContentBlue("Route E title") }
147+
composable<RouteB> { ScreenB(onDetailClick, onDialogClick) }
148+
composable<RouteB1> { key -> ScreenB1(id = key.toRoute<RouteB1>().id) }
226149
}
227150
}
228151

229152
// Feature module C
230153
private fun NavGraphBuilder.featureCSection(
231-
onDialogClick: () -> Unit,
232-
onOtherClick: () -> Unit,
154+
onDialogClick: () -> Unit
233155
) {
234156
navigation<BaseRouteC>(startDestination = RouteC) {
235-
composable<RouteC> {
236-
ContentMauve("Route C title") {
237-
Column(horizontalAlignment = Alignment.CenterHorizontally) {
238-
Button(onClick = onDialogClick) {
239-
Text("Open dialog D")
240-
}
241-
Button(onClick = onOtherClick) {
242-
Text("Go to E")
243-
}
244-
}
245-
}
246-
}
247-
composable<RouteE> { ContentBlue("Route E title") }
157+
composable<RouteC> { ScreenC(onDialogClick) }
248158
}
249159
}
250160

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.nav3recipes.migration.atomic.begin
18+
19+
import androidx.compose.material.icons.Icons
20+
import androidx.compose.material.icons.filled.Camera
21+
import androidx.compose.material.icons.filled.Face
22+
import androidx.compose.material.icons.filled.Home
23+
import androidx.compose.ui.graphics.vector.ImageVector
24+
import kotlinx.serialization.Serializable
25+
26+
// Feature module A
27+
@Serializable data object BaseRouteA
28+
@Serializable data object RouteA
29+
@Serializable data object RouteA1
30+
31+
// Feature module B
32+
@Serializable data object BaseRouteB
33+
@Serializable data object RouteB
34+
@Serializable data class RouteB1(val id: String)
35+
36+
// Feature module C
37+
@Serializable data object BaseRouteC
38+
@Serializable data object RouteC
39+
40+
// Common UI modules
41+
@Serializable data object RouteD
42+
43+
44+
val TOP_LEVEL_ROUTES = mapOf(
45+
BaseRouteA to NavBarItem(icon = Icons.Default.Home, description = "Route A"),
46+
BaseRouteB to NavBarItem(icon = Icons.Default.Face, description = "Route B"),
47+
BaseRouteC to NavBarItem(icon = Icons.Default.Camera, description = "Route C"),
48+
)
49+
50+
class NavBarItem(
51+
val icon: ImageVector,
52+
val description: String
53+
)

0 commit comments

Comments
 (0)