— android, shazam, audio — 5 min read
In the past, having splash screens in your Android app were not recommended. It didn’t make much sense to intentionally delay the user by adding a splash screen that shows for x seconds. Am sure no one launches an app just to see a splash screen (more on this later).
Getting users to the content they care about should be your #1 priority
Well, when Material Design dropped with a pattern called Launch Screen (Splash Screen), someone from the Android team shared a post on how to do splash screens the ‘right’ way.
In this post I will walk through 4 common methods of implementing splash screens on the Android platform:
Using a Launcher Theme (The good)
Using a Launcher Theme with a Dedicated Splash Activity (The okay)
Using Timers (The bad)
Using Smart Timers (The ugly)
When your app is launched and it isn’t in memory yet, there may be some delay between when the user starts your app and when your launcher Activity’s onCreate() is actually called.
During the ‘cold start’, the window manager tries to draw a placeholder UI using elements from the app theme like the windowBackground. So, rather than showing the default windowBackground (usually white or black), you can change it to a custom drawable that shows your splash screen. This way the splash screen only shows when needed and you don’t slow down your users.
The key is creating a custom theme that overrides android:windowBackground, then replacing that custom theme with your standard theme before calling super.onCreate() in your Activity.
Implementation
In this example, I will assume your app main theme is named AppTheme, but if it’s not then you can replace all occurrence of AppTheme with the name of your app main theme.
You will have to create a new theme for the launcher. The only element we are interested in overriding in this theme is the windowBackground, so the launcher theme will be:
1<?xml version="1.0" encoding="utf-8"?>2<resources>3 4 <!-- Your AppTheme or other themes/styles here -->5 6 <!-- The launcher theme. It sets the main window background to the launch_screen drawable -->7 <style name=”AppTheme.Launcher”>8 <item name=”android:windowBackground”>@drawable/launch_screen</item>9 <!-- Optional, on Android 5+ you can modify the colorPrimaryDark color to match the windowBackground color for further branding-->10 <!-- <item name="colorPrimaryDark">@android:color/white</item> -->11 </style>12 13</resources>
To inherit every other attribute in your main theme, the dot notation was used by prefixing the name of the theme (AppTheme), separated by a period (.).
Let’s define the launch_screen drawable. While you could just use a simple image, it will end up stretched to fill the entire screen. Instead, you can use an XML file such as:
1<?xml version="1.0" encoding="utf-8"?>23<!-- The android:opacity=”opaque” line — this is critical in preventing a flash of black as your theme transitions. -->4<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">5 6 <!-- The background color, preferably the same as your normal theme -->7 <item android:drawable="@android:color/white"/>8 9 <!-- Your product logo - 144dp color version of your app icon -->10 <item>11 12 <bitmap13 android:src="@drawable/product_logo_144dp"14 android:gravity="center"/>15 16 </item>17 18</layer-list>
Then apply the launcher theme to your launcher Activity in your AndroidManifest.xml file using:
1android:theme=”@style/AppTheme.Launcher”
The easiest way to transition back to your normal theme in your launcher (main) activity is to call setTheme(R.style.AppTheme) before super.onCreate() and setContentView():
1import android.os.Bundle2import android.support.v7.app.AppCompatActivity34class MainActivity: AppCompatActivity() {56 override fun onCreate(savedInstanceState: Bundle?) { 7 // Make sure this is before calling super.onCreate 8 setTheme(R.style.AppTheme)9 super.onCreate(savedInstanceState)10 // …11 }1213}
You can read more about this approach from the source here.
Pros:
No launch/splash activity needed — there is no delay such as there would be if you were launching a second activity from a dedicated splash screen activity.
No artificial delays — No intentional x seconds delay. Only showing the splash screen when the system is loading up the app.
Cons:
I have seen 3 very common complains about this approach.
The splash screen showing again if the activity was killed and recreated by the system. In most cases, this is not a very serious issue, but you can use Method 2 to fix it.
Some developers want to have a dedicated splash screen Activity that routes to different pages based on some state after the splash screen is done. Again you can easily do this using Method 2 below, but sometimes this dedicated router Activity end up getting really messy.
Not able to load heavy data/components while the splash screen is shown. It’s usually a bad idea to require heavy data or components to be loaded before the app is actually started (there are some exceptions). You can try one of the suggestions below:
i) Try to lazy load your components/modules/libraries. Except a component is really really required for the app to work, try not to load it at launch time rather load it when you need it or use a background thread to load it after the app has started. Try to keep your Application onCreate() as light as possible
ii) Make use of caching. Except that info changes very frequently, you should cache it. So when next the user comes to your app, you can show this cached content while you load more recent content.
I think we should strive to remove things like long splash screens, ProgressDialogs that make the user unable to perform any other action apart from just staring at the screen. You never know how long it will take to load that data from the Internet.
If your app connects to the web, assume that anything that can go wrong will go wrong. This way you can build for the millions of people still using unstable 2g and 3g connections.
This method builds on top Method 1. It requires you to have a dedicated splash screen Activity. This allows you to quickly solve the first 2 issues in Method 1.
All you have to do in this step is to create a new splash Activity and assign the launcher theme to it in your AndroidManifest.xml file (Like in Method 1). Then edit your splash activity to route to the various pages. Look at the example below:
1import android.os.Bundle2import android.support.v7.app.AppCompatActivity34class SplashActivity: AppCompatActivity() {56 override fun onCreate(savedInstanceState: Bundle?) { 7 // Make sure this is before calling super.onCreate 8 setTheme(R.style.AppTheme)9 super.onCreate(savedInstanceState)1011 val user = UserDb.getCurrentUser()12 routeToAppropriatePage(user)13 finish()14 }1516 private fun routeToAppropriatePage(user: User) {17 // Example routing18 when {19 user == null -> OnboardingActivity.start(this)20 user.hasPhoneNumber() -> EditProfileActivity.start(this)21 user.hasSubscriptionExpired() -> PaymentPlansActivity.start(this)22 else -> HomeActivity.start(this)23 }24 }25 26}
Pros:
Cons:
I have seen this routing thing easily get ugly.
Small delay transitioning between Activities.
It’s easy to forget and start doing some long running operations here.
This is the old easy approach. You just have to create a dedicated splash screen Activity that shows up for x seconds, then opens the appropriate activity. You get more flexibility here as you can add animations, custom views or any other element you can normally fit into an Activity layout. A very basic implementation of this will look like this:
1import android.os.Bundle2import android.os.Handler3import android.support.v7.app.AppCompatActivity45class SplashActivity: AppCompatActivity() {67 override fun onCreate(savedInstanceState: Bundle?) {8 super.onCreate(savedInstanceState)9 setContentView(R.layout.activity_splash)1011 scheduleSplashScreen()12 }1314 private fun scheduleSplashScreen() {15 val splashScreenDuration = getSplashScreenDuration()16 Handler().postDelayed(17 {18 // After the splash screen duration, route to the right activities19 val user = UserDb.getCurrentUser()20 routeToAppropriatePage(user)21 finish()22 },23 splashScreenDuration24 )25 }2627 private fun getSplashScreenDuration() = 2000L2829 private fun routeToAppropriatePage(user: User) {30 // Example routing31 when {32 user == null -> OnboardingActivity.start(this)33 user.hasPhoneNumber() -> EditProfileActivity.start(this)34 user.hasSubscriptionExpired() -> PaymentPlansActivity.start(this)35 else -> HomeActivity.start(this)36 }37 }3839}
Pros:
You get a chance to show your awesome animation or some custom design you have built. This really makes sense for games or apps directed to kids.
More flexibility on what you can do on the splash screen.
Cons:
Double whammy — Your launcher Activity usually doesn’t show up immediately when the app is launched, especially when your app is cold starting. The user waits for the cold start looking at only the windowBackground and then waits for your splash screen again before getting to your app content.
Your awesome animation or design usually only amaze the user the first few times. After that, most users will find it boring and they want to get to the content. I think Method 4 provides a fix for this.
Most times the extra delay is not worth it.
This builds on Method 3. So rather than make the delay fixed, you vary it based on whether the user is launching the app for the first time or not. Here is an example using SharedPreferences:
1import android.content.Context2import android.os.Bundle3import android.os.Handler4import android.support.v7.app.AppCompatActivity56class SplashActivity: AppCompatActivity() {78 override fun onCreate(savedInstanceState: Bundle?) {9 super.onCreate(savedInstanceState)10 setContentView(R.layout.activity_splash)1112 scheduleSplashScreen()13 }1415 private fun scheduleSplashScreen() {16 val splashScreenDuration = getSplashScreenDuration()17 Handler().postDelayed(18 {19 // After the splash screen duration, route to the right activities20 val user = UserDb.getCurrentUser()21 routeToAppropriatePage(user)22 finish()23 },24 splashScreenDuration25 )26 }2728 private fun getSplashScreenDuration(): Long {29 val sp = getPreferences(Context.MODE_PRIVATE)30 val prefKeyFirstLaunch = "pref_first_launch"31 32 return when(sp.getBoolean(prefKeyFirstLaunch, true)) {33 true -> {34 // If this is the first launch, make it slow (> 3 seconds) and set flag to false35 sp.edit().putBoolean(prefKeyFirstLaunch, false).apply()36 500037 }38 false -> {39 // If the user has launched the app, make the splash screen fast (<= 1 seconds)40 100041 }42 }43 }4445 private fun routeToAppropriatePage(user: User) {46 // Example routing47 when {48 user == null -> OnboardingActivity.start(this)49 user.hasPhoneNumber() -> EditProfileActivity.start(this)50 user.hasSubscriptionExpired() -> PaymentPlansActivity.start(this)51 else -> HomeActivity.start(this)52 }53 }5455}
Pros:
Inherits all Method 3 pros.
Cons:
Double whammy — The issue in method 3 still exists here
Most times the extra delay is not worth it.
I haven’t used this method before, but I think there might be some lag while reading from storage.
Okay, so that’s it for splash screens on Android. If I missed any other common implementation, please let me know in the comment section below.