Android 12 Splash Screen API inconsistent behavior

3.4k Views Asked by At

I am implementing the new Splash Screen API but encountered an inconsistent behavior on it. Sometimes the screen with app icon shows and sometimes it doesn't. There is also a long white screen on the beginning which is visibly annoying (The attached image was just speed up 3x since I cannot upload image file higher than 2mb here, but the white screen was clearly visible for a couple of seconds and Splash API seems to cause frame skip from Choreographer log).

Samsung J1 Android L

enter image description here

class LauncherActivity : AppCompatActivity() {

private var keepSplash = true

private lateinit var splashScreen: SplashScreen

override fun onCreate(savedInstanceState: Bundle?) {

        splashScreen = installSplashScreen().apply {
            // Behaves like observable, used to check if splash screen should be keep or not
            setKeepOnScreenCondition {
                keepSplash
            }
            setOnExitAnimationListener { sp ->
                sp.remove() // Remove splash screen
            }
        }

        super.onCreate(savedInstanceState)

}

fun fetchData() { 
   //Fetching network data... 
   keepSplash = false
}

Showing the AlertDialog seems not working unless I minimize the app and reopen it with setKeepOnScreenCondition. It seems to block the UI thread, is there other way to retain the splash but not a blocking UI one? Currently we need to show an AlertDialog if something went wrong but at the same time the splash screen will be retain until the dialog is dismiss.

2

There are 2 best solutions below

2
On BEST ANSWER

I solved the issue, first if you want to keep the splash icon screen on user screen you need to use both setKeepOnScreenCondition and setOnExitAnimationListener

splashScreen.apply {
            // Behaves like observable, used to check if splash screen should be keep or not
            setKeepOnScreenCondition {
                keepSplash // True to keep the screen, False to remove it
            }
            setOnExitAnimationListener { splashScreenViewProvider ->
                // Do nothing so the splash screen will remain visible
            }
        }

or just

splashScreen.setOnExitAnimationListener {

            // Do nothing so the splash screen will remain visible
            splashScreenViewProvider = it

        }

Then just call splashScreenViewProvider.remove() later when you are done.

Just remember that setKeepOnScreenCondition can be a UI blocking thread so if ever you are fetching some data during splash screen and showed an error message via dialog, Toast, or SnackBar it wont work. You need to set setKeepOnScreenCondition to false first.

The role of empty setOnExitAnimationListener here is not to remove the splash screen even after setting a false condition on setKeepOnScreenCondition.

UPDATED

It is probably best to just use and empty setOnExitAnimationListener if you want to control and extend the Splash Screen. Then save its splashScreenViewProvider in a variable and use it later to control or dismiss the screen by calling remove(). Documentation is here.

There might times where the splash logo may not show due to how it works with hot start and cold start. https://developer.android.com/develop/ui/views/launch/splash-screen#how

Note that there still issue when installing and running the app directly using USB debugging in Android Studio where the SplashScreen never showed up and stuck in empty screen which can be problematic when you are using Firebase Test Lab. The issue only happens in SDK 31 and 32.

https://issuetracker.google.com/issues/197906327

0
On

I am using jetpack compose and it working fine

 override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)

var uiState: MainActivityUiState by mutableStateOf(MainActivityUiState.Loading)

// Update the uiState
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.uiState
            .onEach { uiState = it }
            .collect()
    }
}

// Keep the splash screen on-screen until the UI state is loaded. This condition is
// evaluated each time the app needs to be redrawn so it should be fast to avoid blocking
// the UI.
splashScreen.setKeepOnScreenCondition {
    when (uiState) {
         MainActivityUiState.Loading -> true
        is MainActivityUiState.Success -> false
    }
}

setContent {
    if(uiState is MainActivityUiState.Success){
        MainScreen((uiState as MainActivityUiState.Success).route)
    }

}


@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {

    @Inject
    lateinit var preferenceDataStoreHelper: MyPreferenceHelper


    val uiState: StateFlow<MainActivityUiState> = flow<MainActivityUiState>{
       val accessToken= preferenceDataStoreHelper.getPreference(ACCESS_TOKEN,"").first()
        if(accessToken.isEmpty()){
            emit(MainActivityUiState.Success(loginRoute))
        }else{
            emit(MainActivityUiState.Success(homeScreenRoute))
        }

    }.stateIn(
        scope = viewModelScope,
        initialValue = MainActivityUiState.Loading,
        started = SharingStarted.WhileSubscribed(5_000),
    )
}

sealed interface MainActivityUiState {
    data object Loading : MainActivityUiState
    data class Success(val route: String) : MainActivityUiState
}