Skip to content

Android Splash Screen API vs Compose Splash Screen: Which Should You Use in 2026?

The Android Splash Screen API has been the recommended way to implement splash screens since Android 12. But a growing number of developers are questioning whether it’s the right choice for every app. Some have abandoned it entirely, implementing custom splash screens in Jetpack Compose instead. Here’s what you need to know to make the right decision.

The Problem

The Android Splash Screen API comes with significant constraints that can frustrate developers:

Splash Screen API Limitations
1. Requires XML theme configuration
-> Even for Compose-only apps
-> postSplashScreenTheme must reference XML theme
2. Limited customization
-> Fixed icon positioning
-> No custom animations beyond the icon
-> Background must be solid color or drawable
3. OEM-specific bugs
-> Samsung: Sometimes ignores animation duration
-> Xiaomi: Icon scaling issues
-> Huawei: Race conditions with theme transition
4. Cannot reliably perform async operations
-> setKeepOnScreenCondition is a polling API
-> No true async/await support
-> Data loading must complete elsewhere

A Reddit discussion on r/androiddev captured the frustration:

“Splash Screen API is terrible and no one cares.”

This controversial take sparked a debate. Some developers have worked around the API entirely, implementing a Compose splash screen with NavHost instead.

The Direct Answer

For most Jetpack Compose apps, the Android Splash Screen API is still the recommended approach despite its limitations. It provides platform compliance, consistent user experience, and follows Android design guidelines.

However, consider a Compose-based splash screen if:

  1. You need full control over splash behavior and animations
  2. You’re hitting OEM-specific bugs that break the splash experience
  3. You have complex navigation requirements (deciding logged-in state on cold-start)
  4. Your app requires authentication checks before showing any UI

Three Options Compared

This is the standard approach that follows Android platform guidelines.

MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// Keep splash visible while checking auth
var isReady = false
splashScreen.setKeepOnScreenCondition { !isReady }
lifecycleScope.launch {
// Check authentication state
val isLoggedIn = authRepository.checkAuthStatus()
isReady = true
setContent {
AppTheme {
if (isLoggedIn) {
HomeScreen()
} else {
LoginScreen()
}
}
}
}
}
}

Pros:

  • Platform-compliant
  • Consistent with Android design guidelines
  • Handles cold start automatically
  • Supports launch animations

Cons:

  • Limited customization
  • Requires XML theme configuration
  • OEM compatibility issues
  • Cannot perform real async operations during splash

Option B: Compose Splash Screen with NavHost

For apps needing more control, implement splash entirely in Compose:

MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
val navController = rememberNavController()
val startDestination by produceState<SplashDestination> {
value = SplashDestination.Checking
}
NavHost(
navController = navController,
startDestination = "splash"
) {
composable("splash") {
SplashScreen(
onAuthDetermined = { isLoggedIn ->
if (isLoggedIn) {
navController.navigate("home") {
popUpTo("splash") { inclusive = true }
}
} else {
navController.navigate("login") {
popUpTo("splash") { inclusive = true }
}
}
}
)
}
composable("home") {
HomeScreen()
}
composable("login") {
LoginScreen()
}
}
}
}
}
}
SplashScreen.kt
@Composable
fun SplashScreen(
onAuthDetermined: (Boolean) -> Unit
) {
val authRepository = LocalAuthRepository.current
LaunchedEffect(Unit) {
val isLoggedIn = authRepository.checkAuthStatus()
delay(500) // Optional: minimum splash duration for branding
onAuthDetermined(isLoggedIn)
}
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.primary),
contentAlignment = Alignment.Center
) {
// Custom splash UI
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = R.drawable.logo),
contentDescription = "App Logo",
modifier = Modifier.size(120.dp)
)
Spacer(modifier = Modifier.height(16.dp))
CircularProgressIndicator(
color = MaterialTheme.colorScheme.onPrimary
)
}
}
}

Pros:

  • Full control over UI and animations
  • Natural integration with Compose Navigation
  • Easy to add custom loading states
  • No XML required

Cons:

  • No platform launch animation
  • Slight delay before Compose initializes
  • Must handle theme for Activity window
  • Not following Android splash guidelines

Option C: Hybrid Approach

Combine both approaches to get platform compliance with Compose flexibility:

MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// The platform splash shows during Compose initialization
// Then transitions to your Compose splash for auth/loading
setContent {
AppTheme {
val navController = rememberNavController()
var showPlatformSplash by remember { mutableStateOf(true) }
// Dismiss platform splash once Compose is ready
LaunchedEffect(Unit) {
splashScreen.setKeepOnScreenCondition { false }
}
NavHost(
navController = navController,
startDestination = "compose_splash"
) {
composable("compose_splash") {
ComposeSplashScreen(
onReady = { destination ->
navController.navigate(destination) {
popUpTo("compose_splash") { inclusive = true }
}
}
)
}
// ... other destinations
}
}
}
}
}

