Search code examples
kotlincallbackarchitecture

Implementing functions callback for a library without enumerating lots of callbacks


I'm working on a library firing methods at different times, but these methods need to be implemented by the user.

abstract class MainClass(
    username: String,
) {
    abstract var callback1 : ((greeting : String)->Unit)

    private var secondClass: SecondClass

    init {
        secondClass= SecondClass(username, callback1)
    }
}

class SecondClass(username: String, callback1 : (greeting : String)->Unit){
    init {
        callback1.invoke("Hello $username")
    }
}

fun main() {
    val mainClass= object : MainClass("Loulou") {
        override var callback1= { greeting : String ->
            println(greeting)
        }
    }
}

The idea is that the user using the library could change the implementation of callback1, but I found the syntax very heavy when we have saying 50 callbacks methods to pass to SecondClass

How can this design be structured to avoid passing 50 parameters to the second class?


Solution

  • You can put all the callback properties in an interface with default implementations for the ones where that's acceptable:

    interface MyApiCallbacks
        val callback1: (greeting: String)->Unit
        val callback2: (count: Int)->Unit = {} // not required for User to override
        // etc.
    }
    

    Your second class can take an instance of the interface as a parameter to get all the callbacks in one:

    class SecondClass(username: String, val callbacks: MyApiCallbacks){
        init {
            callbacks.callback1.invoke("Hello $username")
        }
    }
    

    Then your main class can implement the interface to automatically have all these overridable callbacks that have default implementations:

    abstract class MainClass(
        username: String,
    ): MyApiCallbacks {
        
        private var secondClass: SecondClass = SecondClass(username, this)
    }
    

    The user can override only the ones they need to when creating the main class. They will have to override any that lack a default implementation in the interface.

    fun main() {
        val mainClass = object : MainClass("Loulou") {
            override val callback1 = { println(it) }
        }
    }
    

    You could also do this without an interface, and have SecondClass take a MainClass as a parameter, but that's not as good for encapsulation. Probably SecondClass doesn't need to know about other features of the MainClass.