Skip to content

Commit a1c2fd9

Browse files
committed
Merge branch 'chore/improve-the-base-navigation' into feature/define-results-for-up-navigation
2 parents 4ed2969 + 1fc5e8b commit a1c2fd9

File tree

22 files changed

+185
-125
lines changed

22 files changed

+185
-125
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package co.nimblehq.sample.compose.test
2+
3+
import co.nimblehq.sample.compose.domain.model.Model
4+
5+
object MockUtil {
6+
7+
val models = listOf(
8+
Model(
9+
id = 1,
10+
username = "name1",
11+
),
12+
Model(
13+
id = 2,
14+
username = "name2",
15+
),
16+
Model(
17+
id = 3,
18+
username = "name3",
19+
),
20+
)
21+
}

sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt renamed to sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
1-
package co.nimblehq.sample.compose.ui.screens.home
1+
package co.nimblehq.sample.compose.ui.screens.main.home
22

33
import androidx.activity.compose.setContent
4-
import androidx.compose.ui.test.*
4+
import androidx.compose.ui.test.assertIsDisplayed
55
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
66
import androidx.compose.ui.test.junit4.createAndroidComposeRule
7+
import androidx.compose.ui.test.onNodeWithText
8+
import androidx.compose.ui.test.performClick
79
import androidx.test.ext.junit.rules.ActivityScenarioRule
810
import androidx.test.rule.GrantPermissionRule
9-
import co.nimblehq.sample.compose.domain.model.Model
10-
import co.nimblehq.sample.compose.domain.usecase.*
11+
import co.nimblehq.sample.compose.domain.usecase.GetModelsUseCase
12+
import co.nimblehq.sample.compose.domain.usecase.IsFirstTimeLaunchPreferencesUseCase
13+
import co.nimblehq.sample.compose.domain.usecase.UpdateFirstTimeLaunchPreferencesUseCase
14+
import co.nimblehq.sample.compose.test.MockUtil
1115
import co.nimblehq.sample.compose.test.TestDispatchersProvider
12-
import co.nimblehq.sample.compose.ui.AppDestination
16+
import co.nimblehq.sample.compose.ui.base.BaseDestination
1317
import co.nimblehq.sample.compose.ui.screens.MainActivity
18+
import co.nimblehq.sample.compose.ui.screens.main.MainDestination
1419
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
1520
import io.mockk.every
1621
import io.mockk.mockk
1722
import kotlinx.coroutines.flow.flowOf
18-
import org.junit.*
1923
import org.junit.Assert.assertEquals
24+
import org.junit.Before
25+
import org.junit.Rule
26+
import org.junit.Test
2027

