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/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/AppDestination.kt b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/AppDestination.kt index fcf7281f3..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,54 +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" -const val KeyResultOk = "keyResultOk" +sealed class AppDestination { -sealed class AppDestination(val route: String = "") { + object RootNavGraph : BaseDestination("rootNavGraph") - open val arguments: List = emptyList() - - open val deepLinks: List = listOf( - "https://android.nimblehq.co/$route", - "android://$route", - ) - - open var destination: String = route - - open var parcelableArgument: Pair = "" to null - - data class Up(val results: List = emptyList()) : 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") } - -data class Result( - val key: String, - val value: Any, -) 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 87d711f96..4782b215d 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.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navDeepLink +import co.nimblehq.sample.compose.ui.base.BaseDestination import co.nimblehq.sample.compose.ui.screens.main.mainNavGraph @Composable @@ -23,7 +24,7 @@ fun AppNavGraph( } fun NavGraphBuilder.composable( - destination: AppDestination, + destination: BaseDestination, content: @Composable (NavBackStackEntry) -> Unit, ) { composable( @@ -39,25 +40,24 @@ 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 -> { - appDestination.results.forEach { (key, value) -> +fun NavHostController.navigate(destination: BaseDestination, parcel: Pair? = null) { + when (destination) { + is BaseDestination.Up -> { + destination.results.forEach { (key, value) -> previousBackStackEntry?.savedStateHandle?.set(key, value) } 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..03af800b4 --- /dev/null +++ b/sample-compose/app/src/main/java/co/nimblehq/sample/compose/ui/base/BaseDestination.kt @@ -0,0 +1,26 @@ +package co.nimblehq.sample.compose.ui.base + +import androidx.navigation.NamedNavArgument + +const val KeyResultOk = "keyResultOk" + +abstract class BaseDestination(val route: String = "") { + + open val arguments: List = emptyList() + + open val deepLinks: List = listOf( + "https://android.nimblehq.co/$route", + "android://$route", + ) + + open var destination: String = route + + open var parcelableArgument: Pair = "" to null + + data class Up(val results: HashMap = hashMapOf()) : BaseDestination() { + + fun addResult(key: String, value: Any) = apply { + results[key] = value + } + } +} 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 67a9aa77e..99982c88a 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,12 +3,10 @@ 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 -import co.nimblehq.sample.compose.ui.KeyModel -import co.nimblehq.sample.compose.ui.KeyResultOk +import co.nimblehq.sample.compose.ui.base.KeyResultOk import co.nimblehq.sample.compose.ui.composable import co.nimblehq.sample.compose.ui.navigate import co.nimblehq.sample.compose.ui.screens.main.home.HomeScreen @@ -20,11 +18,11 @@ fun NavGraphBuilder.mainNavGraph( ) { navigation( route = AppDestination.MainNavGraph.route, - startDestination = AppDestination.Home.destination + startDestination = MainDestination.Home.destination ) { - composable(destination = AppDestination.Home) { backStackEntry -> + composable(destination = MainDestination.Home) { backStackEntry -> val isResultOk = backStackEntry.savedStateHandle - .getAndRemove(KeyResultOk) ?: false + .getThenRemove(KeyResultOk) ?: false HomeScreen( navigator = { destination -> navController.navigate(destination, destination.parcelableArgument) @@ -33,14 +31,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 5fbdce641..b43dfeb3e 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, isResultOk: Boolean = false, ) { val context = LocalContext.current 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 be00e0cf7..605d093db 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 @@ -14,9 +14,8 @@ 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.KeyResultOk -import co.nimblehq.sample.compose.ui.Result +import co.nimblehq.sample.compose.ui.base.BaseDestination +import co.nimblehq.sample.compose.ui.base.KeyResultOk 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 @@ -24,22 +23,13 @@ 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 = id, onUpdateClick = { - navigator( - AppDestination.Up( - listOf( - Result( - key = KeyResultOk, - value = true, - ), - ) - ) - ) + navigator(BaseDestination.Up().addResult(KeyResultOk, true)) }, ) } 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/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) 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 e30ce5b60..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,25 +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 val deepLinks: List = emptyList() - - open var destination: String = route - - data class Up(val results: List = emptyList()) : AppDestination() - - object RootNavGraph : AppDestination("rootNavGraph") - - object MainNavGraph : AppDestination("mainNavGraph") - - object Home : AppDestination("home") + object MainNavGraph : BaseDestination("mainNavGraph") } - -data class Result( - val key: String, - val value: Any, -) 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 6e926218f..e2c662a68 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.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable +import androidx.navigation.navDeepLink +import co.nimblehq.template.compose.ui.base.BaseDestination import co.nimblehq.template.compose.ui.screens.main.mainNavGraph @Composable @@ -20,7 +24,7 @@ fun AppNavGraph( } fun NavGraphBuilder.composable( - destination: AppDestination, + destination: BaseDestination, content: @Composable (NavBackStackEntry) -> Unit, ) { composable( @@ -35,14 +39,14 @@ fun NavGraphBuilder.composable( ) } -fun NavHostController.navigate(appDestination: AppDestination) { - when (appDestination) { - is AppDestination.Up -> { - appDestination.results.forEach { (key, value) -> +fun NavHostController.navigate(destination: BaseDestination) { + when (destination) { + is BaseDestination.Up -> { + destination.results.forEach { (key, value) -> previousBackStackEntry?.savedStateHandle?.set(key, value) } navigateUp() } - else -> navigate(route = appDestination.destination) + 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..9efb868b2 --- /dev/null +++ b/template-compose/app/src/main/java/co/nimblehq/template/compose/ui/base/BaseDestination.kt @@ -0,0 +1,19 @@ +package co.nimblehq.template.compose.ui.base + +import androidx.navigation.NamedNavArgument + +abstract class BaseDestination(val route: String = "") { + + open val arguments: List = emptyList() + + open val deepLinks: List = emptyList() + + open var destination: String = route + + data class Up(val results: HashMap = hashMapOf()) : BaseDestination() { + + fun addResult(key: String, value: Any) = apply { + results[key] = value + } + } +} 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 } ) } }