Tim Malseed
11/25/2021, 3:48 AMjava.lang.NullPointerException
at androidx.compose.ui.layout.SubcomposeLayoutState.disposeCurrentNodes$ui_release(SubcomposeLayout.kt:386)
...
Tim Malseed
11/25/2021, 3:48 AM@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel(), onNavigate: (destination: NavigationDestination) -> Unit = {}) {
val authenticationState by viewModel.authenticationState.collectAsState()
when (authenticationState) {
is UserAuthenticationState.Undetermined -> {
LoadingScreen()
}
is UserAuthenticationState.Unauthenticated -> {
LaunchedEffect(authenticationState) {
onNavigate(NavigationDestination.Root.Onboarding)
}
}
is UserAuthenticationState.Authenticated -> {
HomeScreen()
}
}
}
@Composable
fun HomeScreen() {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
Scaffold(
bottomBar = {
BottomAppBar(
modifier = Modifier,
backgroundColor = MaterialColors.background
) {
BottomNav(currentDestination) { screen ->
navController.navigate(screen.route)
}
}
},
modifier = Modifier.semantics { testDescription = "Home Screen" }
) { padding ->
HomeNavHost(
navController = navController,
modifier = Modifier.padding(padding)
)
}
}
HomeNavHost:
@Composable
fun HomeNavHost(navController: NavHostController, modifier: Modifier = Modifier) {
NavHost(
navController = navController,
startDestination = NavigationDestination.Home.Coach.route,
modifier = modifier
) {
composable(NavigationDestination.Home.Coach.route) {
CoachScreen()
}
composable(NavigationDestination.Home.Discover.route) {
DiscoverScreen()
}
composable(NavigationDestination.Home.Library.route) {
LibraryScreen()
}
composable(NavigationDestination.Home.You.route) {
YouScreen()
}
composable(NavigationDestination.Home.Search.route) {
SearchScreen()
}
}
}
Tim Malseed
11/25/2021, 3:48 AMTim Malseed
11/25/2021, 3:49 AM@HiltAndroidTest
class HomeNavHostTest {
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<MainActivity>()
private lateinit var navController: NavHostController
@Before
fun setup() {
hiltRule.inject()
composeTestRule.setContent {
navController = rememberNavController()
HomeNavHost(navController = navController)
}
}
@Test
fun initialDestinationIsCoach() {
assert(navController.currentDestination?.route == NavigationDestination.Home.Coach.route)
}
@Test
fun navigateToCoachScreen() {
runBlocking {
withContext(Dispatchers.Main) {
navController.navigate(NavigationDestination.Home.Coach.route)
}
}
assert(navController.currentDestination?.route == NavigationDestination.Home.Coach.route)
}
...
UI Tests:
@HiltAndroidTest
class HomeScreenTest {
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<MainActivity>()
@Before
fun setup() {
hiltRule.inject()
composeTestRule.setContent {
HomeScreen()
}
}
@Test
fun bottomTabsAreDisplayed() {
composeTestRule.onNodeWithTestDescription("coach").assertIsDisplayed()
composeTestRule.onNodeWithTestDescription("discover").assertIsDisplayed()
composeTestRule.onNodeWithTestDescription("library").assertIsDisplayed()
composeTestRule.onNodeWithTestDescription("you").assertIsDisplayed()
composeTestRule.onNodeWithTestDescription("search").assertIsDisplayed()
}
@Test
fun initialDestinationIsCoach() {
composeTestRule
.onNodeWithTestDescription("Coach Screen")
.assertIsDisplayed()
}
...
Tim Malseed
11/25/2021, 3:50 AMTim Malseed
11/25/2021, 3:50 AMTim Malseed
11/25/2021, 3:50 AMTim Malseed
11/25/2021, 3:51 AME/AndroidJUnitRunner: An unhandled exception was thrown by the app.
java.lang.NullPointerException
at androidx.compose.ui.layout.SubcomposeLayoutState.disposeCurrentNodes$ui_release(SubcomposeLayout.kt:386)
at androidx.compose.ui.layout.SubcomposeLayoutKt$SubcomposeLayout$3$invoke$$inlined$onDispose$1.dispose(Effects.kt:484)
at androidx.compose.runtime.DisposableEffectImpl.onForgotten(Effects.kt:85)
at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:793)
at androidx.compose.runtime.CompositionImpl.dispose(Composition.kt:496)
at androidx.compose.ui.platform.WrappedComposition.dispose(Wrapper.android.kt:171)
at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:179)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
at androidx.lifecycle.LifecycleRegistry.backwardPass(LifecycleRegistry.java:284)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:302)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:148)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
at androidx.lifecycle.ReportFragment.dispatch(ReportFragment.java:68)
at androidx.lifecycle.ReportFragment$LifecycleCallbacks.onActivityPreDestroyed(ReportFragment.java:224)
at android.app.Activity.dispatchActivityPreDestroyed(Activity.java:1516)
at android.app.Activity.performDestroy(Activity.java:8312)
at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1364)
at androidx.test.runner.MonitoringInstrumentation.callActivityOnDestroy(MonitoringInstrumentation.java:756)
at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:5374)
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:5420)
at android.app.servertransaction.DestroyActivityItem.execute(DestroyActivityItem.java:47)
at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2210)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7839)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Tim Malseed
11/25/2021, 4:16 AMTim Malseed
11/25/2021, 4:18 AMTim Malseed
11/25/2021, 4:41 AMsetContent()
call in MainActivity
(the Activity being used by the createAndroidComposeRule
, due to the presence of Hilt), the exception moves elsewhere:Tim Malseed
11/25/2021, 4:41 AMjava.lang.NullPointerException: Attempt to invoke virtual method 'androidx.compose.ui.graphics.vector.ImageVector com.myapp.android.ui.components.common.NavigationDestination$Home.getImage()' on a null object reference
at com.myapp.android.ui.components.home.HomeScreenKt$BottomNav$2$1$2.invoke(HomeScreen.kt:90)
at com.myapp.android.ui.components.home.HomeScreenKt$BottomNav$2$1$2.invoke(HomeScreen.kt:88)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
Tim Malseed
11/25/2021, 4:42 AMimageVector = screen.image,
In BottomNav:
@Composable
fun BottomNav(currentDestination: NavDestination?, onItemClick: (NavigationDestination) -> Unit = {}) {
BottomNavigation(
modifier = Modifier,
backgroundColor = MaterialColors.surface
) {
NavigationDestination.Home.all.forEach { screen ->
val selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true
BottomNavigationItem(
selected = selected,
alwaysShowLabel = selected,
icon = {
Icon(
imageVector = screen.image,
contentDescription = stringResource(id = screen.titleResId),
Modifier.semantics { testDescription = screen.route }
)
},
label = { Text(text = stringResource(id = screen.titleResId)) },
selectedContentColor = MaterialColors.onBackground,
unselectedContentColor = MaterialColors.onBackground.copy(alpha = ContentAlpha.medium),
onClick = { onItemClick(screen) }
)
}
}
}
Tim Malseed
11/25/2021, 4:44 AMsealed class Home(
override val route: String,
@StringRes val titleResId: Int,
val image: ImageVector
) : NavigationDestination(route) {
object Coach : Home(route = "coach", titleResId = R.string.home_tab_coach, image = Icons.Outlined.EmojiPeople)
object Discover : Home(route = "discover", titleResId = R.string.home_tab_discover, image = Icons.Outlined.Explore)
object Library : Home(route = "library", titleResId = R.string.home_tab_library, image = Icons.Outlined.GridView)
object You : Home(route = "you", titleResId = R.string.home_tab_you, image = Icons.Outlined.PersonOutline)
object Search : Home(route = "search", titleResId = R.string.home_tab_search, image = Icons.Outlined.Search)
companion object {
val all: List<Home> = listOf(Coach, Discover, Library, You, Search)
}
}
Tim Malseed
11/25/2021, 4:52 AMTim Malseed
11/25/2021, 4:55 AMTim Malseed
11/25/2021, 4:55 AMZach Klippenstein (he/him) [MOD]
11/25/2021, 7:57 PMTim Malseed
11/25/2021, 10:10 PMTim Malseed
11/25/2021, 10:12 PMZach Klippenstein (he/him) [MOD]
11/29/2021, 1:14 AMTim Malseed
11/29/2021, 1:31 AMTim Malseed
11/30/2021, 2:12 AMconnectedAndroidTest
Tim Malseed
11/30/2021, 2:12 AM