2128
class HomeScreenTest {
2229

@@ -36,13 +43,11 @@ class HomeScreenTest {
3643
private val mockUpdateFirstTimeLaunchPreferencesUseCase: UpdateFirstTimeLaunchPreferencesUseCase = mockk()
3744

3845
private lateinit var viewModel: HomeViewModel
39-
private var expectedAppDestination: AppDestination? = null
46+
private var expectedDestination: BaseDestination? = null
4047

4148
@Before
4249
fun setUp() {
43-
every { mockGetModelsUseCase() } returns flowOf(
44-
listOf(Model(1), Model(2), Model(3))
45-
)
50+
every { mockGetModelsUseCase() } returns flowOf(MockUtil.models)
4651
every { mockIsFirstTimeLaunchPreferencesUseCase() } returns flowOf(false)
4752

4853
viewModel = HomeViewModel(
@@ -69,7 +74,7 @@ class HomeScreenTest {
6974
fun when_clicking_on_a_list_item__it_navigates_to_Second_screen() = initComposable {
7075
onNodeWithText("1").performClick()
7176

72-
assertEquals(expectedAppDestination, AppDestination.Second)
77+
assertEquals(expectedDestination, MainDestination.Second)
7378
}
7479

7580
private fun initComposable(
@@ -79,7 +84,7 @@ class HomeScreenTest {
7984
ComposeTheme {
8085
HomeScreen(
8186
viewModel = viewModel,
82-
navigator = { destination -> expectedAppDestination = destination }
87+
navigator = { destination -> expectedDestination = destination }
8388
)
8489
}
8590
}
Lines changed: 4 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,10 @@
11
package co.nimblehq.sample.compose.ui
22

3-
import androidx.navigation.NamedNavArgument
4-
import androidx.navigation.NavType
5-
import androidx.navigation.navArgument
6-
import co.nimblehq.sample.compose.model.UiModel
3+
import co.nimblehq.sample.compose.ui.base.BaseDestination
74

8-
const val KeyId = "id"
9-
const val KeyModel = "model"
10-
const val KeyResultOk = "keyResultOk"
5+
sealed class AppDestination {
116

12-
sealed class AppDestination(val route: String = "") {
7+
object RootNavGraph : BaseDestination("rootNavGraph")
138

14-
open val arguments: List<NamedNavArgument> = emptyList()
15-
16-
open var destination: String = route
17-
18-
open var parcelableArgument: Pair<String, Any?> = "" to null
19-
20-
data class Up(val results: HashMap<String, Any> = hashMapOf()) : AppDestination() {
21-
22-
fun addResult(key: String, value: Any) = apply {
23-
results[key] = value
24-
}
25-
}
26-
27-
object RootNavGraph : AppDestination("rootNavGraph")
28-
29-
object MainNavGraph : AppDestination("mainNavGraph")
30-
31-
object Home : AppDestination("home")
32-
33-
object Second : AppDestination("second/{$KeyId}") {
34-
35-
override val arguments = listOf(
36-
navArgument(KeyId) { type = NavType.StringType }
37-
)
38-
39-
fun createRoute(id: String) = apply {
40-
destination = "second/$id"
41-
}
42-
}
43-
44-
object Third : AppDestination("third") {
45-
fun addParcel(value: UiModel) = apply {
46-
parcelableArgument = KeyModel to value
47-
}
48-
}
9+
object MainNavGraph : BaseDestination("mainNavGraph")
4910
}

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppNavGraph.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.navigation.NavGraphBuilder
77
import androidx.navigation.NavHostController
88
import androidx.navigation.compose.NavHost
99
import androidx.navigation.compose.composable
10+
import co.nimblehq.sample.compose.ui.base.BaseDestination
1011
import co.nimblehq.sample.compose.ui.screens.main.mainNavGraph
1112

1213
@Composable
@@ -23,7 +24,7 @@ fun AppNavGraph(
2324
}
2425

2526
fun NavGraphBuilder.composable(
26-
destination: AppDestination,
27+
destination: BaseDestination,
2728
deepLinks: List<NavDeepLink> = emptyList(),
2829
content: @Composable (NavBackStackEntry) -> Unit,
2930
) {
@@ -36,25 +37,24 @@ fun NavGraphBuilder.composable(
3637
}
3738

3839
/**
39-
* Navigate to provided [AppDestination] with a Pair of key value String and Data [parcel]
40+
* Navigate to provided [BaseDestination] with a Pair of key value String and Data [parcel]
4041
* Caution to use this method. This method use savedStateHandle to store the Parcelable data.
4142
* When previousBackstackEntry is popped out from navigation stack, savedStateHandle will return null and cannot retrieve data.
4243
* eg.Login -> Home, the Login screen will be popped from the back-stack on logging in successfully.
4344
*/
44-
fun NavHostController.navigate(appDestination: AppDestination, parcel: Pair<String, Any?>? = null) {
45-
when (appDestination) {
46-
is AppDestination.Up -> {
47-
appDestination.results.forEach { (key, value) ->
45+
fun NavHostController.navigate(destination: BaseDestination, parcel: Pair<String, Any?>? = null) {
46+
when (destination) {
47+
is BaseDestination.Up -> {
48+
destination.results.forEach { (key, value) ->
4849
previousBackStackEntry?.savedStateHandle?.set(key, value)
4950
}
5051
navigateUp()
5152
}
52-
5353
else -> {
5454
parcel?.let { (key, value) ->
5555
currentBackStackEntry?.savedStateHandle?.set(key, value)
5656
}
57-
navigate(route = appDestination.destination)
57+
navigate(route = destination.destination)
5858
}
5959
}
6060
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package co.nimblehq.sample.compose.ui.base
2+
3+
import androidx.navigation.NamedNavArgument
4+
5+
const val KeyResultOk = "keyResultOk"
6+
7+
abstract class BaseDestination(val route: String = "") {
8+
9+
open val arguments: List<NamedNavArgument> = emptyList()
10+
11+
open var destination: String = route
12+
13+
open var parcelableArgument: Pair<String, Any?> = "" to null
14+
15+
data class Up(val results: HashMap<String, Any> = hashMapOf()) : BaseDestination() {
16+
17+
fun addResult(key: String, value: Any) = apply {
18+
results[key] = value
19+
}
20+
}
21+
}

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseViewModel.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package co.nimblehq.sample.compose.ui.base
22

33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
5-
import co.nimblehq.sample.compose.ui.AppDestination
65
import kotlinx.coroutines.*
76
import kotlinx.coroutines.flow.*
87
import kotlin.coroutines.CoroutineContext
@@ -19,7 +18,7 @@ abstract class BaseViewModel : ViewModel() {
1918
protected val _error = MutableSharedFlow<Throwable>()
2019
val error = _error.asSharedFlow()
2120

22-
protected val _navigator = MutableSharedFlow<AppDestination>()
21+
protected val _navigator = MutableSharedFlow<BaseDestination>()
2322
val navigator = _navigator.asSharedFlow()
2423

2524
/**
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package co.nimblehq.sample.compose.ui.screens.main
2+
3+
import androidx.navigation.NavType
4+
import androidx.navigation.navArgument
5+
import co.nimblehq.sample.compose.model.UiModel
6+
import co.nimblehq.sample.compose.ui.base.BaseDestination
7+
8+
const val KeyId = "id"
9+
const val KeyModel = "model"
10+
11+
sealed class MainDestination {
12+
13+
object Home : BaseDestination("home")
14+
15+
object Second : BaseDestination("second/{$KeyId}") {
16+
17+
override val arguments = listOf(
18+
navArgument(KeyId) { type = NavType.StringType }
19+
)
20+
21+
fun createRoute(id: String) = apply {
22+
destination = "second/$id"
23+
}
24+
}
25+
26+
object Third : BaseDestination("third") {
27+
fun addParcel(value: UiModel) = apply {
28+
parcelableArgument = KeyModel to value
29+
}
30+
}
31+
}

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ import androidx.navigation.navigation
66
import co.nimblehq.sample.compose.extensions.getThenRemove
77
import co.nimblehq.sample.compose.model.UiModel
88
import co.nimblehq.sample.compose.ui.AppDestination
9-
import co.nimblehq.sample.compose.ui.KeyId
10-
import co.nimblehq.sample.compose.ui.KeyModel
11-
import co.nimblehq.sample.compose.ui.KeyResultOk
9+
import co.nimblehq.sample.compose.ui.base.KeyResultOk
1210
import co.nimblehq.sample.compose.ui.composable
1311
import co.nimblehq.sample.compose.ui.navigate
1412
import co.nimblehq.sample.compose.ui.screens.main.home.HomeScreen
@@ -20,9 +18,9 @@ fun NavGraphBuilder.mainNavGraph(
2018
) {
2119
navigation(
2220
route = AppDestination.MainNavGraph.route,
23-
startDestination = AppDestination.Home.destination
21+
startDestination = MainDestination.Home.destination
2422
) {
25-
composable(destination = AppDestination.Home) { backStackEntry ->
23+
composable(destination = MainDestination.Home) { backStackEntry ->
2624
val isResultOk = backStackEntry.savedStateHandle
2725
.getThenRemove<Boolean>(KeyResultOk) ?: false
2826
HomeScreen(
@@ -33,14 +31,14 @@ fun NavGraphBuilder.mainNavGraph(
3331
)
3432
}
3533

36-
composable(destination = AppDestination.Second) { backStackEntry ->
34+
composable(destination = MainDestination.Second) { backStackEntry ->
3735
SecondScreen(
3836
navigator = { destination -> navController.navigate(destination) },
3937
id = backStackEntry.arguments?.getString(KeyId).orEmpty()
4038
)
4139
}
4240

43-
composable(destination = AppDestination.Third) {
41+
composable(destination = MainDestination.Third) {
4442
ThirdScreen(
4543
navigator = { destination -> navController.navigate(destination) },
4644
model = navController.previousBackStackEntry?.savedStateHandle?.get<UiModel>(

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import co.nimblehq.sample.compose.extensions.collectAsEffect
1616
import co.nimblehq.sample.compose.extensions.showToast
1717
import co.nimblehq.sample.compose.lib.IsLoading
1818
import co.nimblehq.sample.compose.model.UiModel
19-
import co.nimblehq.sample.compose.ui.AppDestination
19+
import co.nimblehq.sample.compose.ui.base.BaseDestination
2020
import co.nimblehq.sample.compose.ui.common.AppBar
2121
import co.nimblehq.sample.compose.ui.showToast
2222
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.*
2626
@Composable
2727
fun HomeScreen(
2828
viewModel: HomeViewModel = hiltViewModel(),
29-
navigator: (destination: AppDestination) -> Unit,
29+
navigator: (destination: BaseDestination) -> Unit,
3030
isResultOk: Boolean = false,
3131
) {
3232
val context = LocalContext.current

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import co.nimblehq.sample.compose.domain.usecase.IsFirstTimeLaunchPreferencesUse
66
import co.nimblehq.sample.compose.domain.usecase.UpdateFirstTimeLaunchPreferencesUseCase
77
import co.nimblehq.sample.compose.model.UiModel
88
import co.nimblehq.sample.compose.model.toUiModel
9-
import co.nimblehq.sample.compose.ui.AppDestination
109
import co.nimblehq.sample.compose.ui.base.BaseViewModel
10+
import co.nimblehq.sample.compose.ui.screens.main.MainDestination
1111
import co.nimblehq.sample.compose.util.DispatchersProvider
1212
import dagger.hilt.android.lifecycle.HiltViewModel
1313
import kotlinx.coroutines.flow.MutableStateFlow
@@ -60,10 +60,10 @@ class HomeViewModel @Inject constructor(
6060
}
6161

6262
fun navigateToSecond(uiModel: UiModel) {
63-
launch { _navigator.emit(AppDestination.Second.createRoute(uiModel.id)) }
63+
launch { _navigator.emit(MainDestination.Second.createRoute(uiModel.id)) }
6464
}
6565

6666
fun navigateToThird(uiModel: UiModel) {
67-
launch { _navigator.emit(AppDestination.Third.addParcel(uiModel)) }
67+
launch { _navigator.emit(MainDestination.Third.addParcel(uiModel)) }
6868
}
6969
}

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@ import androidx.compose.ui.res.stringResource
1414
import androidx.compose.ui.tooling.preview.Preview
1515
import androidx.hilt.navigation.compose.hiltViewModel
1616
import co.nimblehq.sample.compose.R
17-
import co.nimblehq.sample.compose.ui.AppDestination
18-
import co.nimblehq.sample.compose.ui.KeyResultOk
17+
import co.nimblehq.sample.compose.ui.base.BaseDestination
18+
import co.nimblehq.sample.compose.ui.base.KeyResultOk
1919
import co.nimblehq.sample.compose.ui.common.AppBar
2020
import co.nimblehq.sample.compose.ui.theme.AppTheme.dimensions
2121
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
2222

2323
@Composable
2424
fun SecondScreen(
2525
viewModel: SecondViewModel = hiltViewModel(),
26-
navigator: (destination: AppDestination) -> Unit,
26+
navigator: (destination: BaseDestination) -> Unit,
2727
id: String,
2828
) {
2929
SecondScreenContent(
3030
id = id,
3131
onUpdateClick = {
32-
navigator(AppDestination.Up().addResult(KeyResultOk, true))
32+
navigator(BaseDestination.Up().addResult(KeyResultOk, true))
3333
},
3434
)
3535
}

sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/third/ThirdScreen.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ import androidx.compose.ui.tooling.preview.Preview
1414
import androidx.hilt.navigation.compose.hiltViewModel
1515
import co.nimblehq.sample.compose.R
1616
import co.nimblehq.sample.compose.model.UiModel
17-
import co.nimblehq.sample.compose.ui.AppDestination
17+
import co.nimblehq.sample.compose.ui.base.BaseDestination
1818
import co.nimblehq.sample.compose.ui.common.AppBar
1919
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
2020

2121
@Composable
2222
fun ThirdScreen(
2323
viewModel: ThirdViewModel = hiltViewModel(),
24-
navigator: (destination: AppDestination) -> Unit,
24+
navigator: (destination: BaseDestination) -> Unit,
2525
model: UiModel?,
2626
) {
2727
ThirdScreenContent(data = model)

0 commit comments

Comments
 (0)