The challenge with hybrid: Avoiding the “2x splash” problem where users see two splash screens in sequence.

Avoiding 2x Splash
// WRONG: Platform splash shows, then Compose splash shows again
setContent {
SplashScreen() // User sees splash twice
}
// CORRECT: Platform splash transitions directly to content
setContent {
// Platform splash already handled the branding moment
// Go directly to auth check or main content
AuthGate()
}

Comparison Table

FeatureSplash Screen APICompose SplashHybrid
Platform complianceFullNoneFull
Custom animationsLimitedUnlimitedLimited
XML requiredYesNoYes
OEM compatibilityVariableConsistentVariable
Auth flow supportBasicFullFull
Cold start handlingAutomaticManualAutomatic
Implementation complexityLowMediumHigh
Design flexibilityLowHighMedium

Common Mistakes

Mistake 1: Assuming the API Will Wait for Data Loading

WRONG: Data loading won't complete during splash
splashScreen.setKeepOnScreenCondition {
// This is polled, not awaited
!dataRepository.isDataLoaded() // May never complete
}

The setKeepOnScreenCondition is a polling API. It doesn’t pause execution. Complex data loading should happen after the splash dismisses.

Mistake 2: Ignoring OEM Testing

Some device manufacturers modify splash screen behavior. Always test on:

  • Samsung devices (OneUI)
  • Xiaomi devices (MIUI/HyperOS)
  • Huawei devices (EMUI)
  • OnePlus devices (OxygenOS)

Mistake 3: Creating 2x Splash Unintentionally

WRONG: Two splash screens in sequence
// In themes.xml
<style name="Theme.App.Splash" parent="Theme.SplashScreen">
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
</style>
// In MainActivity.kt
setContent {
SplashScreen() // User sees platform splash, then this
}

Mistake 4: Over-Engineering with Compose Splash

If your app doesn’t need authentication checks or complex initialization, stick with the platform API. A simple splash is often better than a custom one.

Keep it simple when you can
// Simple use case: Just show app icon
installSplashScreen()
setContent { AppTheme { HomeScreen() } }
// Complex use case: Auth + data loading
// -> Consider Compose splash with NavHost

Mistake 5: Skipping the API Entirely for New Apps

For new apps, start with the platform API. Only switch to a custom solution if you hit specific limitations.

When to Use Which Approach

Decision Guide
Use Android Splash Screen API when:
-> Your app has simple initialization needs
-> You want platform compliance
-> You don't need complex auth flows during splash
-> You're starting a new project
Use Compose Splash Screen when:
-> You need full UI control
-> Auth state determines initial destination
-> You've hit OEM bugs with the platform API
-> Your splash has complex animations
Use Hybrid when:
-> You need platform compliance AND auth flows
-> You want smooth transition from platform to Compose
-> You're willing to handle the complexity

The Authentication Flow Pattern

The most common reason developers choose Compose splash is authentication:

AuthFlowWithComposeSplash.kt
@Composable
fun AuthGate(
navController: NavHostController,
authRepository: AuthRepository
) {
val authState by authRepository.authState.collectAsState()
LaunchedEffect(authState) {
when (authState) {
AuthState.LoggedIn -> {
navController.navigate("home") {
popUpTo("splash") { inclusive = true }
}
}
AuthState.LoggedOut -> {
navController.navigate("login") {
popUpTo("splash") { inclusive = true }
}
}
AuthState.Loading -> {
// Stay on splash
}
}
}
}

This pattern is clean with Compose Navigation but awkward with the platform Splash Screen API because the API doesn’t support async decision-making naturally.

Summary

The Android Splash Screen API is the right choice for most apps. It’s platform-compliant, handles cold starts automatically, and follows Android design guidelines.

However, if your app has complex authentication flows, needs full UI control, or you’re hitting OEM-specific bugs, a Compose-based splash screen with NavHost is a valid alternative.

Key takeaways:

  1. Start with the platform API for new apps
  2. Use setKeepOnScreenCondition for simple initialization checks
  3. Consider Compose splash when auth state determines initial destination
  4. Test on multiple OEM devices
  5. Avoid the “2x splash” problem with hybrid approaches
  6. Don’t pre-load data expecting it to complete during splash

Choose the approach that fits your app’s specific requirements, not what’s “recommended” in the abstract.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments