Splash screen keeps showing on the screen

Viewed 74

I'm using SplashScreen API. When the app launches, I show the SplashScreen then I navigate the user to the correct destination based on some conditions. Here is the logic :


@HiltViewModel
class SplashViewModel @Inject constructor(
    private val splashUseCases: SplashUseCases
) : ViewModel() {

    private val _isLoading = MutableStateFlow(true)
    val isLoading = _isLoading.asStateFlow()

    private val _navDestination = MutableStateFlow(SplashNavDestination.IDLE)
    val navDestination = _navDestination.asStateFlow()

    private val _errorFlow = MutableStateFlow<UiText?>(null)
    val errorFlow = _errorFlow.asStateFlow()

    init {
        val rememberMe = splashUseCases.getRememberMeUseCase()
 
        if (rememberMe) {
     
            val refreshToken = splashUseCases.getRefreshTokenUseCase()
            if (refreshToken.isEmpty()) {
                clearPreferencesThenNavigateToAuthGraph()
            } else {
                viewModelScope.launch(Dispatchers.Main) {
                    val refreshTokenType = RefreshTokenType(refreshToken = refreshToken)
                    splashUseCases.refreshTokenUseCase(refreshTokenType).collect {
                        when (it) {
                           
                            is Resource.Error -> {
                                if (it.errorCode != 401) {
                                    _errorFlow.value = it.error
                                }
                                clearPreferencesThenNavigateToAuthGraph()
                            }
                            is Resource.Loading -> _isLoading.value = true
                            is Resource.Success -> {
                                val superUser = it.data?.roles?.contains(
                                    Role.SUPER_ADMIN_USER
                                ) ?: false

                                if (superUser) {
                                    runBlocking {
                                        splashUseCases.clearCompanyStorageManagerUseCase()
                                    }
                                }
                                splashUseCases.saveTokenUseCase(it.data?.token.toString())
                                splashUseCases.saveRefreshTokenUseCase(it.data?.refreshToken.toString())
                                splashUseCases.saveIsSuperUserUseCase(superUser)
                                if (!superUser) {
                                    it.data?.company?.name?.let { name ->
                                        splashUseCases.saveCompanyNameUseCase(name)
                                    }
                                }
                                _navDestination.value = SplashNavDestination.HOME
                                _isLoading.value = false
                            }
                        }
                    }
                }
            }
        } else { 
            clearPreferencesThenNavigateToAuthGraph()
        }
    }

    private fun clearPreferencesThenNavigateToAuthGraph() {
        runBlocking {
            splashUseCases.clearCompanyStorageManagerUseCase()
        }
        splashUseCases.clearAuthCacheUseCase()
        _navDestination.value = SplashNavDestination.AUTH
        _isLoading.value = false
    }
}

So first of all I check if the the user checked to remember me option which I need for auto login when the user comes back to the app for the second time. If this option is checked then I refresh the token and then navigate the user to the Main flow. If any error occurs while refreshing the token I clear everything that I store in the local storage and then navigate the user to the login screen. Here is also my activity :


@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@AndroidEntryPoint
class AuthActivity : ComponentActivity() {

    private val splashViewModel: SplashViewModel by viewModels()
    private lateinit var splashScreen: SplashScreen

    override fun onCreate(savedInstanceState: Bundle?) {
        splashScreen = installSplashScreen()
        super.onCreate(savedInstanceState)
        setContent {
            val errorMessage by splashViewModel.errorFlow.collectAsState()
            val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
            RubiBrandsTheme(widthSizeClass = widthSizeClass) {
                splashScreen.setKeepOnScreenCondition {
                    //keeps the splash screen on the screen
                    //while the condition is true
                    splashViewModel.isLoading.value
                }
                errorMessage?.asString(this)?.let {
                    ErrorDialog(message = it)
                }
                AuthNavGraph(
                    navController = rememberAnimatedNavController(),
                    activity = this@AuthActivity
                )
            }
            splashScreen.setOnExitAnimationListener {
                val startDestination = splashViewModel.navDestination.value.route
                if (startDestination == AUTH_GRAPH) {
                    it.remove()
                } else {
                    val intent = Intent(this@AuthActivity, MainActivity::class.java)
                    startActivity(intent)
                    finish()
                }
            }
        }
    }
}

I keep showing the splash screen on the screen using:

 splashScreen.setKeepOnScreenCondition {                  
                    splashViewModel.isLoading.value
                }

while I refresh the token.

And then I check the start destination then navigate the user to the destination :

   splashScreen.setOnExitAnimationListener {
                val startDestination = splashViewModel.navDestination.value.route
                if (startDestination == AUTH_GRAPH) {
                    it.remove()
                } else {
                    val intent = Intent(this@AuthActivity, MainActivity::class.java)
                    startActivity(intent)
                    finish()
                }
            }

It works as intended except for the error case. When I got an error while refreshing the token, splash screen keeps showing and not removing from the screen. Here is the behavior :

Lastly here is also my theme and the manifest files :

theme.xml


<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="Theme.App.Starting" parent="Theme.SplashScreen">
        <item name="windowSplashScreenBackground">@color/splash_bg_light</item>
        <item name="windowSplashScreenAnimatedIcon">@drawable/ic_splash_logo</item>

        <item name="postSplashScreenTheme">@style/Theme.RubiBrands</item>
    </style>
</resources>

AndroidManifest.xml

    <application
        android:theme="@style/Theme.RubiBrands"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/title_activity_main"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustResize"
             />
        <activity
            android:name=".AuthActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.App.Starting"
            android:screenOrientation="portrait"
            >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

1 Answers

I have moved the setOnExitAnimationListener and setKeepOnScreenCondition out of the setContent block and the issue has been resolved. The code is :

    override fun onCreate(savedInstanceState: Bundle?) {
        splashScreen = installSplashScreen()
        super.onCreate(savedInstanceState)
        splashScreen.setKeepOnScreenCondition {
            splashViewModel.isLoading.value
        }
        splashScreen.setOnExitAnimationListener {
            val startDestination = splashViewModel.navDestination.value.route
            if (startDestination == AUTH_GRAPH) {
                it.remove()
            } else {
                val intent = Intent(this@AuthActivity, MainActivity::class.java)
                startActivity(intent)
                finish()
            }
        }
        setContent {
            val errorMessage by splashViewModel.errorFlow.collectAsState()
            val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
            RubiBrandsTheme(widthSizeClass = widthSizeClass) {
                errorMessage?.asString(this@AuthActivity)?.let {
                    ErrorDialog(message = it)
                }
                AuthNavGraph(
                    navController = rememberAnimatedNavController(),
                    activity = this@AuthActivity
                )

            }
        }
    }

Related