Essential Android Base Classes for View Binding and Coroutine Scope

Michael Brodacz-Geier
6 min readApr 1, 2021

There are repetitive tasks in Android development that need very similar boilerplate code in several locations of the codebase. For such cases, it makes sense to create a base class implementing those re-occurring functionalities only once.

In this article, we will take a look at two specific topics needed in almost every Android application and write base classes for simplifying their usage.

This article assumes a basic understanding of basic Android concepts, such as views, View Binding, layouts, Activities and Fragments, Kotlin coroutines, class inheritance, and generic classes.

Introduction

Probably every Android application needs to access views. As Kotlin synthetics were recently (~ November 2020) deprecated, Android View Binding is one of the ways (or the way) to go. It is almost as easy to use as Kotlin synthetics, but it requires — in contrast to Kotlin synthetics — some lines of initialization code in every Activity or Fragment.

Also, many modern Kotlin-based Android applications call asynchronous code via Kotlin coroutines to escape the callback hell. To run a suspend function from a non-suspend function, you’ll have to start a coroutine using a launch or async block first. launch or async need a coroutine scope to run on. Therefore, every time before launching a coroutine, you have to create or retrieve a coroutine scope.

I want to be honest with you, both initializing View Binding and retrieving a coroutine scope is not a huge amount of additional code. But it’s something you have to write again and again, and you have to remember how to do it again and again. I love to structure my code in a way I don’t have to think or remember things, so I can focus on the actual task rather than on implementation details. I will explain what I mean further below.

The Goal

The goal is to create base classes for Activity and Fragment which allow using View Binding and coroutines as simple as shown below. The example FragmentUsingBaseFragment doesn’t do much — it loads a layout and displays some text after a delay (simulating some useful asynchronous operation by using delay()).

Let’s compare how the same code would look like without using our BaseFragment:

In our FragmentNotUsingBaseFragment we have to initialize View Binding by hand, have to store the binding reference for later use, and for launching coroutines we have to (or should) use lifecycleScope which is part of androidx.lifecycle:lifecycle-runtime-ktx.

It doesn’t look like a whole lot of boilerplate code but repeatedly writing similar code for each Activity or Fragment in your project, or even in multiple projects, will make you wish for a base class reducing and simplifying these repetitive code parts.

But now let’s take a closer look at both topics (View Binding and coroutine scopes) and let’s create the base classes for Fragment and Activity which will make your life so much easier.

View Binding

View Binding has a few advantages over alternative methods for accessing views, such as findViewById() and third-party libraries such as Butterknife (deprecated). View Binding provides both null safety and type safety and is integrated into the Android SDK.

To use View Binding, you have to enable it in your module’s Gradle file:

android {    ...

buildFeatures {
viewBinding true
}
}

View Binding creates classes from your XML layout files automatically. The generated classes are named after your XML file. activity_main.xml will result in a class named ActivityMainViewBinding which extends ViewBinding. The generated View Binding class contains references to the corresponding layout’s views having an id.

Each time we want to initialize View Binding in an Activity or a Fragment, we have to do something like this in onCreate() or in onCreateView():

views = ActivityMainBinding.inflate(layoutInflater)
setContentView(requireNotNull(binding).root)

It’s just two lines, but lines you have to repeat again and again in every Activity and Fragment. Also, you have to keep a reference to the View Binding instance (here: views) for being able to access your views later on.

What we want is to move this initialization logic and the binding reference to a base class. That base class will be a generic base class because the ViewBinding class will be different for each Activity or Fragment, based on the layout to be used.

Coroutine Context

For using Kotlin coroutines you first need to have a coroutine scope. After retrieving a coroutine scope, you can run launch or async on it.

Usually, it’s not a good idea to use the GlobalScope because it may lead to leaks. Instead, use a scope that is tied to the lifecycle of a component, for example, an Activity.

Android’s Architecture Components are there to help with that. Architecture Components KTX extensions provide coroutine scopes for common Android components, such as LifecycleScope and ViewModelScope. Generally, it’s a good idea to use those because those scopes are tied to the lifecycles of their components.

If you want to use a lifecycle scope, for example in a Fragment or an Activity, you have to use it explicitly (lifecycleScope):

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
...
}
}

A base class could instead use class delegation to provide a coroutine scope directly via this.

The base class

Now let’s combine both ideas from above into one single base class for Activities and one single base class for Fragments.

That’s the base class for Activities:

That’s the base class for Fragments:

These base classes now allow us to simplify our code in our actual Fragments and Activities. We spoiled the simplicity of the FragmentUsingBaseFragment at the beginning of this article already. So, now let’s take a look at the ActivityUsingBaseActivity (it’s almost identical to FragmentUsingBaseFragment):

In above example, you can see that there now is almost no thinking required anymore when writing code related to view access and coroutines. For initializing View Binding, you have no choice but to do what the compiler tells you: “Give me a reference to the ViewBinding’s inflate method”.

And for coroutines, we just launch them right away. onDestroy() the corresponding jobs will be canceled automatically.

The benefits

We already highlighted some of the benefits of using the suggested base classes above. In this section, we will once more summarize the benefits…

…for View Binding

When using the base classes, all you have to do is what the compiler tells you: overriding bindingInflater and returning a reference of the ViewBinding’s inflate method. This is done using the double-colon operator in Kotlin which gives you a reference to a — in this case static — method:

override val bindingInflater: 
(LayoutInflater) -> ActivityMainBinding =
ActivityMainBinding::inflate

Being forced to do what the compiler tells you is good because you don’t have to use your brain anymore when initializing View Binding.

…for coroutines

The base classes provide a coroutine scope via this which makes it possible to use launch and async directly, without the need to explicitly use a coroutine context:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
launch {
...
}
}

Again, less thinking and less writing because you don’t have to remember the name of the scope property anymore. You can just start a new coroutine right away by typing launch or async which feels very natural. At the same time, it’s still attached to the Activity’s or Fragment’s lifecycle because the base class is canceling the job onDestroy().

Also, you don’t need to add the androidx.lifecycle:lifecycle-runtime-ktx dependency (you might still want the dependency for other extensions, though). Using lifecycleScope may be more flexible for certain cases, but for general tasks, the presented approach is more than sufficient.

Conclusion

Using simple base classes can make a huge difference in both convenience and efficiency when writing code. Building a solid foundation of base classes, helpers, and extension methods can greatly improve efficiency when implementing features because they can help you to focus on the problem and you are less distracted by implementation details.

The full code with BaseActivity, BaseFragment, and an Activity and Fragment using both base classes are available on GitHub.

--

--