Sunday 5 June 2022

SAM - Functional Interface




Almost we all of us have seen code like view.setOnClickListener { } or view.setOnLongClickListener { } in Kotlin, and when you click on it to see the source code it will show you the Java OnClickListener interface. Is it a kotlin extension? How can we call our custom listener like this? Let’s see the answer to all of these now.

This syntax is not an extension, it is SAM conversion, SAM stands for Single Abstract Method interface and it’s called also Functional interface which is an interface with only one non-default method (abstract method) and any number of default methods (non-abstract methods), for examples In the JDK we have Runnable class which has only one method called run and in Android SDK we have OnClickListener, OnLongClickListener etc..

How can we create a custom functional interface?

In Kotlin starting from Version 1.4, we can declare a functional interface in Kotlin, we need to use the fun modifier before to the interface keyword.
fun interface MyInterface {
    fun aMethod()
}
Creating a function with the functional interface as param

fun runMyInterface(fi : MyInterface) { ... }


We can pass MyInterface as an anonymous object 

runMyInterface(object : MyInterface {

    override fun aMethod() {

        println("Welcome to www.rajendhiraneasu.in")

    }

})

For functional interfaces, SAM conversion can be achieved through lambda expressions, which makes the code more concise and more readable.  Using lambda expressions can replace manually creating classes that implement functional interfaces. Through SAM conversion, Kotlin can convert any lambda expression whose signature matches the signature of a single abstract method of an interface into an instance of a class that implements the interface.  Please see the above anonymous reference with lambda expressions as below

runMyInterface ({ println("Welcome to www.rajendhiraneasu.in") })


Also, in Kotlin if your last parameter is functional interface you can move your lambda out the brackets ()

runMyInterface {

    println("Welcome to www.rajendhiraneasu.in")

}


Now you can realize that setOnClickListener { } is just because this method takes functional interface which is OnClickListener as a parameter.

Eg: Let’s see with the use case to get the Students Grade of different class levels.
 
// Class Levels
const val PRIMARY_SCHOOL = 1
const val HIGH_SCHOOL = 2

// Data Class holds the student details
data class StudentMarks(val sName: String, val classLevel: Int, val sAvg: Double) {
    fun getStudentGrade(predicate: GradePredicate) = predicate.getGrade((sAvg))
}

// Functional interface (SAM)
fun interface GradePredicate {
    fun getGrade(avg: Double): String
} 

// Grade Predicate definition as lambda 
val primarySchoolGrade = GradePredicate {
    when {
        it > 90 -> "A+"
        it > 80 -> "A"
        it > 65 -> "B"
        it > 45 -> "C"
        it > 34 -> "D"
        else -> "F"
    }
}

val highSchoolGrade = GradePredicate {
    when {
        it > 80 -> "Very Good"
        it > 60 -> "Good"
        it > 34 -> "Fair"
        else -> "Fail"
    }
}

// Lambda expression to select the grade predicate based on the class level 
val gradeSel by lazy { { classLevel: Int -> if (classLevel == PRIMARY_SCHOOL) primarySchoolGrade else highSchoolGrade } }

fun main() {
 val studentList = listOf(
        StudentMarks("Ragavan", PRIMARY_SCHOOL, 92.79),
        StudentMarks("Rajeevan", PRIMARY_SCHOOL, 65.15),
        StudentMarks("Rajeevan", PRIMARY_SCHOOL, 52.23),
        StudentMarks("Arun", HIGH_SCHOOL, 83.21),
        StudentMarks("Harish", HIGH_SCHOOL, 63.56)
    )

    println("Name || Grade")
    studentList.forEach {
        println(
            "${it.sName} ||  ${
                it.getStudentGrade(gradeSel(it.classLevel))
            }"
        )
    }
}
 
Output:



No comments: