Adapter Pattern in Kotlin
Theo Millard

Theo Millard @tohemt

About: Software Engineer

Joined:
Sep 25, 2024

Adapter Pattern in Kotlin

Publish Date: May 26
0 0
  • Structural design pattern
  • Allow two or more different interfaces to work together
  • Bridge between two classes with different interfaces, allowing the two work together seamlessly.

Problems

You want to use some functionality.
There are so many services that have different interfaces.
Various inputs that only match with one of the interfaces.

Real-life Example

We can see Adapter pattern in literally memory card adapter,
we can read different type of cards (miniSD, microSD) and able to read them all using single adapter

Adapter Pattern

Most of the adapter pattern will have 3 components:

  1. Client - Needs to use adaptee, but incompatible
  2. Adapter - bridge client and adaptee
  3. Adaptee - More than 1 Class, needs to be implemented but doesn't share same interfaces or incompatible with each other.

For Example:

We will be building card reader:

  1. Client - Class MediaReader
  2. Adapter - Class MediaAdapter
  3. Adaptee - MicroSDdReader, MiniSDReader, FlashdiskReader

Kotlin Example

// Interface for adapter class implementation and functionality to be used
// Adapter will implement this
interface CardReader {
    fun read(cardType: String, fileName: String)
}

interface SpecificMediaReader {
    fun read(fileName: String)
}

class MicroSDReader : SpecificMediaReader {
    override fun read(fileName: String) {
        println("Reading from MicroSD: $fileName")
    }
}

class MiniSDReader : SpecificMediaReader {
    override fun read(fileName: String) {
        println("Reading from MiniSD: $fileName")
    }
}

class FlashdiskReader : SpecificMediaReader {
    override fun read(fileName: String) {
        println("Reading from Flashdisk: $fileName")
    }
}

class CardReaderAdapter : CardReaderInterface {
    override fun read(cardType: String, fileName: String) {
        val reader: SpecificMediaReader? = when (cardType.lowercase()) {
            "microsd" -> MicroSDReader()
            "minisd" -> MiniSDReader()
            "flashdisk" -> FlashdiskReader()
            else -> null
        }

        if (reader != null) {
            reader.read(fileName)
        } else {
            println("Unsupported card type: $cardType")
        }
    }
}

// Usage Example
fun main() {
    val cardReader = CardReaderAdapter()

    cardReader.read("MicroSD", "data.txt")
    cardReader.read("MiniSD", "photo.jpg")
    cardReader.read("Flashdisk", "video.mp4")
    cardReader.read("BluRay", "movie.mkv") // Unsupported
}
Enter fullscreen mode Exit fullscreen mode

Common Uses in Android Practices

  1. RecyclerView Adapters: Convert data into ViewHolder items for efficient display in a RecyclerView.
  2. ListView Adapters: Adapt data for ListView and GridView components.
  3. ViewPager Adapters: Adapt pages for ViewPager to display a series of fragments or views.

Here raw data will be translated to what RecylerView(RV) Understand which is UI elements, so that RV can render

// Data class representing an item
data class User(val name: String, val age: Int)

// ViewHolder class
class UserViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val nameTextView: TextView = view.findViewById(R.id.nameTextView)
    val ageTextView: TextView = view.findViewById(R.id.ageTextView)
}

// Adapter class
class UserAdapter(private val userList: List<User>) : RecyclerView.Adapter<UserViewHolder>() {

    // Specify layout
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_user, parent, false)
        return UserViewHolder(view)
    }

    // Translating data to each view will be easier, can easily be customized here
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.nameTextView.text = user.name
        holder.ageTextView.text = user.age.toString()
    }

    override fun getItemCount(): Int {
        return userList.size
    }
}
Enter fullscreen mode Exit fullscreen mode

Pros

  • Allows incompatible interfaces/class to work together
  • Save time and effort, eliminating needs to modify existing code
  • Promotes reusability
  • Adapter handles translation logic, and other can focus on their own functionality
  • Can add new adaptee and only touch the adapter layer
  • Create ways to works with legacy code to match our needs

Cons

  • Adding complexity, translating each classes to fit to the interfaces
  • Overhead, since it needs to be instantiated and maintained
  • More layers
  • Adapter will be rigid because use hardcoded branching

References

Comments 0 total

    Add comment