From 54ef3e3b4700b5aaa04f10fb134876ce54c3ac48 Mon Sep 17 00:00:00 2001 From: Luong Vo Date: Tue, 21 Nov 2023 22:51:31 +0700 Subject: [PATCH 1/3] [#547] Use Map instead of List --- .../co/nimblehq/sample/compose/ui/AppDestination.kt | 12 ++++++------ .../compose/ui/screens/main/second/SecondScreen.kt | 12 +----------- .../nimblehq/template/compose/ui/AppDestination.kt | 12 ++++++------ 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt index 3167eaf88..6a6a4b0a1 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt @@ -17,7 +17,12 @@ sealed class AppDestination(val route: String = "") { open var parcelableArgument: Pair = "" to null - data class Up(val results: List = emptyList()) : AppDestination() + data class Up(val results: HashMap = hashMapOf()) : AppDestination() { + + fun addResult(key: String, value: Any) = apply { + results[key] = value + } + } object RootNavGraph : AppDestination("rootNavGraph") @@ -42,8 +47,3 @@ sealed class AppDestination(val route: String = "") { } } } - -data class Result( - val key: String, - val value: Any, -) diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt index be00e0cf7..29e262f89 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt @@ -16,7 +16,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import co.nimblehq.sample.compose.R import co.nimblehq.sample.compose.ui.AppDestination import co.nimblehq.sample.compose.ui.KeyResultOk -import co.nimblehq.sample.compose.ui.Result import co.nimblehq.sample.compose.ui.common.AppBar import co.nimblehq.sample.compose.ui.theme.AppTheme.dimensions import co.nimblehq.sample.compose.ui.theme.ComposeTheme @@ -30,16 +29,7 @@ fun SecondScreen( SecondScreenContent( id = id, onUpdateClick = { - navigator( - AppDestination.Up( - listOf( - Result( - key = KeyResultOk, - value = true, - ), - ) - ) - ) + navigator(AppDestination.Up().addResult(KeyResultOk, true)) }, ) } diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt index 85bdaacff..f997299cc 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt @@ -8,7 +8,12 @@ sealed class AppDestination(val route: String = "") { open var destination: String = route - data class Up(val results: List = emptyList()) : AppDestination() + data class Up(val results: HashMap = hashMapOf()) : AppDestination() { + + fun addResult(key: String, value: Any) = apply { + results[key] = value + } + } object RootNavGraph : AppDestination("rootNavGraph") @@ -16,8 +21,3 @@ sealed class AppDestination(val route: String = "") { object Home : AppDestination("home") } - -data class Result( - val key: String, - val value: Any, -) From 4ed2969da6824ce98cefed38d01dedb7c7a5d788 Mon Sep 17 00:00:00 2001 From: Luong Vo Date: Tue, 21 Nov 2023 23:02:20 +0700 Subject: [PATCH 2/3] [#547] Rename: getAndRemove > getThenRemove --- .../nimblehq/sample/compose/extensions/SavedStateHandleExt.kt | 2 +- .../nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt | 4 ++-- .../template/compose/extensions/SavedStateHandleExt.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/extensions/SavedStateHandleExt.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/extensions/SavedStateHandleExt.kt index 9e8c13717..61fe3a7d5 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/extensions/SavedStateHandleExt.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/extensions/SavedStateHandleExt.kt @@ -2,7 +2,7 @@ package co.nimblehq.sample.compose.extensions import androidx.lifecycle.SavedStateHandle -fun SavedStateHandle.getAndRemove(key: String): T? { +fun SavedStateHandle.getThenRemove(key: String): T? { return if (contains(key)) { val value = get(key) remove(key) diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt index 67a9aa77e..e398a61c9 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt @@ -3,7 +3,7 @@ package co.nimblehq.sample.compose.ui.screens.main import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.navigation -import co.nimblehq.sample.compose.extensions.getAndRemove +import co.nimblehq.sample.compose.extensions.getThenRemove import co.nimblehq.sample.compose.model.UiModel import co.nimblehq.sample.compose.ui.AppDestination import co.nimblehq.sample.compose.ui.KeyId @@ -24,7 +24,7 @@ fun NavGraphBuilder.mainNavGraph( ) { composable(destination = AppDestination.Home) { backStackEntry -> val isResultOk = backStackEntry.savedStateHandle - .getAndRemove(KeyResultOk) ?: false + .getThenRemove(KeyResultOk) ?: false HomeScreen( navigator = { destination -> navController.navigate(destination, destination.parcelableArgument) diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/extensions/SavedStateHandleExt.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/extensions/SavedStateHandleExt.kt index afcdbb69f..c041c5545 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/extensions/SavedStateHandleExt.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/extensions/SavedStateHandleExt.kt @@ -2,7 +2,7 @@ package co.nimblehq.template.compose.extensions import androidx.lifecycle.SavedStateHandle -fun SavedStateHandle.getAndRemove(key: String): T? { +fun SavedStateHandle.getThenRemove(key: String): T? { return if (contains(key)) { val value = get(key) remove(key) From 1fc5e8b41786dab053d08e57d522ed46cf26cfab Mon Sep 17 00:00:00 2001 From: Luong Vo Date: Wed, 22 Nov 2023 09:11:08 +0700 Subject: [PATCH 3/3] [#552] Separate destinations per nav graph with a BaseDestination --- .../nimblehq/sample/compose/test/MockUtil.kt | 21 ++++++++++ .../screens/{ => main}/home/HomeScreenTest.kt | 29 +++++++------ .../sample/compose/ui/AppDestination.kt | 41 ++----------------- .../nimblehq/sample/compose/ui/AppNavGraph.kt | 13 +++--- .../sample/compose/ui/base/BaseDestination.kt | 14 +++++++ .../sample/compose/ui/base/BaseViewModel.kt | 3 +- .../ui/screens/main/MainDestination.kt | 31 ++++++++++++++ .../compose/ui/screens/main/MainNavGraph.kt | 10 ++--- .../ui/screens/main/home/HomeScreen.kt | 4 +- .../ui/screens/main/home/HomeViewModel.kt | 6 +-- .../ui/screens/main/second/SecondScreen.kt | 4 +- .../ui/screens/main/third/ThirdScreen.kt | 4 +- .../ui/screens/main/home/HomeScreenTest.kt | 9 ++-- .../ui/screens/main/home/HomeViewModelTest.kt | 4 +- .../template/compose/ui/AppDestination.kt | 16 ++------ .../template/compose/ui/AppNavGraph.kt | 16 +++++--- .../compose/ui/base/BaseDestination.kt | 12 ++++++ .../template/compose/ui/base/BaseViewModel.kt | 3 +- .../ui/screens/main/MainDestination.kt | 8 ++++ .../compose/ui/screens/main/MainNavGraph.kt | 12 ++++-- .../ui/screens/main/home/HomeScreen.kt | 10 +++-- .../ui/screens/main/home/HomeScreenTest.kt | 6 +-- 22 files changed, 168 insertions(+), 108 deletions(-) create mode 100644 sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/test/MockUtil.kt rename sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/{ => main}/home/HomeScreenTest.kt (72%) create mode 100644 sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseDestination.kt create mode 100644 sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainDestination.kt create mode 100644 template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseDestination.kt create mode 100644 template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainDestination.kt diff --git a/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/test/MockUtil.kt b/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/test/MockUtil.kt new file mode 100644 index 000000000..d79a92a4c --- /dev/null +++ b/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/test/MockUtil.kt @@ -0,0 +1,21 @@ +package co.nimblehq.sample.compose.test + +import co.nimblehq.sample.compose.domain.model.Model + +object MockUtil { + + val models = listOf( + Model( + id = 1, + username = "name1", + ), + Model( + id = 2, + username = "name2", + ), + Model( + id = 3, + username = "name3", + ), + ) +} diff --git a/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt b/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt similarity index 72% rename from sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt rename to sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt index a5ce5d6ea..c5358e41a 100644 --- a/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt +++ b/sample-compose/app/src/androidTest/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt @@ -1,22 +1,29 @@ -package co.nimblehq.sample.compose.ui.screens.home +package co.nimblehq.sample.compose.ui.screens.main.home import androidx.activity.compose.setContent -import androidx.compose.ui.test.* +import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.rule.GrantPermissionRule -import co.nimblehq.sample.compose.domain.model.Model -import co.nimblehq.sample.compose.domain.usecase.* +import co.nimblehq.sample.compose.domain.usecase.GetModelsUseCase +import co.nimblehq.sample.compose.domain.usecase.IsFirstTimeLaunchPreferencesUseCase +import co.nimblehq.sample.compose.domain.usecase.UpdateFirstTimeLaunchPreferencesUseCase +import co.nimblehq.sample.compose.test.MockUtil import co.nimblehq.sample.compose.test.TestDispatchersProvider -import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.screens.MainActivity +import co.nimblehq.sample.compose.ui.screens.main.MainDestination import co.nimblehq.sample.compose.ui.theme.ComposeTheme import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.flow.flowOf -import org.junit.* import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test class HomeScreenTest { @@ -36,13 +43,11 @@ class HomeScreenTest { private val mockUpdateFirstTimeLaunchPreferencesUseCase: UpdateFirstTimeLaunchPreferencesUseCase = mockk() private lateinit var viewModel: HomeViewModel - private var expectedAppDestination: AppDestination? = null + private var expectedDestination: BaseDestination? = null @Before fun setUp() { - every { mockGetModelsUseCase() } returns flowOf( - listOf(Model(1), Model(2), Model(3)) - ) + every { mockGetModelsUseCase() } returns flowOf(MockUtil.models) every { mockIsFirstTimeLaunchPreferencesUseCase() } returns flowOf(false) viewModel = HomeViewModel( @@ -69,7 +74,7 @@ class HomeScreenTest { fun when_clicking_on_a_list_item__it_navigates_to_Second_screen() = initComposable { onNodeWithText("1").performClick() - assertEquals(expectedAppDestination, AppDestination.Second) + assertEquals(expectedDestination, MainDestination.Second) } private fun initComposable( @@ -79,7 +84,7 @@ class HomeScreenTest { ComposeTheme { HomeScreen( viewModel = viewModel, - navigator = { destination -> expectedAppDestination = destination } + navigator = { destination -> expectedDestination = destination } ) } } diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt index 5c8a3f4ef..325e9c7cd 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt @@ -1,43 +1,10 @@ package co.nimblehq.sample.compose.ui -import androidx.navigation.NamedNavArgument -import androidx.navigation.NavType -import androidx.navigation.navArgument -import co.nimblehq.sample.compose.model.UiModel +import co.nimblehq.sample.compose.ui.base.BaseDestination -const val KeyId = "id" -const val KeyModel = "model" +sealed class AppDestination { -sealed class AppDestination(val route: String = "") { + object RootNavGraph : BaseDestination("rootNavGraph") - open val arguments: List = emptyList() - - open var destination: String = route - - open var parcelableArgument: Pair = "" to null - - object Up : AppDestination() - - object RootNavGraph : AppDestination("rootNavGraph") - - object MainNavGraph : AppDestination("mainNavGraph") - - object Home : AppDestination("home") - - object Second : AppDestination("second/{$KeyId}") { - - override val arguments = listOf( - navArgument(KeyId) { type = NavType.StringType } - ) - - fun createRoute(id: String) = apply { - destination = "second/$id" - } - } - - object Third : AppDestination("third") { - fun addParcel(value: UiModel) = apply { - parcelableArgument = KeyModel to value - } - } + object MainNavGraph : BaseDestination("mainNavGraph") } diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppNavGraph.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppNavGraph.kt index 4cb5680de..fe41f58a7 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppNavGraph.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppNavGraph.kt @@ -7,6 +7,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.screens.main.mainNavGraph @Composable @@ -23,7 +24,7 @@ fun AppNavigation( } fun NavGraphBuilder.composable( - destination: AppDestination, + destination: BaseDestination, deepLinks: List = emptyList(), content: @Composable (NavBackStackEntry) -> Unit, ) { @@ -36,19 +37,19 @@ fun NavGraphBuilder.composable( } /** - * Navigate to provided [AppDestination] with a Pair of key value String and Data [parcel] + * Navigate to provided [BaseDestination] with a Pair of key value String and Data [parcel] * Caution to use this method. This method use savedStateHandle to store the Parcelable data. * When previousBackstackEntry is popped out from navigation stack, savedStateHandle will return null and cannot retrieve data. * eg.Login -> Home, the Login screen will be popped from the back-stack on logging in successfully. */ -fun NavHostController.navigate(appDestination: AppDestination, parcel: Pair? = null) { - when (appDestination) { - is AppDestination.Up -> navigateUp() +fun NavHostController.navigate(destination: BaseDestination, parcel: Pair? = null) { + when (destination) { + is BaseDestination.Up -> navigateUp() else -> { parcel?.let { (key, value) -> currentBackStackEntry?.savedStateHandle?.set(key, value) } - navigate(route = appDestination.destination) + navigate(route = destination.destination) } } } diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseDestination.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseDestination.kt new file mode 100644 index 000000000..e7f3fe7ff --- /dev/null +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseDestination.kt @@ -0,0 +1,14 @@ +package co.nimblehq.sample.compose.ui.base + +import androidx.navigation.NamedNavArgument + +abstract class BaseDestination(val route: String = "") { + + open val arguments: List = emptyList() + + open var destination: String = route + + open var parcelableArgument: Pair = "" to null + + object Up : BaseDestination() +} diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseViewModel.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseViewModel.kt index e787f7676..ad7649708 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseViewModel.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseViewModel.kt @@ -2,7 +2,6 @@ package co.nimblehq.sample.compose.ui.base import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import co.nimblehq.sample.compose.ui.AppDestination import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlin.coroutines.CoroutineContext @@ -19,7 +18,7 @@ abstract class BaseViewModel : ViewModel() { protected val _error = MutableSharedFlow() val error = _error.asSharedFlow() - protected val _navigator = MutableSharedFlow() + protected val _navigator = MutableSharedFlow() val navigator = _navigator.asSharedFlow() /** diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainDestination.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainDestination.kt new file mode 100644 index 000000000..c7645ebdd --- /dev/null +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainDestination.kt @@ -0,0 +1,31 @@ +package co.nimblehq.sample.compose.ui.screens.main + +import androidx.navigation.NavType +import androidx.navigation.navArgument +import co.nimblehq.sample.compose.model.UiModel +import co.nimblehq.sample.compose.ui.base.BaseDestination + +const val KeyId = "id" +const val KeyModel = "model" + +sealed class MainDestination { + + object Home : BaseDestination("home") + + object Second : BaseDestination("second/{$KeyId}") { + + override val arguments = listOf( + navArgument(KeyId) { type = NavType.StringType } + ) + + fun createRoute(id: String) = apply { + destination = "second/$id" + } + } + + object Third : BaseDestination("third") { + fun addParcel(value: UiModel) = apply { + parcelableArgument = KeyModel to value + } + } +} diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt index f4192df30..c45135c04 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/MainNavGraph.kt @@ -5,8 +5,6 @@ import androidx.navigation.NavHostController import androidx.navigation.navigation import co.nimblehq.sample.compose.model.UiModel import co.nimblehq.sample.compose.ui.AppDestination -import co.nimblehq.sample.compose.ui.KeyId -import co.nimblehq.sample.compose.ui.KeyModel import co.nimblehq.sample.compose.ui.composable import co.nimblehq.sample.compose.ui.navigate import co.nimblehq.sample.compose.ui.screens.main.home.HomeScreen @@ -18,9 +16,9 @@ fun NavGraphBuilder.mainNavGraph( ) { navigation( route = AppDestination.MainNavGraph.route, - startDestination = AppDestination.Home.destination + startDestination = MainDestination.Home.destination ) { - composable(destination = AppDestination.Home) { + composable(destination = MainDestination.Home) { HomeScreen( navigator = { destination -> navController.navigate(destination, destination.parcelableArgument) @@ -28,14 +26,14 @@ fun NavGraphBuilder.mainNavGraph( ) } - composable(destination = AppDestination.Second) { backStackEntry -> + composable(destination = MainDestination.Second) { backStackEntry -> SecondScreen( navigator = { destination -> navController.navigate(destination) }, id = backStackEntry.arguments?.getString(KeyId).orEmpty() ) } - composable(destination = AppDestination.Third) { + composable(destination = MainDestination.Third) { ThirdScreen( navigator = { destination -> navController.navigate(destination) }, model = navController.previousBackStackEntry?.savedStateHandle?.get( diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreen.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreen.kt index 0bd05c0ee..0b112da01 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreen.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreen.kt @@ -16,7 +16,7 @@ import co.nimblehq.sample.compose.extensions.collectAsEffect import co.nimblehq.sample.compose.extensions.showToast import co.nimblehq.sample.compose.lib.IsLoading import co.nimblehq.sample.compose.model.UiModel -import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.common.AppBar import co.nimblehq.sample.compose.ui.showToast import co.nimblehq.sample.compose.ui.theme.ComposeTheme @@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.* @Composable fun HomeScreen( viewModel: HomeViewModel = hiltViewModel(), - navigator: (destination: AppDestination) -> Unit, + navigator: (destination: BaseDestination) -> Unit, ) { val context = LocalContext.current viewModel.error.collectAsEffect { e -> e.showToast(context) } diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModel.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModel.kt index 3598004e3..606270a30 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModel.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModel.kt @@ -6,8 +6,8 @@ import co.nimblehq.sample.compose.domain.usecase.IsFirstTimeLaunchPreferencesUse import co.nimblehq.sample.compose.domain.usecase.UpdateFirstTimeLaunchPreferencesUseCase import co.nimblehq.sample.compose.model.UiModel import co.nimblehq.sample.compose.model.toUiModel -import co.nimblehq.sample.compose.ui.AppDestination import co.nimblehq.sample.compose.ui.base.BaseViewModel +import co.nimblehq.sample.compose.ui.screens.main.MainDestination import co.nimblehq.sample.compose.util.DispatchersProvider import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -60,10 +60,10 @@ class HomeViewModel @Inject constructor( } fun navigateToSecond(uiModel: UiModel) { - launch { _navigator.emit(AppDestination.Second.createRoute(uiModel.id)) } + launch { _navigator.emit(MainDestination.Second.createRoute(uiModel.id)) } } fun navigateToThird(uiModel: UiModel) { - launch { _navigator.emit(AppDestination.Third.addParcel(uiModel)) } + launch { _navigator.emit(MainDestination.Third.addParcel(uiModel)) } } } diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt index 596952901..7fc436a88 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/second/SecondScreen.kt @@ -12,14 +12,14 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import co.nimblehq.sample.compose.R -import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.common.AppBar import co.nimblehq.sample.compose.ui.theme.ComposeTheme @Composable fun SecondScreen( viewModel: SecondViewModel = hiltViewModel(), - navigator: (destination: AppDestination) -> Unit, + navigator: (destination: BaseDestination) -> Unit, id: String, ) { SecondScreenContent(id) diff --git a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/third/ThirdScreen.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/third/ThirdScreen.kt index 6a5aad599..7c60dfc94 100644 --- a/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/third/ThirdScreen.kt +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/screens/main/third/ThirdScreen.kt @@ -14,14 +14,14 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.hilt.navigation.compose.hiltViewModel import co.nimblehq.sample.compose.R import co.nimblehq.sample.compose.model.UiModel -import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.common.AppBar import co.nimblehq.sample.compose.ui.theme.ComposeTheme @Composable fun ThirdScreen( viewModel: ThirdViewModel = hiltViewModel(), - navigator: (destination: AppDestination) -> Unit, + navigator: (destination: BaseDestination) -> Unit, model: UiModel?, ) { ThirdScreenContent(data = model) diff --git a/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt b/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt index bbd744cae..0c439162f 100644 --- a/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt +++ b/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeScreenTest.kt @@ -9,9 +9,10 @@ import androidx.test.rule.GrantPermissionRule import co.nimblehq.sample.compose.R import co.nimblehq.sample.compose.domain.usecase.* import co.nimblehq.sample.compose.test.MockUtil -import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.screens.BaseScreenTest import co.nimblehq.sample.compose.ui.screens.MainActivity +import co.nimblehq.sample.compose.ui.screens.main.MainDestination import co.nimblehq.sample.compose.ui.theme.ComposeTheme import io.kotest.matchers.shouldBe import io.mockk.* @@ -45,7 +46,7 @@ class HomeScreenTest : BaseScreenTest() { mockk() private lateinit var viewModel: HomeViewModel - private var expectedAppDestination: AppDestination? = null + private var expectedDestination: BaseDestination? = null @Before fun setUp() { @@ -105,7 +106,7 @@ class HomeScreenTest : BaseScreenTest() { fun `When clicking on a list item, it navigates to Second screen`() = initComposable { onNodeWithText("1").performClick() - assertEquals(expectedAppDestination, AppDestination.Second) + assertEquals(expectedDestination, MainDestination.Second) } private fun initComposable( @@ -117,7 +118,7 @@ class HomeScreenTest : BaseScreenTest() { ComposeTheme { HomeScreen( viewModel = viewModel, - navigator = { destination -> expectedAppDestination = destination }, + navigator = { destination -> expectedDestination = destination }, ) } } diff --git a/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModelTest.kt b/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModelTest.kt index dd3f57231..5584d7d30 100644 --- a/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModelTest.kt +++ b/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/main/home/HomeViewModelTest.kt @@ -7,7 +7,7 @@ import co.nimblehq.sample.compose.domain.usecase.UpdateFirstTimeLaunchPreference import co.nimblehq.sample.compose.model.toUiModel import co.nimblehq.sample.compose.test.CoroutineTestRule import co.nimblehq.sample.compose.test.MockUtil -import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.screens.main.MainDestination import co.nimblehq.sample.compose.util.DispatchersProvider import io.kotest.matchers.shouldBe import io.mockk.Runs @@ -86,7 +86,7 @@ class HomeViewModelTest { viewModel.navigator.test { viewModel.navigateToSecond(MockUtil.models[0].toUiModel()) - expectMostRecentItem() shouldBe AppDestination.Second + expectMostRecentItem() shouldBe MainDestination.Second } } diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt index 940f5ee08..be93bcfd7 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppDestination.kt @@ -1,18 +1,10 @@ package co.nimblehq.template.compose.ui -import androidx.navigation.NamedNavArgument +import co.nimblehq.template.compose.ui.base.BaseDestination -sealed class AppDestination(val route: String = "") { +sealed class AppDestination { - open val arguments: List = emptyList() + object RootNavGraph : BaseDestination("rootNavGraph") - open var destination: String = route - - object Up : AppDestination() - - object RootNavGraph : AppDestination("rootNavGraph") - - object MainNavGraph : AppDestination("mainNavGraph") - - object Home : AppDestination("home") + object MainNavGraph : BaseDestination("mainNavGraph") } diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppNavGraph.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppNavGraph.kt index 30b82de12..f2b975f97 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppNavGraph.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/AppNavGraph.kt @@ -1,9 +1,13 @@ package co.nimblehq.template.compose.ui import androidx.compose.runtime.Composable -import androidx.navigation.* +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDeepLink +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import co.nimblehq.template.compose.ui.base.BaseDestination import co.nimblehq.template.compose.ui.screens.main.mainNavGraph @Composable @@ -20,7 +24,7 @@ fun AppNavigation( } fun NavGraphBuilder.composable( - destination: AppDestination, + destination: BaseDestination, deepLinks: List = emptyList(), content: @Composable (NavBackStackEntry) -> Unit, ) { @@ -32,9 +36,9 @@ fun NavGraphBuilder.composable( ) } -fun NavHostController.navigate(appDestination: AppDestination) { - when (appDestination) { - is AppDestination.Up -> navigateUp() - else -> navigate(route = appDestination.destination) +fun NavHostController.navigate(destination: BaseDestination) { + when (destination) { + is BaseDestination.Up -> navigateUp() + else -> navigate(route = destination.destination) } } diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseDestination.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseDestination.kt new file mode 100644 index 000000000..3247e7561 --- /dev/null +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseDestination.kt @@ -0,0 +1,12 @@ +package co.nimblehq.template.compose.ui.base + +import androidx.navigation.NamedNavArgument + +abstract class BaseDestination(val route: String = "") { + + open val arguments: List = emptyList() + + open var destination: String = route + + object Up : BaseDestination() +} diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseViewModel.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseViewModel.kt index b6f4f4068..44b27a425 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseViewModel.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseViewModel.kt @@ -2,7 +2,6 @@ package co.nimblehq.template.compose.ui.base import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import co.nimblehq.template.compose.ui.AppDestination import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlin.coroutines.CoroutineContext @@ -19,7 +18,7 @@ abstract class BaseViewModel : ViewModel() { protected val _error = MutableSharedFlow() val error = _error.asSharedFlow() - protected val _navigator = MutableSharedFlow() + protected val _navigator = MutableSharedFlow() val navigator = _navigator.asSharedFlow() /** diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainDestination.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainDestination.kt new file mode 100644 index 000000000..038759363 --- /dev/null +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainDestination.kt @@ -0,0 +1,8 @@ +package co.nimblehq.template.compose.ui.screens.main + +import co.nimblehq.template.compose.ui.base.BaseDestination + +sealed class MainDestination { + + object Home : BaseDestination("home") +} diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainNavGraph.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainNavGraph.kt index 329f77b78..b6230c0dd 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainNavGraph.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/MainNavGraph.kt @@ -1,7 +1,11 @@ package co.nimblehq.template.compose.ui.screens.main -import androidx.navigation.* -import co.nimblehq.template.compose.ui.* +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.navigation +import co.nimblehq.template.compose.ui.AppDestination +import co.nimblehq.template.compose.ui.composable +import co.nimblehq.template.compose.ui.navigate import co.nimblehq.template.compose.ui.screens.main.home.HomeScreen fun NavGraphBuilder.mainNavGraph( @@ -9,9 +13,9 @@ fun NavGraphBuilder.mainNavGraph( ) { navigation( route = AppDestination.MainNavGraph.route, - startDestination = AppDestination.Home.destination + startDestination = MainDestination.Home.destination ) { - composable(AppDestination.Home) { + composable(MainDestination.Home) { HomeScreen( navigator = { destination -> navController.navigate(destination) } ) diff --git a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreen.kt b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreen.kt index 5ad7fe7d1..dda7bd4d4 100644 --- a/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreen.kt +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreen.kt @@ -1,6 +1,10 @@ package co.nimblehq.template.compose.ui.screens.main.home -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -13,7 +17,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.nimblehq.template.compose.R import co.nimblehq.template.compose.extensions.collectAsEffect -import co.nimblehq.template.compose.ui.AppDestination +import co.nimblehq.template.compose.ui.base.BaseDestination import co.nimblehq.template.compose.ui.models.UiModel import co.nimblehq.template.compose.ui.showToast import co.nimblehq.template.compose.ui.theme.AppTheme.dimensions @@ -23,7 +27,7 @@ import timber.log.Timber @Composable fun HomeScreen( viewModel: HomeViewModel = hiltViewModel(), - navigator: (destination: AppDestination) -> Unit + navigator: (destination: BaseDestination) -> Unit, ) { val context = LocalContext.current viewModel.error.collectAsEffect { e -> e.showToast(context) } diff --git a/template-compose/app/src/test/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreenTest.kt b/template-compose/app/src/test/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreenTest.kt index 8532d8147..e3a2d12d2 100644 --- a/template-compose/app/src/test/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreenTest.kt +++ b/template-compose/app/src/test/java/co/nimblehq/template/compose/ui/screens/main/home/HomeScreenTest.kt @@ -7,7 +7,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import co.nimblehq.template.compose.R import co.nimblehq.template.compose.domain.usecases.UseCase import co.nimblehq.template.compose.test.MockUtil -import co.nimblehq.template.compose.ui.AppDestination +import co.nimblehq.template.compose.ui.base.BaseDestination import co.nimblehq.template.compose.ui.screens.BaseScreenTest import co.nimblehq.template.compose.ui.screens.MainActivity import co.nimblehq.template.compose.ui.theme.ComposeTheme @@ -31,7 +31,7 @@ class HomeScreenTest : BaseScreenTest() { private val mockUseCase: UseCase = mockk() private lateinit var viewModel: HomeViewModel - private var expectedAppDestination: AppDestination? = null + private var expectedDestination: BaseDestination? = null @Before fun setUp() { @@ -67,7 +67,7 @@ class HomeScreenTest : BaseScreenTest() { ComposeTheme { HomeScreen( viewModel = viewModel, - navigator = { destination -> expectedAppDestination = destination } + navigator = { destination -> expectedDestination = destination } ) } }