https://kotlinlang.org logo
#getting-started
Title
# getting-started
c

Conor Gould

11/18/2023, 3:39 PM
Hi! I'm trying to create a Kotlin wrapper for a Java library, and I'm having some trouble. I declare a map of type
Map<String, List<(Event) -> Unit>>
, and then I have this function that adds a lambda into the map:
Copy code
inline fun <reified T : Event> T.subscribe( handler: (T) -> Unit) {
    EventManager.handlers.getOrPut(T::class.simpleName!!, mutableListOf<(T) -> Unit>())
}
The problem is is that I am getting this error:
Copy code
Type mismatch.
Required:
() → TypeVariable(V)
Found:
MutableList<(T) → Unit>
Even though I declare
T
as a subclass of
Event
. Is this intended behavior?
d

Daniel Pitts

11/18/2023, 3:57 PM
The problem is that T is a subclass of Event, not a super. You’d have to declare the list element as (Any)->Unit
Wait scrap that…
You can’t do what you’re trying to do without an unsafe cast somewhere, since the type information is lost when you put the list into the map.
c

Conor Gould

11/18/2023, 4:23 PM
Ohhhhhhh. Okay, thank you. I meant subclass anyways.
d

Daniel Pitts

11/18/2023, 4:23 PM
I'm writing up an example that will work, hold on a second.
c

Conor Gould

11/18/2023, 4:23 PM
Oh, well thank you!
d

Daniel Pitts

11/18/2023, 4:29 PM
I would go about it this way:
Copy code
import kotlin.reflect.KClass

typealias Handler<T> = (T) -> Unit
typealias HandlerList<T> = MutableList<Handler<T>>

interface Event

class EventManager {
    private val handlers: MutableMap<KClass<*>, HandlerList<*>> = mutableMapOf()

    inline fun <reified T : Event> subscribe(noinline handler: Handler<T>) = subscribe(T::class, handler)

    fun <T : Event> subscribe(kClass: KClass<T>, handler: Handler<T>) {
        handlers.computeIfAbsent(kClass) { mutableListOf() }.add(handler)
    }

    inline fun <reified T : Event> dispatch(event: T) = dispatch(T::class, event)

    fun <T : Event> dispatch(kClass: KClass<T>, event: T) =
        handlerList(kClass).forEach { handler -> handler(event) }

    @Suppress("UNCHECKED_CAST")
    private fun <T : Event> handlerList(kClass: KClass<T>): List<Handler<T>> =
        handlers[kClass] as? HandlerList<T> ?: emptyList()
}
c

Conor Gould

11/18/2023, 4:29 PM
I see where I went wrong.
MutableMap#getOrPut
takes
(K, () -> V)
d

Daniel Pitts

11/18/2023, 4:29 PM
Note, this is untested.
It should compile, but whether it actually works or not 🤷
c

Conor Gould

11/18/2023, 4:30 PM
I see. thank you
i'll test it
d

Daniel Pitts

11/18/2023, 4:31 PM
I wrote this in hopes that you can dissect it and understand what it is doing (rather than just using it as is). If you have questions about how any piece of it works, please feel free to ask!
c

Conor Gould

11/18/2023, 4:32 PM
Yep.
It's such a simple solution..... `
Copy code
inline fun <T : Event> T.subscribe(handler: (T) -> Unit) {
    EventManager.handlers.getOrPut(this::class.simpleName!!) { mutableListOf() }
}
because I wasn't passing a lambda.
I really do need to look at function signatures.
Thanks for the help, though!
d

Daniel Pitts

11/18/2023, 4:34 PM
The terms you should learn about are covariance and contravariance
In particular, the Contravariant method parameter type section and below
c

Conor Gould

11/18/2023, 5:05 PM
Ah, thank you 🙂. I just realized this isn't even possible, as I cannot introduce a
companion object
to a java class.
d

Daniel Pitts

11/18/2023, 5:06 PM
You can use
@JvmStatic
to create static methods though.
c

Conor Gould

11/18/2023, 5:07 PM
nevermind nly members in named objects and companion objects can be annotated with '@JvmStatic'
Only members in named objects and companion objects can be annotated with '@JvmStatic'
I guess this is a lost cause lol.
o

Oliver Eisenbarth

11/18/2023, 9:01 PM
If anyone seeing this wants to learn about variance, there's a great guide by Dave Leeds https://typealias.com/guides/illustrated-guide-covariance-contravariance/
👍 1
d

Daniel Pitts

11/18/2023, 9:01 PM
I'm a little confused on what you're trying to do then. I thought you were calling Java code from Kotlin, not the other way around.
c

Conor Gould

11/19/2023, 1:18 AM
Nope, making a Kotlin wrapper for a Java library.
Currently, you'd have to create a class and then annotate a method with
@EventHandler
, but that's ugly and unintuative. I was going to make an
object
that subscribes to all of the events that are possible to subscribe to, and then iterate through the handlers for that event and call each one. This would allow you to, instead of using an entire class/file, use one line of code to handle an event.
d

Daniel Pitts

11/19/2023, 1:21 AM
Which library are you trying to wrap?
c

Conor Gould

11/19/2023, 1:22 AM
Event.subscribe { println("event called" }
instead of
class EventHandler : Listener { @EventHandler fun onEvent(event: Event) { println("event called" } }
I use it all the time, and hate the syntax.
d

Daniel Pitts

11/19/2023, 1:24 AM
So the syntax you'd need is going to be more like
subscribe<Event> { println("event called") }
c

Conor Gould

11/19/2023, 1:25 AM
I'd never though of that. Thank you!
d

Daniel Pitts

11/19/2023, 1:29 AM
It looks like maybe the event handling is actually done by Bukkit? I don't actually know a lot about Minecraft modding.
You might be able to call this method directly from your
subscribe
implementation.
Copy code
object DefaultListener : Listener

inline fun <reified E : Event> Plugin.subscribe(
    priority: EventPriority = EventPriority.NORMAL,
    ignoreCancelled: Boolean = false,
    crossinline handler: (E) -> Unit
) {
    pluginManager.registerEvent(
        E::class.java, DefaultListener, priority,
        object : EventExecutor {
            override fun execute(listener: Listener, event: Event) {
                handler(event as E)
            }
        },
        this,
        ignoreCancelled
    )
}
If I'm correct, I think you can use that, and then inside methods in your Plugin class, you can do
Copy code
subscribe<SomeEvent> { println(it) }
Where
it
is the SomeEvent object. This also lets you override the priority and the ignoreCancelled, just like you can with the annotation.
c

Conor Gould

11/19/2023, 3:45 AM
The event handling is done by Bukkit, correct.
and, this is now a reality. Thank you 🙂
Copy code
EventManager.subscribe<BlockBreakEvent> { event ->
            
}
I just ended up registering
EventManager
as a listener for every event. Generated ~3k lines of code.
d

Daniel Pitts

11/19/2023, 3:47 AM
That’s one way to do it ;-)
c

Conor Gould

11/19/2023, 3:48 AM
I didn't even see your other reply. Looks neat (and useful 😭 )
d

Daniel Pitts

11/19/2023, 3:48 AM
Could slow things down though , if there are a lot of events fired that you don’t care about.
c

Conor Gould

11/19/2023, 3:48 AM
Oooh.
Hmmm
d

Daniel Pitts

11/19/2023, 3:49 AM
If you have something working now though, don’t worry about it unless you see slowness.
c

Conor Gould

11/19/2023, 3:49 AM
All I do is call
handlers[event.eventName]?.forEach { it(event) }
yeah, that was my thought process.