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>