Ivan Garza Ivan is an Android engineer at Mixhalo, a passionate soccer fan, and an amateur salsa maker.

How to extend classes in Kotlin without using inheritance

4 min read 1196

Kotlin Logo

We have all used the process of inheritance, the object-oriented programming (OOP) concept in which any given class may inherit all the variable fields and functionality from another class.

The class inheriting the properties is called the child class, or subclass, while the class that provides the baseline properties is known as the parent class. This pattern is found all over Android applications.

All of our activity classes inevitably extend the superclass Activity to override and manipulate some of its most fundamental functionality, such as its lifecycle.

With the arrival of Kotlin, we were introduced to several new ideas that hadn’t been present in Java before, such as Kotlin extensions, which provide the ability to extend a class with new functionality without having to inherit or subclass from it, as Kotlin’s official documentation explains.

Because of the similar nature that both classic inheritance and the contemporary Kotlin extensions provide, it’s difficult to determine which one of these tools to leverage to solve a problem at hand. This is why these concepts are usually contrasted side by side.

In this tutorial, we’ll compare and contrast inheritance and Kotlin extensions to understand the pros and cons of each. Here’s what we’ll cover:

What is conventional inheritance in Kotlin?

Let’s expand on our previous example using the Activity class. Traditionally, inheritance in Java was signified by the extends keyword, which made it very explicit and apparent to the reader what was going on:

public class MyActivity extends Activity {
    ...

Nowadays in Kotlin, the inheritance pattern syntax is not as obvious as it used to be:

class MyActivity() : Activity() {
    ...

By default, and as the Kotlin official documentation describes, all classes in Kotlin inherit from a common superclass, Any, which serves as the default parent class for all classes that do not declare one explicitly.

Any has three main methods that all classes inherit: equals(), hasCode(), toString(). All Kotlin classes are final, so they cannot be inherited. To make a class inheritable, the keyword open needs to be present at the beginning of the class signature, which also makes them non-final.

We made a custom demo for .
No really. Click here to check it out.

open class Activity() { ... }

class MyActivity() : Activity() {
    ...

Inheritance also allows the child class to access all protected fields, functions, and variables from the parent class, which are typically hidden from all outside callers. If access wasn’t enough, child classes may also override either functions or variables from the superclass, as long as they’re visible to the subclass and non-final as well.

open class Activity() { ... }

class MyActivity(): Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
    }
    ...
}

For more detailed information on inheritance, check out either Java’s official documentation for a more traditional approach to OOP’s inheritance or Kotlin’s more modern approach to the topic.

What are Kotlin extensions?

Kotlin extensions allow us to extend a class without the need to inherit, or without using design patterns that enable this sort of functionality, such as the Decorator design pattern.

We can extend the functionality of all kinds of classes, including final classes, third-party library classes, etc., without the need to create a subclass. Because we are not subclassing our base class, extension functions only have access to public fields, and do not protect ones as regular inheriting classes usually do.

The added functionality from extensions is resolved statically and can be called normally as any other regular method and from any instance of the class that has been extended.

Here’s an example of a very common function we execute in every Android application:

fun Activity.requestPermission(permission: String, requestCode: Int) {
    ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
}

Requesting permissions usually requires an instance of Activity, which serves as a perfect opportunity to add such functions as an extension function into the general Activity class. It can be called directly from any particular Activity class implementation.

open class Activity() { ... }

fun Activity.requestPermission(permission: String, requestCode: Int) {
    ActivityCompat.requestPermissions(this, arrayOf(permission), requestCode)
}

class MyActivity: Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        requestPermission(
            permission = Manifest.permission.ACCESS_FINE_LOCATION,
            requestCode = 2022
    }
    ...
}

By extending a class, we are not inserting new properties into it. Instead, we’re creating new functions that can be called using the dot-notation (.) on variables of this type.

The new Kotlin extensions paradigm also introduced extension variables with it, which work similarly to how extension functions do except that, because we don’t have full visibility or access to the base class, extension variables cannot be backed up by the base class altogether. They also cannot access protected fields from the base class.

For a deeper analysis of the extent of possibilities that Kotlin extensions can provide, check out my fellow LogRocketeer’s article on the topic.

Inheritance vs. Kotlin extensions

Let’s compare and contrast the main differences between them.

Modifying types of classes in Kotlin

We’ll start with the type of classes each of these concepts can modify. While inheritance can only inherit properties from non-final classes, i.e., open classes in Kotlin, Kotlin extensions can add functionality to all kinds of classes, no matter their “openness.”

open class InheritableClass() { … }

Extending properties into classes

Kotlin extensions can extend properties into classes without the need to subclass. When it comes to the accessibility of the base class, inherited child classes have access to all protected variables from the parent class, whereas extensions do not have access to the protected fields of their base class the same way.

open class InheritableClass() { … } // This class can be inherited from
class InheritingClass : InheritableClass() { … } // This class is inheriting, but may not be inherited from
fun InheritingClass.someExtension() { … } // Extension may happen in non-open classes

Adding new variables

Although child classes can add new variables into the object instance declaration of the parent class, Kotlin extensions cannot add variables that are backed up using extension variables, as these are only processed statically.

open class InheritableClass() { … }
class InheritingClass : InheritableClass() { … }

Here’s a comparative table to visualize the differences more structurally:

Adding functionality Adding functionality without subclassing Extending final classes (non-open) Adding variables with backing fields Accessing protected fields and functions
Inheritance
Kotlin Extensions

Conclusion

Of course, different tools and concepts are meant to be used for different situations. Kotlin extensions are great for creating quick-access functionality for any given class, no matter where it comes from. They serve as a great substitute for the old Java notion of utility classes, as we can now simply add new functions that are easily discoverable and utilized without much boilerplate code.

On the other hand, using inheritance is ideal whenever we need to create a hierarchy of related classes, as it allows for natural grouping thanks to the parent-child relationship it creates between the members of such a hierarchy. It also allows the inheriting class to override the previous functionality of parent classes, something that can be effective whenever we need similar objects to act slightly differently.

Both inheritance and extensions have their advantages and disadvantages. We may prefer one over the other, depending on the type of project you’re working on. I hope this overview helped you better understand Kotlin extensions and inheritance. Thanks for reading!

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Ivan Garza Ivan is an Android engineer at Mixhalo, a passionate soccer fan, and an amateur salsa maker.

Leave a Reply