fbpx

Blogs from the Ranch

< Back to Our Blog

An Updated Look at Launching a Splash Screen

Avatar

Brian Gardner

Launching a Splash Screen Update

Splash screens are a big deal for Android developers. In fact, BNR’s VP of Engineering, Chris Stewart, wrote our original Splash Screens the Right Way blog post over five years ago and it is still our most popular post.

We’re aiming to update the implementation with modern Android practices and to fix a bug in the original post. The general implementation of the splash screen is the same but there are a few considerations to make about how to navigate. If you are interested, I uploaded some sample project code on Github.

Before getting started, the official Material name for a splash screen is now Launch Screen. And though there are several types of launch screens, we’ll focus on one that displays the app icon when the app process first loads.

Fixing a bug

Configuring the launch screen background works the same as the original post. A background XML drawable is created and set as the window background for a launch screen-specific theme.

The original implementation declared the background like this:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@color/gray"/>

    <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/ic_launcher"/>
    </item>

</layer-list>

This no longer works for more recently created Android apps for an interesting reason.

The <bitmap> tag expects an image @drawable resource, but it doesn’t allow specifying an XML drawable source file. When a newer Android app is created, the mipmap launcher icons include a vector version which is an XML file.

This is the reason some people run into this issue when creating a launch screen using the original blog post. Since one of the launcher icons is an XML file, the <bitmap> tag throws an exception. Trying to run the app in this configuration logs this error:

2020-09-15 13:14:18.365 25218-25218/com.example.splashscreen E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.splashscreen, PID: 25218
    java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.splashscreen/com.example.splashscreen.SplashActivity}: android.content.res.Resources$NotFoundException: Drawable com.example.splashscreen:drawable/background_splash with resource ID #0x7f07005f
        ...
    Caused by: android.content.res.Resources$NotFoundException: Drawable com.example.splashscreen:drawable/background_splash with resource ID #0x7f07005f
    Caused by: android.content.res.Resources$NotFoundException: File res/drawable/background_splash.xml from drawable resource ID #0x7f07005f
        ...
    Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line #8: <bitmap> requires a valid 'src' attribute
2020-09-15 13:14:18.365 25218-25218/com.example.splashscreen E/AndroidRuntime:     at android.graphics.drawable.BitmapDrawable.inflate(BitmapDrawable.java:775)
    ...

The original blog post was created before vector resources were available on Android so all of the launcher icons are images, meaning no error is thrown by the <bitmap> tag.

Fixing this for apps with vector launcher icons requires deleting the <bitmap> tag and moving the drawable and gravity attributes to the <item> tag.

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@color/gray"/>

    <item
        android:drawable="@mipmap/ic_launcher_round"
        android:gravity="center"/>

</layer-list>

Launch screen implementation

With the resource exception handled, the launch screen implementation is next. Much of this is similar to the previous post so I will breeze through it.

The first step is creating a custom theme for the launch screen.

<style name="LaunchTheme" parent="Theme.AppCompat.NoActionBar">
    <item name="android:windowBackground">@drawable/background_launch</item>
</style>

The style is then applied to the <activity> tag in the AndroidManifest.xml.

<activity
    android:name=".LaunchActivity"
    android:theme="@style/LaunchTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Modernization issues

In the spirit of modernizing the sample app, my first attempt at the launch screen navigation involved adding the Navigation Component. This involved adding the dependency to the app/build.gradle file, declaring a navigation graph resource, and adding the destinations. The LaunchActivity was declared as the home destination which has an action to go to the MainActivity.

This quickly revealed a problem. In order to perform navigation from an Activity, the findNavController(view: View) function is called passing in a view that is a NavHost or is within a NavHost. Normally this is not an issue for most activities because they have a view to set up and display to the user.

For the LaunchActivity, it does not have a view since it is just waiting for the onCreate() function to be called before navigating to a different activity. Using the navigation component to navigate from the LaunchActivity would require creating a layout file that contained a navigation host, setting up the layout in LaunchActivity‘s onCreate() function, accessing the NavController from the view, and then using it to navigate to the next screen.

The issue with this implementation is speed. As soon as the LaunchActivity is ready, it should immediately navigate the user to the next activity. Inflating a view and grabbing the NavController takes time causing the user to see the launch image longer than necessary.

For this reason, the navigation component should not be used to perform navigation from the LaunchActivity. Using the manual navigation with startActivity() is faster and more concise.

class LaunchActivity : AppCompatActivity() {

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

        // Send user to MainActivity as soon as this activity loads
        val intent = Intent(this, MainActivity::class.java)
        startActivity(intent)

        // remove this activity from the stack
        finish()
    }
}

One more consideration

One common idea for launch screens is to perform app setup when the activity launches and navigate when the setup is finished. This sounds like a good idea because there may be certain components an app needs on the main screen, so setting them up in the launch activity seems like an obvious choice. Unfortunately, there is an issue with this.

If the app is evicted from memory while in the background, the system tracks the activities in the task so it can recreate them when the user returns to the app. The problem is that the launch activity is finished when the user leaves, meaning the system will not recreate the activity when the app is restarted. This means that any setup code will not run and the app may end up in a bad state.

This does not necessarily mean that any setup code in the launch activity is destined for bugs and crashes. Tasks like refreshing data from a server can be alright, as long as the app can handle a situation where that data is not present on other screens. If those other screens can themselves request the data again, then having that kind of logic in the launch activity is not a problem.

Where issues occur are in situations where critical components are set up in the launch activity and other screens expect those components to be initialized without checking first. For example, if an app uses the launch activity to initialize its Dagger component and the other activities expect it to be present, the app will not work when reinitialized by the system because the new app process will not have a new component available.

For this reason, a critical setup code should occur in an Application subclass. This ensures that the setup code runs whenever the application process is loaded into memory so the components are available to the rest of the app.

For more information, check out Andrew Bailey’s Falsehoods Android Developers Believe about Lifecycles talk from Droidcon Online.

Avatar

Brian Gardner

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project