Skip to content

What Are the Best Practices for Splash Screens in Jetpack Compose Apps?

The Direct Answer

The best splash screen is one users barely notice. Use the Android Splash Screen API with a simple background and centered icon, extend it only for critical initialization (not data loading), and ensure the transition to your app is seamless. Avoid complex animations, never use splash screens for branding alone, and always use the androidx library for consistent behavior across devices.

The Problem

Splash screens have a troubled history. Before Android 12, developers created custom splash screens using separate activities or overlay views, leading to inconsistent experiences across apps. The Android 12 Splash Screen API was introduced to standardize behavior, but it came with its own challenges and potential pitfalls.

A Reddit discussion on r/androiddev captured the core issue:

“Users hate meaningless splash screens. Every second your splash screen adds to startup is a second the user is waiting instead of using your app.”

The problem compounds when developers misuse the splash screen API:

Common Anti-Patterns
What Developers Do:
-> Show splash for 3+ seconds for "branding"
-> Load all data during splash screen
-> Create complex animated logos
-> Use splash as a "loading screen"
What Users Experience:
-> Frustration waiting for the app
-> Perception of slow, bloated app
-> Negative app store reviews
-> Uninstall if it happens repeatedly

Five Essential Best Practices

Practice 1: Use the androidx Library (Always)

Even if your minSdk is 31 (Android 12+), use the androidx.core:core-splashscreen library. It provides:

  • Consistent behavior across all Android versions
  • Fixes for OEM-specific bugs
  • Backward compatibility for older devices
  • Proper handling of edge cases
build.gradle.kts (Module: app)
dependencies {
// Always use the androidx library
implementation("androidx.core:core-splashscreen:1.0.1")
}

Practice 2: Keep It Minimal

The splash screen should be simple: a solid background color with a centered icon. No complex animations, no video, no loading indicators.

res/values/themes.xml
<style name="Theme.App.Splash" parent="Theme.SplashScreen">
<!-- Transition to your app theme -->
<item name="postSplashScreenTheme">@style/Theme.App</item>
<!-- Simple, solid background -->
<item name="windowSplashScreenBackground">@color/splash_background</item>
<!-- Centered icon (your app icon works well) -->
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<!-- Animation duration: 200-500ms for smooth exit -->
<item name="windowSplashScreenAnimationDuration">300</item>
</style>
res/drawable/splash_icon.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Simple centered icon - no complex animations -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:width="108dp"
android:height="108dp"
android:drawable="@mipmap/ic_launcher_round" />
</layer-list>

Practice 3: Extend Only When Necessary

Use setKeepOnScreenCondition sparingly. Only extend the splash screen for:

  • Critical database initialization
  • Essential preference loading
  • Authentication state check
  • Required SDK initialization
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// ONLY for critical initialization
var isReady = false
splashScreen.setKeepOnScreenCondition { !isReady }
lifecycleScope.launch {
// Critical initialization only
initializeCriticalComponents()
isReady = true
}
setContent {
AppTheme {
// Your UI
}
}
}
}

Practice 4: Use OnExitAnimationListener Wisely

The exit animation listener lets you customize the transition from splash to your app. Use it sparingly.

MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// Optional: Customize exit animation
splashScreen.setOnExitAnimationListener { splashScreenView ->
// Create custom transition
splashScreenView.remove()
}
setContent {
AppTheme {
// Your UI should match splash screen for seamless transition
}
}
}

The key principle: your app’s initial state should visually match the splash screen for a seamless transition. If the splash is white with a centered icon, your app’s first frame should look similar.

Practice 5: Test on Multiple OEMs

Device manufacturers modify Android’s splash screen behavior. Test on:

  • Samsung devices (OneUI)
  • Xiaomi devices (MIUI/HyperOS)
  • Huawei devices (EMUI)
  • OnePlus devices (OxygenOS)
  • Google Pixel devices (stock Android)
OEM-Specific Issues Found in Testing
Samsung:
-> Sometimes ignores animation duration
-> May clip splash icon
Xiaomi:
-> Can show double splash with custom themes
-> Different behavior in MIUI versions
Huawei:
-> May override background colors
-> Exit animation timing differs
OnePlus:
-> Generally follows stock behavior
-> Minor timing differences

Common Mistakes to Avoid

