Adapter Design Pattern in Kotlin

CATALIN STEFAN
5 min readJan 29, 2022

What is it

If you’ve worked with Kotlin before, especially in Android development, you will be familiar with the Adapter pattern, simply because it’s used quite often in Android.

At its most basics, an Adapter converts the interface of a class into another interface that the client expects. So we have two different sets of classes that each have their own interface. By interface, we can mean the explicit interface component, or it can simply mean the methods that are available. The Adapter makes the connection between those two and allows those classes to interact.

It can also convert data from one format into another. If your library requires data in a certain format, then that data must be converted to the requirements of a third party client library.

This pattern is used extensively in Android. Let’s have a look at an Android example of a Adapter pattern implementation. This is an example from an app I built for an Android course where I talk about how to build an app using the latest technologies. If you’re interested in that, you can find it here.

So briefly, what we are doing in this project is we’re simply retrieving some data from an endpoint and displaying it in our application. So of course the format the data comes back in from the endpoint is going to be different from the format the data needs to be in to be displayed. That is why we have a CountryListAdapter that converts the list of countries data into a RecyclerView Adapter

Check out the complete course

There are a lot more design patterns than are covered in this tutorial. Check out the full course to learn about all of them.

How to use it

Let’s have a high level view to better understand what Adapter is and how to use it.

Let’s say we have a third party library that has a class Adaptee. This is the class that we’re going to adapt to. This class has a specificCall() method, this is the interface. Since this is a third party library, we cannot modify this, and even if it was our own library we wouldn’t want to modify it simply for one use case. We don’t want strong attachment here, we want the components separated to have the benefits of the separation of concerns principle.

Our local code has a Target class with a call() function. The call() in Target cannot simply use the data from the specificCall() in Adaptee, there is a difference in functionality. So to achieve this interoperability, we need an adapter class.

In the simplest way possible, the call() function will do some processing in the Adapter in order to convert that information so that it can call the interface of the Adaptee class.

Try out some code

In order to put this in practice and give you a practical example, we’re going to implement this pattern in a kotlin example class.

First, let’s implement the 3rd party library classes. We will assume that these cannot be changed or updated in our project.

// 3rd party functionalitydata class DisplayDataType(val index: Float, val data: String)class DataDisplay {
fun displayData(data: DisplayDataType) {
println("Data is displayed: ${data.index} - ${data.data}")
}
}

This 3rd party library has data of a certain type, and has a class DataDisplay that we can use to display that data to the console.

Let’s also assume that our code has a different kind of data from a different source, for example a database. For the purposes of this example, we will implement a class that generates the DatabaseData. We can assume in a real world scenario, we would have a component that retrieves the data from the database and makes it available to other parts of our code.

data class DatabaseData(val position: Int, val amount: Int)class DatabaseDataGenerator {
fun generateData(): List<DatabaseData> {
val list = arrayListOf<DatabaseData>()
list.add(DatabaseData(2, 2))
list.add(DatabaseData(3, 7))
list.add(DatabaseData(4, 23))
return list
}
}

What we would like to do is to use the functionality in the 3rd party library to display the data that we have in our code, i.e. the database data. Since the two data types are different, we cannot simply call the library functionality directly.

So we need to implement an Adapter to adapt the 3rd party library interface to one that we can use.

interface DatabaseDataConverter {
fun convertData(data: List<DatabaseData>): List<DisplayDataType>
}
class DataDisplayAdapter(val display: DataDisplay): DatabaseDataConverter {
override fun convertData(data: List<DatabaseData>): List<DisplayDataType> {
val returnList = arrayListOf<DisplayDataType>()
for (datum in data) {
val ddt = DisplayDataType(datum.position.toFloat(), datum.amount.toString())
display.displayData(ddt)
returnList.add(ddt)
}
return returnList
}
}

Now that all the pieces are in place, we just need to invoke it and see the result. Let’s create a test class and use the functionality we created.

Don’t forget to add the testing imports to your test class.

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
class AdapterTest {
@Test
fun adapterTest() {
val generator = DatabaseDataGenerator()
val generatedData = generator.generateData()
val adapter = DataDisplayAdapter(DataDisplay())
val convertData = adapter.convertData(generatedData)
Assertions.assertThat(convertData.size).isEqualTo(3)
Assertions.assertThat(convertData[1].index).isEqualTo(3f)
Assertions.assertThat(convertData[1].data).isEqualTo("7")
}
}

Running the above code, you should get an output similar to this.

Check out the complete course

There are a lot more design patterns than are covered in this tutorial. Check out the full course to learn about all of them.

--

--