AsyncLayoutInflater / Layouts

AsyncLayoutInflater

You never get a second chance to make a good first impression. This old adage is particularly true for apps. A bad first impression can often drive users to abandon an app before they have properly tried it. Slow app startup can certainly contribute to a poor first experience. But the Jetpack Startup library can certainly help improve startup times. However, there are other tools to help us improve our startup times. In this article, we’ll look at one of them: AsyncLayoutInflater.

Adapted from this icon under CC BY 4.0 licence

The specific area that we’ll consider is layout inflation within our Activity. This can be a relatively slow process because it involves binary XML parsing and View object creation. The more complex our layout, then more work this requires.

This can be further slowed down if we are using the libraries such as Jetpack Navigation. When the NavHostFragment is inflated, this will require the navigation graph to then be inflated.

Similarly any embedded Fragment instances in the layout will also require inflation of their own layouts.

While none of these individually may take too much time, the inflation of the entire Activity layout hierarchy may take a little time. To give some basic idea: A layout consisting of a ConstraintLayout with a single TextView child takes around 100ms to inflate on a Pixel 3a XL. Obviously, a more complex layout may take considerably longer.

The knock-on effect

The user will barely notice a few hundred milliseconds. But we must remember that this is not happening in isolation. In many apps, we might trigger a network request from the onCreate()method in the Activity. If we wait until after layout inflation has completed these operations will be happening serially. This means that the user may be waiting for longer than necessary before they see any content.

The obvious way of addressing this is to trigger the network request before we perform layout inflation. If we do not perform the network request asynchronously, then we’re doing it wrong. However, there is another option – perform the layout inflation asynchronously as well as other potentially time-consuming operations.

AsyncLayoutInflator

The Jetpack library named asynclayoutinflator does precisely that. It’s not a new library nor one which has many updates – it has been at release 1.0.0 since September 2018. The API itself is fairly straightforward. However, it may not be immediately clear how to use it with either Data Binding or View Binding. So we’ll actually look at how to implement it along with View Binding.

Let’s start by adding the library dependency:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    .
    .
    .
}

dependencies {
    .
    .
    .
    implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
    .
    .
    .
}
.
.
.

Our simple Activity looks like this:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

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

        val content = findViewById(android.R.id.content)
        AsyncLayoutInflater(this)
            .inflate(R.layout.activity_main, content) { view, _, _ ->
                binding = ActivityMainBinding.bind(view)
                setContentView(view)
            }
    }
}

We first need to find the content view. This is the parent view which will hold the layout that we’ll inflate. Next, we create an instance of AsyncLayoutInflater which takes a Context as it’s constructor argument. We call the inflate() method on this to initialise the inflation.

The inflate() method takes three arguments: The first is the ID of the layout that will be inflated. The second is the parent view – in this case, it is the content view that we just obtained. The third is an instance of OnInflateFinishedListener which is a SAM interface, so we can reduce its single method to a lambda. The lambda will be invoked once inflation is complete.

The lambda represents the onInflateFinished method of OnInflateFinishedListener. There are three arguments to this method: The first is the root view of the newly inflated view hierarchy. The second is the layout ID that was inflated. The third is the parent view – the content view that we passed to inflate().

We first bind our ActivityMainBinding to the new hierarchy using its static bind() method. We can now use the binding instance as we would normally use a View binding instance. Next we call setContentView() on the Activity to add the newly inflated view hierarchy as the Activity content. AsyncLayoutInflater does not add the inflated hierarch to the parent ViewGroup so we need to do this manually. It is equivalent to calling:

layoutInflater.inflate(R.layout.activity_main, content, false)

Caution

It is worth remembering that performing different operations in parallel can result in race conditions. Therefore we must be careful to check that the binding instance has been initialised before we attempt to use it. For example, it may not have been initialised by the time onResume() is called. So we should check ::binding.isInitialized before using it in such cases.

Fragments

The next logical consideration is whether we should use this within Fragments for their layout inflation. Unfortunately AsyncLayoutInflator is not well suited to this because Fragments have layout inflation tied into their lifecycle methods. Whereas in Activity we typically perform layout inflation during onCreate(), in a Fragment we do it in onCreateView(). This method must return the inflated view hierarchy and using AsyncLayoutInflater within it does not allow this.

It is worth bearing in mind that most Activity layouts will be far more complex than those of Fragments. The latter are a smaller logical UI controller. So this may not be such a big deal. Typically it is Activities that will have NavHostFragment instances within their layouts, and any other embedded Fragment instances within the layout will also be inflated within the Activity‘s AsyncLayoutInflater invocation.

Conclusion

Using AsyncLayoutInflater isn’t something that we need to use in all cases. However, when we are trying to reduce the initial wait time before we can display something to the user it can permit layout inflation to occur in parallel with other operations. But, we must be careful to avoid race conditions that may result from binding not being initialised before onCreate() returns.

The source code for this article is available here.

© 2020 – 2021, Mark Allison. All rights reserved.

Copyright © 2020 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.