Skip to content

Commit 1974e18

Browse files
committed
Merge branch 'feature/define-results-for-up-navigation' into feature/define-deeplink-structure
2 parents f085023 + a1c2fd9 commit 1974e18

File tree

24 files changed

+196
-146
lines changed

24 files changed

+196
-146
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
}

sample-compose/app/src/main/java/co/nimblehq/sample/compose/extensions/SavedStateHandleExt.kt

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

33
import androidx.lifecycle.SavedStateHandle
44

5-
fun <T> SavedStateHandle.getAndRemove(key: String): T? {
5+
fun <T> SavedStateHandle.getThenRemove(key: String): T? {
66
return if (contains(key)) {
77
val value = get<T>(key)
88
remove<T>(key)
Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +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 val deepLinks: List<String> = listOf(
17-
"https://android.nimblehq.co/$route",
18-
"android://$route",
19-
)
20-
21-
open var destination: String = route
22-
23-
open var parcelableArgument: Pair<String, Any?> = "" to null
24-
25-
data class Up(val results: List<Result> = emptyList()) : AppDestination()
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
}
50-
51-
data class Result(
52-
val key: String,
53-
val value: Any,
54-
)

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.NavHostController
77
import androidx.navigation.compose.NavHost
88
import androidx.navigation.compose.composable
99
import androidx.navigation.navDeepLink
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
content: @Composable (NavBackStackEntry) -> Unit,
2829
) {
2930
composable(
@@ -39,25 +40,24 @@ fun NavGraphBuilder.composable(
3940
}
4041

4142
/**
42-
* Navigate to provided [AppDestination] with a Pair of key value String and Data [parcel]
43+
* Navigate to provided [BaseDestination] with a Pair of key value String and Data [parcel]
4344
* Caution to use this method. This method use savedStateHandle to store the Parcelable data.
4445
* When previousBackstackEntry is popped out from navigation stack, savedStateHandle will return null and cannot retrieve data.
4546
* eg.Login -> Home, the Login screen will be popped from the back-stack on logging in successfully.
4647
*/
47-
fun NavHostController.navigate(appDestination: AppDestination, parcel: Pair<String, Any?>? = null) {
48-
when (appDestination) {
49-
is AppDestination.Up -> {
50-
appDestination.results.forEach { (key, value) ->
48+
fun NavHostController.navigate(destination: BaseDestination, parcel: Pair<String, Any?>? = null) {
49+
when (destination) {
50+
is BaseDestination.Up -> {
51+
destination.results.forEach { (key, value) ->
5152
previousBackStackEntry?.savedStateHandle?.set(key, value)
5253
}
5354
navigateUp()
5455
}
55-
5656
else -> {
5757
parcel?.let { (key, value) ->
5858
currentBackStackEntry?.savedStateHandle?.set(key, value)
5959
}
60-
navigate(route = appDestination.destination)
60+
navigate(route = destination.destination)
6161
}
6262
}
6363
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 val deepLinks: List<String> = listOf(
12+
"https://android.nimblehq.co/$route",
13+
"android://$route",
14+
)
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()) : BaseDestination() {
21+
22+
fun addResult(key: String, value: Any) = apply {
23+
results[key] = value
24+
}
25+
}
26+
}

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: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@ package co.nimblehq.sample.compose.ui.screens.main
33
import androidx.navigation.NavGraphBuilder
44
import androidx.navigation.NavHostController
55
import androidx.navigation.navigation
6-
import co.nimblehq.sample.compose.extensions.getAndRemove
6+
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,11 +18,11 @@ 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
27-
.getAndRemove<Boolean>(KeyResultOk) ?: false
25+
.getThenRemove<Boolean>(KeyResultOk) ?: false
2826
HomeScreen(
2927
navigator = { destination ->
3028
navController.navigate(destination, destination.parcelableArgument)
@@ -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
}

0 commit comments

Comments
 (0)