Interfaces and Abstract Classes in Kotlin: Getting Started

Learn how to best use interfaces and abstract classes to create class hierarchies in your Kotlin Android apps. By Mattia Ferigutti.

4.5 (4) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Implementing Interfaces in the Project

Animals have different abilities. For example, kangaroos can jump very high, cheetahs can run extremely fast and some fish can survive in the ocean’s greatest depths. However, animals often have similar abilities.

In this project, you’ll implement three main abilities animals share: Walking, flying and swimming. These abilities are represented by the following interfaces: Walkable, Flyable and Swimmable.

While it may seem straightforward, animal categories are complicated. For example, not every mammal can walk and not every bird can fly. So Mammal can’t define member runningSpeed() if not all mammals can walk or run. You must implement these specific behaviors separately.

At the root of the project, create a package and name it interfaces. In this package, create the following interfaces in their respective files:

Walkable.kt

interface Walkable {
  val runningSpeed: Int
}

Walkable.kt

interface Flyable {
  val flyingSpeed: Int
}

Walkable.kt

interface Swimmable {
  val swimmingSpeed: Int
}

Every interface implements a speed member representing how fast the animal can go. In this example, the code shows a single property for every interface, but feel free to play around with the code and add some abstract methods and methods with bodies.

Finally, every animal has to implement the right interface. Let Bat and Parrot implement Flyable. Then set their flyingSpeeds to 95 and 30 respectively. For instance, update Bat with:

class Bat : Mammal(), Flyable

and …

  override val flyingSpeed: Int
    get() = 95

Let Elephant, Giraffe and Lion implement Walkable. Then set their runningSpeeds to 40, 60 and 80 respectively. For instance, update Elephant with:

class Elephant : Mammal(), Walkable 
  override val runningSpeed: Int
    get() = 40

Lastly, let Penguin implement both Walkable and Swimmable. Then set the runningSpeed and swimmingSpeed to 7 and 40 respectively. Here are the required changes:

class Penguin : Bird(), Walkable, Swimmable 

and …

  override val runningSpeed: Int
    get() = 7

  override val swimmingSpeed: Int
    get() = 40

Here’s a code breakdown:

  1. Bat, Parrot, Elephant, Giraffe and Lion implement a single interface and redefine a single method.
  2. Penguin has two skills. It can swim and run, even though he’s pretty slow. That’s why it’s crucial to have the ability to implement multiple interfaces.

The Practical Side of Interfaces

While you now understand why your future projects should implement interfaces, it’s often hard to grasp this argument in a practical way. In this section, you’ll explore the practical side of interfaces.

As you may have noticed, Zoo Guide is still missing an important feature, the filter.

Spinner in the main screen

The Spinner lets you choose between the three different behaviors defined above, Walkable, Flyable and Swimmable, and an All option that includes all the animals in the list. When you select one of these options, the list updates.

Navigate to ui/fragments package. Then open ListFragment.kt. Inside onItemSelected(), replace the TODO with:

val updatedList: List<Animal> = when(position) {
  0 -> AnimalStore.getAnimalList()
  1 -> AnimalStore.getAnimalList().filter { it is Flyable }
  2 -> AnimalStore.getAnimalList().filter { it is Walkable }
  3 -> AnimalStore.getAnimalList().filter { it is Swimmable }
  else -> throw Exception("Unknown animal")
}
viewModel.updateItem(updatedList)

Here’s a code breakdown:

  1. Anytime a user clicks an item inside the Spinner, onItemSelected() is called.
  2. The when statement checks the position and filters the list based on the item the user selects.
  3. ListViewModel defines updateItem(). You need to call it every time you update the list to notify the adapter that something has changed.

filter will check every Animal object in the list and choose only the one that respects the behavior the user selected.

Now you can see how an interface isn’t only a contract to implement methods and properties but also creates a sub-category of objects. You can instantiate a group of objects, such as arrays or lists, that share the same behavior. They may be completely different objects, but they share the same behavior.

Holding State Inside an Interface

If you’ve searched for interfaces on the internet, you’ve likely encountered some blogs that say interfaces can’t hold states. That was true until Java 8 introduced methods with a body inside interfaces. But, what’s the state?

The state is what an object has. For example, the name property inside Bird defines a state of a Bird object. If an object doesn’t have any instance properties or has some properties with unknown values that don’t change, it’s stateless.

Where can you store the state? You store it in a companion object property.

Consider the code below:

// 1
interface Animal {
    var name: String
        get() = names[this]?.toUpperCase() ?: "Not in the list"
        set(value) { names[this] = value }

    var speed: Int
        get() = speeds[this] ?: -1
        set(value) { speeds[this] = value }

    val details: String
        get() = "The $name runs at $speed km/h"

    companion object {
        private val names = mutableMapOf<Animal, String>()
        private val speeds = mutableMapOf<Animal, Int>()
    }
}

// 2
class Lion: Animal

// 3
class Penguin: Animal

// 4
fun main() {
    val lion = Lion()
    lion.name = "Lion"
    lion.speed = 80

    val penguin = Penguin()
    penguin.name = "Penguin"
    penguin.speed = 7

    println(lion.details) // The LION runs at 80 km/h
    println(penguin.details) // The PENGUIN runs at 7 km/h
}

Similar to Zoo Guide, this code will save a list of animals.

Here’s what the code does:

  1. Every animal has three properties: a name, speed and details. The first two are mutable while the last one is immutable.
  2. Both name and speed can get and store a value using a MutableMap. Note that the key of the Map is a reference to the Animal interface itself that changes every time a class implements it.
  3. The Animal interface has two private static mutableMaps that hold the state.
  4. Both Lion and Penguin implement Animal. They don’t have to define any members of Animal since all of them have been initialized.

Change the names‘s visibility from private to public and iterate the list:

// 1
interface Animal {
    
    companion object {
        val names = mutableMapOf<Animal, String>()
    }
}

// 2
Animal.names.forEach { name ->
    println(name.value) 
} 
    
//Output:
//Lion
//Penguin

As you can see, Animal.names holds all the values.

The scope of this example was only to show you that it’s possible to store the state inside an interface. However, you shouldn’t do that in real-world projects because it’s poorly managed and not cleaned properly by the garbage collector.