Skip to content

Commit

Permalink
Merge pull request #529 from nimblehq/chore/improve-the-base-navigation
Browse files Browse the repository at this point in the history
[#552] Improve the base navigation with a "MainNavGraph"
  • Loading branch information
ryan-conway authored Dec 6, 2023
2 parents df2fc0b + efe2ee9 commit db35b24
Show file tree
Hide file tree
Showing 33 changed files with 332 additions and 220 deletions.
Original file line number Diff line number Diff line change
@@ -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",
),
)
}
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -79,7 +84,7 @@ class HomeScreenTest {
ComposeTheme {
HomeScreen(
viewModel = viewModel,
navigator = { destination -> expectedAppDestination = destination }
navigator = { destination -> expectedDestination = destination }
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +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.ui.models.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<NamedNavArgument> = emptyList()

open var destination: String = route

open var parcelableArgument: Pair<String, Any?> = "" to null

object Up : AppDestination()

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")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package co.nimblehq.sample.compose.ui

import androidx.compose.runtime.Composable
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.sample.compose.ui.base.BaseDestination
import co.nimblehq.sample.compose.ui.screens.main.mainNavGraph

@Composable
fun AppNavigation(
navController: NavHostController,
) {
NavHost(
navController = navController,
route = AppDestination.RootNavGraph.route,
startDestination = AppDestination.MainNavGraph.destination
) {
mainNavGraph(navController = navController)
}
}

fun NavGraphBuilder.composable(
destination: BaseDestination,
deepLinks: List<NavDeepLink> = emptyList(),
content: @Composable (NavBackStackEntry) -> Unit,
) {
composable(
route = destination.route,
arguments = destination.arguments,
deepLinks = deepLinks,
content = content
)
}

/**
* 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(destination: BaseDestination, parcel: Pair<String, Any?>? = null) {
when (destination) {
is BaseDestination.Up -> navigateUp()
else -> {
parcel?.let { (key, value) ->
currentBackStackEntry?.savedStateHandle?.set(key, value)
}
navigate(route = destination.destination)
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<NamedNavArgument> = emptyList()

open var destination: String = route

open var parcelableArgument: Pair<String, Any?> = "" to null

object Up : BaseDestination()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -19,7 +18,7 @@ abstract class BaseViewModel : ViewModel() {
protected val _error = MutableSharedFlow<Throwable>()
val error = _error.asSharedFlow()

protected val _navigator = MutableSharedFlow<AppDestination>()
protected val _navigator = MutableSharedFlow<BaseDestination>()
val navigator = _navigator.asSharedFlow()

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package co.nimblehq.sample.compose.ui.screens
package co.nimblehq.sample.compose.ui.common

import androidx.annotation.StringRes
import androidx.compose.material.Text
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package co.nimblehq.sample.compose.ui.screens
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.navigation.compose.rememberNavController
import co.nimblehq.sample.compose.ui.AppNavigation
import co.nimblehq.sample.compose.ui.theme.ComposeTheme
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -14,7 +15,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
ComposeTheme {
AppNavigation()
AppNavigation(navController = rememberNavController())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.ui.base.BaseDestination
import co.nimblehq.sample.compose.ui.models.UiModel

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
}
}
}
Loading

0 comments on commit db35b24

Please sign in to comment.