We have been facing a runtime issue since our upgr...
# javascript
m
We have been facing a runtime issue since our upgrade to KMP
2.0.0
where some of our `addEventListener`/`removeEventListener` that are coming from an external definition (IMA SDK or FreeWheel SDK mapping) are removed at transpilation from Kotlin to JS and replaced by a
throwLinkageError
with message:
Copy code
Function 'addEventListener' can not be called: No function found for symbol 'googleima/Google.Ima.AdsLoader.addEventListener|addEventListener(kotlin.String;kotlin.Function1<kotlin.Nothing?,kotlin.Unit>;kotlin.Boolean){}[0]'
It only occurs on transpilation following code modification within the class that uses the external definition mapping, and disappear following these 2 steps: a clean up (gradle intensifies clean and a removal of the
kotlin-js-store
folders) and a simple new line added to the current code base. It is very strange, we cannot for the love of god isolate it to a simple test projet, it only do that within our huge code base. Any idea if this is known or if there is a possible workaround? P.S.: attached more details in 馃У 馃憠
External definition of IMA SDK:
Copy code
package googleima

@JsName("google")
external object Google {
    @JsName("ima")
    object Ima {
        class AdsRequest {
            var adTagUrl: String?
            var vastLoadTimeout: Double?
            fun setAdWillAutoPlay(value: Boolean)
            fun setAdWillPlayMuted(value: Boolean)
        }

        class AdsManager {
            fun addEventListener(event: String, callback: (event: dynamic) -> Unit, useCapture: Boolean = definedExternally)
            fun removeEventListener(event: String, callback: (event: dynamic) -> Unit)
        }
    }
}
Screenshot 2024-09-05 at 4.41.14鈥疨M.png,Screenshot 2024-09-05 at 4.45.14鈥疨M.png
Screenshot 2024-09-09 at 8.41.12鈥疉M.png
Code that uses the definitions:
Copy code
private var onAdStartedReference: ((event: Google.Ima.AdEvent) -> Unit)? = null

    private fun setUpAdsManagerEventListeners(adsManager: Google.Ima.AdsManager) {
        onAdStartedReference = { onAdStarted(it) }

        adsManager.addEventListener(Google.Ima.AdEvent.Type.STARTED, onAdStartedReference!!)
    }

    private fun removeAdsManagerEventListeners(adsManager: Google.Ima.AdsManager) {
        onAdStartedReference?.let { adsManager.removeEventListener(Google.Ima.AdEvent.Type.STARTED, it) }
    }

    private fun onAdStarted(event: Google.Ima.AdEvent) {
    }
e
First thing I'd do is upgrade to 2.0.20, and verify if it still happens.
m
Good point, will do this.
t
Pill - remove
dynamic
usage from callbacks 馃槈
And it's common recommendation - don't use
dynamic
in externals. In most cases
Any?
is what you need instead.
m
Really? We been using dynamic all over the place since the beginning of our project, 3 years ago, never had an issue with that.
e
I mean it's not an issue per se. It's just that Any is a proper type, dynamic is not. That said, dynamic doesn't introduce type checks, and you'll never deal with class cast exceptions, so it definitely has its use cases.
m
Alright! Thats good information! We will adapt and look into using any? in that particular case, and advise for the rest.
e
Better not go around fixing what's not broken. Start introducing it only for new externals.
m
Yep, thats what we will do!
What is the risk of a cast from any to an external definition event type? Shall I protect myself everytime with a try/catch? i.e:
Copy code
private fun onAdError(event: Any) {
   val errorEvent = event as Google.Ima.AdErrorEvent
}
e
Not needed. But, can
event
be better typed, or is
Any
the only way to do it?
m
Well, depends on the use case, but for those particular add/remove event listeners, the event is dynamic and can receive different object types. Example from FreeWheel:
Copy code
private var onFreeWheelRequestCompleteReference: ((event: FreeWheel.SDK.RequestCompleteEvent) -> Unit)? = null
    private var onFreeWheelSlotStartedReference: ((event: FreeWheel.SDK.SlotEvent) -> Unit)? = null
    private var onFreeWheelSlotEndedReference: ((event: FreeWheel.SDK.SlotEvent) -> Unit)? = null
Because of this, I was using dynamic in the external definition. Using Any now, forces us to do the cast as mentioned above. These 2 types are objects like this:
Copy code
object RequestCompleteEvent {
                val type: String
                val target: Context
                val success: Boolean
            }

            object SlotEvent {
                val type: String
                val target: Context
                val slot: Slot
            }
e
So, where specifically is
onAdError
called?
m
onAdError was actually from our Google IMA implementation. Im currently doing both as we speak, but FreeWheel was easier to explain 馃槈
On IMA SDK, these are the 2 types that can be sent as event from the addEventListener callback:
Copy code
object AdErrorEvent {
            fun getError(): AdError
            fun getUserRequestContext(): Any
        }
        object AdEvent {
            val type: String
            fun getAd(): Ad?
            fun getAdData(): AdData?
        }
e
Seems quite a complex scenario. I guess the best advice I can give you is to try to split up types. Use function overloads and cast at the very source of the event if you receive an "any"-like object
Are you writing externals manually or translating them from TypeScript?
I've found https://github.com/alugha/typed-ima-sdk, which could be a good start to generate the external types. Not sure how up to date it is.
t
FAQ for externals
馃憖 1
e
Since that library uses many enums, Seskar could also be handy.
m
Good point on the overload, I missed that as a possible approach!
馃挴 on the method overloads, that was exactly it, instead of Any or dynamic. Works wonder. Thanks guys, much appreciated. Since the issue is unpredictable, I will ping back if we encounter it again following these changes.