Mistake 1: The “2x Splash” Anti-Pattern

This happens when you have both an XML splash screen AND a custom Compose splash screen:

WRONG: Double splash screen
// XML splash shows first (from theme)
// Then this shows second - user sees TWO splash screens!
setContent {
if (isLoading) {
CustomSplashScreen() // REMOVE THIS
} else {
MainContent()
}
}

Solution: Remove any custom splash screen UI in Compose. Use the Android Splash Screen API only.

Mistake 2: Using Splash for Branding

WRONG: Branding-focused splash
<style name="Theme.App.Splash" parent="Theme.SplashScreen">
<!-- DON'T: Complex animated logo for branding -->
<item name="windowSplashScreenAnimatedIcon">@drawable/animated_brand_logo</item>
<item name="windowSplashScreenAnimationDuration">3000</item>
</style>

Users don’t want to see your logo for 3 seconds. They want to use your app.

Mistake 3: Loading Data During Splash

WRONG: Data loading during splash
splashScreen.setKeepOnScreenCondition { !dataLoaded }
lifecycleScope.launch {
// DON'T: Load all data during splash
userRepository.loadAllUsers()
productRepository.loadAllProducts()
orderRepository.loadOrderHistory()
dataLoaded = true
}

This is problematic because:

  1. Splash code may not run on all launches
  2. Network failures leave users stuck on splash
  3. Users wait longer than necessary

Solution: Load data in your UI with loading states.

Mistake 4: Extending Splash Unnecessarily

WRONG: Extending splash for non-critical work
splashScreen.setKeepOnScreenCondition { !isReady }
lifecycleScope.launch {
// DON'T: These don't need to block splash
analytics.initialize()
crashReporting.setup()
featureFlags.fetch()
isReady = true
}

Analytics and feature flags can initialize in the background. Don’t make users wait.

Mistake 5: Ignoring Exit Animation

WRONG: Jarring transition
// Splash screen with white background
// App starts with dark theme
// User sees jarring flash

Solution: Match your app’s initial frame to the splash screen appearance.

Best Practice Implementation Checklist

[ ] Added androidx.core:core-splashscreen dependency
[ ] Created Theme.SplashScreen in XML
[ ] Set postSplashScreenTheme reference
[ ] Applied splash theme to launcher activity in manifest
[ ] Called installSplashScreen() BEFORE super.onCreate()
[ ] Used simple background + centered icon only
[ ] Animation duration between 200-500ms
[ ] Only extended splash for critical initialization
[ ] No custom Compose splash screen UI
[ ] Tested on Samsung, Xiaomi, Huawei, OnePlus devices
[ ] App's first frame matches splash screen appearance
[ ] No data loading during splash

Complete Working Example

MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 1. Install splash screen FIRST
val splashScreen = installSplashScreen()
super.onCreate(savedInstanceState)
// 2. Only extend for critical initialization
var isCriticalReady = false
splashScreen.setKeepOnScreenCondition { !isCriticalReady }
lifecycleScope.launch {
// Critical work only
Database.init(this@MainActivity)
isCriticalReady = true
}
// 3. Set up your Compose UI
setContent {
val isReady by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
// Non-critical work
Analytics.init()
isReady = true
}
AppTheme {
if (isReady) {
MainContent()
} else {
// Loading state, NOT another splash screen
LoadingScreen()
}
}
}
}
}
AndroidManifest.xml
<application
android:theme="@style/Theme.App">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

Why This Matters

AspectImpact
User ExperienceUsers perceive faster app startup
Platform ComplianceFollows Android design guidelines
Cold Start PerceptionSeamless transition masks initialization
App Store RatingsFewer complaints about slow startup
Device FragmentationConsistent behavior across OEMs

Summary

The best splash screen implementation is one that users barely notice. Follow these five practices:

  1. Use the androidx library - Ensures consistent behavior across all devices and Android versions
  2. Keep it minimal - Simple background + centered icon, no complex animations
  3. Extend only when necessary - Only for critical initialization, never for data loading
  4. Handle exit animations wisely - Match your app’s first frame to the splash appearance
  5. Test on multiple OEMs - Samsung, Xiaomi, Huawei, and others have different behaviors

The splash screen API is a tool for platform compliance and smooth perceived startup, not a marketing opportunity. Treat it that way, and your users will thank you.

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