Reputation: 307
I am writing some coroutines based event handling code in Kotlin and it's going great. I have code in various events handlers that does the same thing and I am trying to put this code in one place. I am stuck on the following. The idea is that subclasses can specify which event types the can handle by providing a map of classes to methods. I can't get it to compile though. Is there a way to make this work? Is there a better way of doing it? Thanks.
abstract class EventHandler(private val handlers: Map<KClass<out Event>, suspend (Event) -> Unit>) {
suspend fun handle(event: Event) {
val handler = handlers[event::class]
if (handler != null) {
handler(event)
} else {
throw IllegalStateException("No handler configured for $event")
}
}
}
data class ExampleEvent(private val id: String): Event
class ExampleHandler(): EventHandler(mapOf(ExampleEvent::class to handleExample)) {
^^^^^^^^^^^^^ - compile error
suspend fun handleExample(event: ExampleEvent) {
TODO()
}
}
Upvotes: 1
Views: 159
Reputation: 16214
You can't get it to compile for 3 different reasons:
Since handleExample
is an instance method, you can't refer it in the super constructor since the instance of your subclass isn't created yet.
If you want a function reference of an instance method you should prefix it with ::
, so in your case ::handleExample
.
The function handleExample
accepts an event of type ExampleEvent
so it doesn't conform to the input type Event
, in this case, you'd need a cast.
That said, there's a solution to your problem which solves the 3 points above and the burden to repeat that boilerplate for each EventHandler
subclass.
The explanation is all on the comments.
inline fun <reified T : Event> typedCoroutine(crossinline block: suspend (T) -> Unit): Pair<KClass<out Event>, suspend (Event) -> Unit> =
// Since the type is reified, we can access its class.
// The suspend function calls the required suspend function casting its input.
// The pair between the two is returned.
T::class to { event -> block(event as T) }
abstract class EventHandler {
// Require the subclasses to implement the handlers.
protected abstract val handlers: Map<KClass<out Event>, suspend (Event) -> Unit>
suspend fun handle(event: Event) {
val handler = handlers[event::class]
if (handler != null) {
handler(event)
} else {
throw IllegalStateException("No handler configured for $event")
}
}
}
class ExampleHandler : EventHandler() {
// The type of typedCoroutine is inferred from handleExample.
override val handlers: Map<KClass<out Event>, suspend (Event) -> Unit> = mapOf(typedCoroutine(::handleExample))
suspend fun handleExample(event: ExampleEvent) {
TODO()
}
}
Using typedCoroutine
you can fill the handlers
map easily in all your EventHandler
subclasses.
Upvotes: 3