Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AndroidX navigation + :api/impl split for feature modules #2

Draft
wants to merge 9 commits into
base: ab/bookmarks-app-impl-split
Choose a base branch
from
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ android {
dependencies {
implementation(projects.feature.interests)
implementation(projects.feature.foryou)
implementation(projects.feature.bookmarks.api)
implementation(projects.feature.bookmarks.impl)
implementation(projects.feature.topic)
implementation(projects.feature.search)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,125 +40,125 @@ import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

/**
* Tests [NiaAppState].
*
* Note: This could become an unit test if Robolectric is added to the project and the Context
* is faked.
*/
class NiaAppStateTest {

@get:Rule
val composeTestRule = createComposeRule()

// Create the test dependencies.
private val networkMonitor = TestNetworkMonitor()

private val timeZoneMonitor = TestTimeZoneMonitor()

private val userNewsResourceRepository =
CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository())

// Subject under test.
private lateinit var state: NiaAppState

@Test
fun niaAppState_currentDestination() = runTest {
var currentDestination: String? = null

composeTestRule.setContent {
val navController = rememberTestNavController()
state = remember(navController) {
NiaAppState(
navController = navController,
coroutineScope = backgroundScope,
networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
}

// Update currentDestination whenever it changes
currentDestination = state.currentDestination?.route

// Navigate to destination b once
LaunchedEffect(Unit) {
navController.setCurrentDestination("b")
}
}

assertEquals("b", currentDestination)
}

@Test
fun niaAppState_destinations() = runTest {
composeTestRule.setContent {
state = rememberNiaAppState(
networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
}

assertEquals(3, state.topLevelDestinations.size)
assertTrue(state.topLevelDestinations[0].name.contains("for_you", true))
assertTrue(state.topLevelDestinations[1].name.contains("bookmarks", true))
assertTrue(state.topLevelDestinations[2].name.contains("interests", true))
}

@Test
fun niaAppState_whenNetworkMonitorIsOffline_StateIsOffline() = runTest(UnconfinedTestDispatcher()) {
composeTestRule.setContent {
state = NiaAppState(
navController = NavHostController(LocalContext.current),
coroutineScope = backgroundScope,
networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
}

backgroundScope.launch { state.isOffline.collect() }
networkMonitor.setConnected(false)
assertEquals(
true,
state.isOffline.value,
)
}

@Test
fun niaAppState_differentTZ_withTimeZoneMonitorChange() = runTest(UnconfinedTestDispatcher()) {
composeTestRule.setContent {
state = NiaAppState(
navController = NavHostController(LocalContext.current),
coroutineScope = backgroundScope,
networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
}
val changedTz = TimeZone.of("Europe/Prague")
backgroundScope.launch { state.currentTimeZone.collect() }
timeZoneMonitor.setTimeZone(changedTz)
assertEquals(
changedTz,
state.currentTimeZone.value,
)
}
}

@Composable
private fun rememberTestNavController(): TestNavHostController {
val context = LocalContext.current
return remember {
TestNavHostController(context).apply {
navigatorProvider.addNavigator(ComposeNavigator())
graph = createGraph(startDestination = "a") {
composable("a") { }
composable("b") { }
composable("c") { }
}
}
}
}
//
///**
// * Tests [NiaAppState].
// *
// * Note: This could become an unit test if Robolectric is added to the project and the Context
// * is faked.
// */
//class NiaAppStateTest {
//
// @get:Rule
// val composeTestRule = createComposeRule()
//
// // Create the test dependencies.
// private val networkMonitor = TestNetworkMonitor()
//
// private val timeZoneMonitor = TestTimeZoneMonitor()
//
// private val userNewsResourceRepository =
// CompositeUserNewsResourceRepository(TestNewsRepository(), TestUserDataRepository())
//
// // Subject under test.
// private lateinit var state: NiaAppState
//
// @Test
// fun niaAppState_currentDestination() = runTest {
// var currentDestination: String? = null
//
// composeTestRule.setContent {
// val navController = rememberTestNavController()
// state = remember(navController) {
// NiaAppState(
// navController = navController,
// coroutineScope = backgroundScope,
// networkMonitor = networkMonitor,
// userNewsResourceRepository = userNewsResourceRepository,
// timeZoneMonitor = timeZoneMonitor,
// )
// }
//
// // Update currentDestination whenever it changes
// currentDestination = state.currentDestination?.route
//
// // Navigate to destination b once
// LaunchedEffect(Unit) {
// navController.setCurrentDestination("b")
// }
// }
//
// assertEquals("b", currentDestination)
// }
//
// @Test
// fun niaAppState_destinations() = runTest {
// composeTestRule.setContent {
// state = rememberNiaAppState(
// networkMonitor = networkMonitor,
// userNewsResourceRepository = userNewsResourceRepository,
// timeZoneMonitor = timeZoneMonitor,
// )
// }
//
// assertEquals(3, state.topLevelDestinations.size)
// assertTrue(state.topLevelDestinations[0].name.contains("for_you", true))
// assertTrue(state.topLevelDestinations[1].name.contains("bookmarks", true))
// assertTrue(state.topLevelDestinations[2].name.contains("interests", true))
// }
//
// @Test
// fun niaAppState_whenNetworkMonitorIsOffline_StateIsOffline() = runTest(UnconfinedTestDispatcher()) {
// composeTestRule.setContent {
// state = NiaAppState(
// navController = NavHostController(LocalContext.current),
// coroutineScope = backgroundScope,
// networkMonitor = networkMonitor,
// userNewsResourceRepository = userNewsResourceRepository,
// timeZoneMonitor = timeZoneMonitor,
// )
// }
//
// backgroundScope.launch { state.isOffline.collect() }
// networkMonitor.setConnected(false)
// assertEquals(
// true,
// state.isOffline.value,
// )
// }
//
// @Test
// fun niaAppState_differentTZ_withTimeZoneMonitorChange() = runTest(UnconfinedTestDispatcher()) {
// composeTestRule.setContent {
// state = NiaAppState(
// navController = NavHostController(LocalContext.current),
// coroutineScope = backgroundScope,
// networkMonitor = networkMonitor,
// userNewsResourceRepository = userNewsResourceRepository,
// timeZoneMonitor = timeZoneMonitor,
// )
// }
// val changedTz = TimeZone.of("Europe/Prague")
// backgroundScope.launch { state.currentTimeZone.collect() }
// timeZoneMonitor.setTimeZone(changedTz)
// assertEquals(
// changedTz,
// state.currentTimeZone.value,
// )
// }
//}
//
//@Composable
//private fun rememberTestNavController(): TestNavHostController {
// val context = LocalContext.current
// return remember {
// TestNavHostController(context).apply {
// navigatorProvider.addNavigator(ComposeNavigator())
// graph = createGraph(startDestination = "a") {
// composable("a") { }
// composable("b") { }
// composable("c") { }
// }
// }
// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.model.data.DarkThemeConfig
import com.google.samples.apps.nowinandroid.core.model.data.ThemeBrand
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigatorProvider
import com.google.samples.apps.nowinandroid.core.ui.LocalTimeZone
import com.google.samples.apps.nowinandroid.ui.NiaApp
import com.google.samples.apps.nowinandroid.ui.rememberNiaAppState
Expand Down Expand Up @@ -75,6 +76,9 @@ class MainActivity : ComponentActivity() {
@Inject
lateinit var userNewsResourceRepository: UserNewsResourceRepository

@Inject
lateinit var navigatorProvider: NiaNavigatorProvider

private val viewModel: MainActivityViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -130,6 +134,7 @@ class MainActivity : ComponentActivity() {

val appState = rememberNiaAppState(
networkMonitor = networkMonitor,
navigatorProvider = navigatorProvider,
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.google.samples.apps.nowinandroid.di

import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigatorProvider
import com.google.samples.apps.nowinandroid.navigation.NiaNavigatorProviderImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Module
@InstallIn(SingletonComponent::class)
internal interface NavigationSingletonModule {
@Binds
fun bindNavigatorProvider(impl: NiaNavigatorProviderImpl): NiaNavigatorProvider
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ package com.google.samples.apps.nowinandroid.navigation
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.BookmarksNavigator
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouBaseRoute
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouSection
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.ForYouNavigator
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests
import com.google.samples.apps.nowinandroid.feature.search.navigation.searchScreen
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
Expand Down Expand Up @@ -49,18 +49,29 @@ fun NiaNavHost(
startDestination = ForYouBaseRoute,
modifier = modifier,
) {
forYouSection(
onTopicClick = navController::navigateToTopic,
) {
topicScreen(
showBackButton = true,
onBackClick = navController::popBackStack,
appState.navigatorProvider.get(ForYouNavigator::class.java).screen(
navGraphBuilder = this,
navController = navController,
actions = ForYouNavigator.Actions(
onTopicClick = navController::navigateToTopic,
)
}
bookmarksScreen(
onTopicClick = navController::navigateToInterests,
onShowSnackbar = onShowSnackbar,
topicDestination = {
topicScreen(
showBackButton = true,
onBackClick = navController::popBackStack,
onTopicClick = navController::navigateToTopic,
)
},
),
properties = Unit,
)
appState.navigatorProvider.get(BookmarksNavigator::class.java).screen(
navGraphBuilder = this,
navController = navController,
actions = BookmarksNavigator.Actions(
onTopicClick = navController::navigateToTopic,
onShowSnackbar = onShowSnackbar,
),
properties = Unit,
)
searchScreen(
onBackClick = navController::popBackStack,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.google.samples.apps.nowinandroid.navigation

import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigator
import com.google.samples.apps.nowinandroid.core.navigation.NiaNavigatorProvider
import javax.inject.Inject

internal class NiaNavigatorProviderImpl @Inject constructor(
// Key should be the Class for the navigator, value should be the navigator instance itself
private val navigators: Map<@JvmSuppressWildcards Class<*>, @JvmSuppressWildcards NiaNavigator<*, *, *>>,
): NiaNavigatorProvider {
override fun <T : NiaNavigator<out Any, out Any, out Any>> get(clazz: Class<T>): T {
return navigators[clazz] as T
}
}
Loading