12

I am learning Android's new SplashScreen API introduced with Android 12. I have so far gotten it to work on my Emulator and Google Pixel 4A, but I want to increase its duration. In my Splash Screen I do not want a fancy animation, I just want a static drawable.

I know, I know (sigh) some of you might be thinking, that I should not increase the duration and I know there are several good arguments in favor of not doing so. However, for me the duration of a splash screen with a non animated drawable is so brief (less than a second), I think it raises an accessibility concern, especially so since it cannot be disabled (ironically). Simply, the organization behind the product or its brand/product identity cannot be properly absorbed or recognized by a new user at that size and in that time, rendering the new splash screen redundant.

I see the property windowSplashScreenAnimationDuration in the theme for the splash screen (shown below), but this has no effect on the duration presumably because I am not animating.

 <style name="Theme.App.starting" parent="Theme.SplashScreen">
        <!--Set the splash screen background, animated icon, and animation duration.-->
        <item name="windowSplashScreenBackground">@color/gold</item>
    
        <!-- Use windowSplashScreenAnimatedIcon to add either a drawable or an
             animated drawable. One of these is required-->
        <item name="windowSplashScreenAnimatedIcon">@drawable/accessibility_today</item>
        <item name="windowSplashScreenAnimationDuration">300</item> <!--# Required for-->
                                                                    <!--# animated icons-->
        <!--Set the theme of the activity that directly follows your splash screen-->
        <item name="postSplashScreenTheme">@style/Theme.MyActivity</item>
    
        <item name="android:windowSplashScreenBrandingImage">@drawable/wculogo</item>
    
    </style>

Is there a straightforward way to extend the duration of a non animated splash screen?

Gaming.ingrs
  • 271
  • 1
  • 13
Andrew S
  • 2,847
  • 3
  • 33
  • 50
  • Splashscreen is useful when the app takes too long to open, but shouldn't be used otherwise. It makes the user wait for no reason. – cmak Jan 27 '22 at 14:33
  • True cmak, but as of Android 12, they are mandatory and cannot be turned off without ugly unofficial hacks. I would say no splash screen would be a better option in this case, but its not an option if I am to deploy apps for Android 12 that start in the way Google want. Note, I am aware of hacks to stop is showing, but this just makes the OS just look like its frozen for a moment which is worse. – Andrew S Jan 28 '22 at 01:47
  • You're right, just realized it's mandatory. That's a bad decision by Google, hope they remove it. – cmak Jan 28 '22 at 11:41

4 Answers4

14

As I was writing this question and almost ready to post it, I stumbled on the method setKeepOnScreenCondition (below) that belongs to the splashScreen that we must install on the onCreate of our main activity. I thought it seemed wasteful not to post this, given there are no other posts on this topic and no such similar answers to other related questions (as of Jan 2022).

SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
splashScreen.setKeepOnScreenCondition(....);

Upon inspecting it I found this method receives an instance of the splashScreen.KeepOnScreenCondition() interface for which the implementation must supply the following method signature implementation:

 public boolean shouldKeepOnScreen() 

It seems this method will be called by the splash screen and retain the splash screen visibly until it returns false. This is where the light bulb moment I so love about programming occurred.

What if I use a boolean initialised as true, and set it to false after a delay? That hunch turned out to work. Here is my solution. It seems to work and I thought it would be useful to others. Presumably instead of using a Handler for a delay, one could also use this to set the boolean after some process had completed.

package com.example.mystuff.myactivity;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends AppCompatActivity {
    
    private boolean keep = true;
    private final int DELAY = 1250;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Handle the splash screen transition.
        SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
        super.onCreate(savedInstanceState);

        //Keep returning false to Should Keep On Screen until ready to begin.
        splashScreen.setKeepOnScreenCondition(new SplashScreen.KeepOnScreenCondition() {
            @Override
            public boolean shouldKeepOnScreen() {
                return keep;
            }
        });
        Handler handler = new Handler();
        handler.postDelayed(runner, DELAY);
    }

    /**Will cause a second process to run on the main thread**/
    private final Runnable runner = new Runnable() {
        @Override
        public void run() {
            keep = false;
        }
    };
    
}

If you are into Java Lambdas an even nicer and more compact solution is as follows:

package com.example.mystuff.myactivity;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.splashscreen.SplashScreen;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends AppCompatActivity {
    
    private boolean keep = true;
    private final int DELAY = 1250;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // Handle the splash screen transition.
        SplashScreen splashScreen = SplashScreen.installSplashScreen(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Keep returning false to Should Keep On Screen until ready to begin.
    splashScreen.setKeepOnScreenCondition(() -> keep);
    Handler handler = new Handler();
    handler.postDelayed(() -> keep = false, DELAY);;
    }


    
}

