fix(splash): navigate to the first screen once, so a recreated activity cannot crash the app (#26)

* test(splash): the initial destination is navigated to once

* fix(splash): navigate to the first screen once

The splash screen asks for the initial destination on every onResume, which
runs again when the activity is recreated during startup while the view model
survives. The second navigation found the first already on the welcome screen,
with no route from there, and crashed the app with 'Unknown case for
welcomeFragment'. The view model now starts it once.
This commit is contained in:
SatoshiQaziMuhammed authored and GitHub committed 2026-10-18 16:04:52 +04:00
1 parent 280ccc3fd6
commit 5f96f63330
3 files changed
+60 -1

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ dependencies {
testImplementation jUnitDep testImplementation jUnitDep
testImplementation mockitoDep testImplementation mockitoDep
testImplementation project(':test-shared')
implementation coroutinesDep implementation coroutinesDep
implementation coroutinesAndroidDep implementation coroutinesAndroidDep
@@ -5,6 +5,7 @@ import io.novafoundation.nova.common.base.BaseViewModel
import io.novafoundation.nova.common.utils.splash.SplashPassedObserver import io.novafoundation.nova.common.utils.splash.SplashPassedObserver
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.splash.SplashRouter import io.novafoundation.nova.splash.SplashRouter
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SplashViewModel( class SplashViewModel(
@@ -13,8 +14,16 @@ class SplashViewModel(
private val splashPassedObserver: SplashPassedObserver private val splashPassedObserver: SplashPassedObserver
) : BaseViewModel() { ) : BaseViewModel() {
private var initialDestinationJob: Job? = null
// Called from the splash screen's onResume, which runs again when the activity is
// recreated (rotation, theme, locale) while this view model survives. Starting a
// second navigation then finds the first one already on the welcome screen, which
// has no route to welcome, and crashes the app. The first call decides.
fun openInitialDestination() { fun openInitialDestination() {
viewModelScope.launch { if (initialDestinationJob != null) return
initialDestinationJob = viewModelScope.launch {
if (repository.isAccountSelected()) { if (repository.isAccountSelected()) {
openPinCode() openPinCode()
} else { } else {
@@ -0,0 +1,49 @@
package io.novafoundation.nova.splash.presentation
import io.novafoundation.nova.common.utils.splash.SplashPassedObserver
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
import io.novafoundation.nova.splash.SplashRouter
import io.novafoundation.nova.test_shared.whenever
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
@OptIn(ExperimentalCoroutinesApi::class)
class SplashViewModelTest {
private val router = mock(SplashRouter::class.java)
private val repository = mock(AccountRepository::class.java)
private val splashPassedObserver = mock(SplashPassedObserver::class.java)
@Before
fun setUp() {
Dispatchers.setMain(UnconfinedTestDispatcher())
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
// The splash screen asks again on every onResume, including the one after the
// activity is recreated; navigating twice crashed the app on the welcome screen.
@Test
fun `navigates to the welcome screen once however often it is asked`() {
runBlocking { whenever(repository.isAccountSelected()).thenReturn(false) }
val viewModel = SplashViewModel(router, repository, splashPassedObserver)
viewModel.openInitialDestination()
viewModel.openInitialDestination()
verify(router, times(1)).openWelcomeScreen()
}
}