If you have comments or feedback (besides telling me I should not increase the duration of the splash screen), or a better way please do comment or respond with additional answers.

Aniruddh Parihar
  • 3,072
  • 3
  • 21
  • 39
Andrew S
  • 2,847
  • 3
  • 33
  • 50
  • 1
    That's exactly the way you should do it (even if you should not do it (sorry I couldn't resist)) – Vadim Caen Jan 27 '22 at 16:10
  • @VadimCaen `setKeepOnScreenCondition` is a UI blocker correct? – Bitwise DEVS Apr 02 '22 at 21:10
  • 1
    It will prevent your application from drawing, but not the splash screen (if animated). – Vadim Caen Apr 04 '22 at 12:26
  • *But not from running the splash screen icon animation. – Vadim Caen Apr 04 '22 at 13:23
  • Why are you suggesting to modify keep in main thread? – Eldar Budagov Jan 29 '23 at 17:12
  • @Eldar Budagov, As far I am aware Eldar, all UI things are done in the main thread. – Andrew S Feb 06 '23 at 16:22
  • Yes you are right, just wanted to point that boolean variable could be changed in another thread, not necessarily in UI thread. In this case, it makes sense to mark it as volatile – Eldar Budagov Feb 07 '23 at 20:28
  • Elgar, only in the case that you might want to do this which is largely a requirement of accessing or updating a single variable from multiple threads, rather than being specific to splash screens. However, I think its a useful comment as someone with this requirement may read your comment. – Andrew S Feb 10 '23 at 02:50
  • @VadimCaen I'm trying to display a custom alert dialog on the splashcreen, until boolean variable is changed, but the splashcreen then turns black and I get a serious lag? But the android system permissions dialog works well with this. – HasIEluS Mar 15 '23 at 05:49
  • 1
    @HasIElus putting an alert dialog over a splashscreen does not seem like an elegant idea to me. It also goes totally against the grain of what Google were trying to achieve with Android's splash screen. So, I do not think this will be achievable by anything other than awful hacks, if even possible at all (likely not). – Andrew S Mar 15 '23 at 07:25
7

in Kotlin:

var keepSplashOnScreen = true
val delay = 2000L

installSplashScreen().setKeepOnScreenCondition { keepSplashOnScreen }
Handler(Looper.getMainLooper()).postDelayed({ keepSplashOnScreen = false }, delay)

you can put this into onCreate fun before super.onCreate calling (in activity with LAUNCHER intent filter in Manifest)

iddqdpwn
  • 222
  • 2
  • 8
1

Kotlin, Compose:

To use postDelayed() or runBlocking { } is not the best solution. I prefer calculate setKeepOnScreenCondition using asynchronous calls. I.e., imagine that you need to show splash icon while you're collecting value from DataStore (to show or not onboarding screens), or pull some data from API/network. In view model:

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

private val _startDestination = MutableStateFlow<String?>(null)
val startDestination get() = _startDestination.asStateFlow()

init {
    viewModelScope.launch {
        delay(2000) // long operation

        // use .collect if you're interested in collecting all emitted values
        onboardingRepository.onboardingState().collect { isFinished ->
            _startDestination.value = if (isFinished) {
                _isLoading.value = false
                Screen.Authentication.route
            } else {
                _isLoading.value = false
                Screen.Onboarding.route
            }
        }

        // or use .first() terminal operator that returns the first element emitted by the flow and then cancels flow's collection
        val isFinished = onboardingRepository.onboardingState().first()
        _startDestination.value = if (isFinished) {
            Screen.Authentication.route
        } else {
            Screen.Onboarding.route
        }
        _isLoading.value = false
    }
}

In MainActivity create a function, collect flow values and use them for onScreenConditions:

private val splashViewModel by viewModels<SplashViewModel>()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setupSplashScreen()

    setContent {
        DiaryAppTheme {
            val navController = rememberNavController()
            val startDestination by splashViewModel.startDestination.collectAsStateWithLifecycle()
            startDestination?.let {
                SetupNavGraph(
                    navController = navController,
                    startDestination = it
                )
            }
        }
    }
}

private fun setupSplashScreen() {
    var keepSplashScreenOn = true
    lifecycleScope.launch {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            splashViewModel.isLoading.collect {
                keepSplashScreenOn = it
            }
        }
    }

    installSplashScreen().setKeepOnScreenCondition {
        keepSplashScreenOn
    }
}
Vi Kalinka
  • 11
  • 2
0

One proxy way could be to use runBlocking { delay(1200) } in onCreate method, to keep on main thread for some specific time.

ibrahim
  • 83
  • 1
  • 8