I'm trying to use Lottie along with Kotlin/JS.
I've been able to create a Lottie external declarations file with Dukat, which gave me an external interface called AnimationConfig
(represents the parameters of the animation that will be played by LottiePlayer
), an external interface called LottiePlayer
(represents all the animation player functions), as well as a variable called Lottie
(of type LottiePlayer
):
@file:JsModule("lottie-web")
@file:JsNonModule
@file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS")
external interface AnimationConfig {
var container: Element
var renderer: String?
get() = definedExternally
set(value) = definedExternally
var loop: dynamic /* Boolean? | Number? */
get() = definedExternally
set(value) = definedExternally
var autoplay: Boolean?
get() = definedExternally
set(value) = definedExternally
var initialSegment: dynamic /* JsTuple<Number, Number> */
get() = definedExternally
set(value) = definedExternally
var name: String?
get() = definedExternally
set(value) = definedExternally
var assetsPath: String?
get() = definedExternally
set(value) = definedExternally
var rendererSettings: Any?
get() = definedExternally
set(value) = definedExternally
val audioFactory: ((assetPath: String) -> `T$1`)?
}
external interface LottiePlayer {
fun play(name: String = definedExternally)
fun pause(name: String = definedExternally)
fun stop(name: String = definedExternally)
fun setSpeed(speed: Number, name: String = definedExternally)
fun setDirection(direction: Number /* 1 */, name: String = definedExternally)
fun setDirection(direction: Number /* 1 */)
fun setDirection(direction: String /* "-1" */, name: String = definedExternally)
fun setDirection(direction: String /* "-1" */)
fun searchAnimations(animationData: Any = definedExternally, standalone: Boolean = definedExternally, renderer: String = definedExternally)
fun loadAnimation(params: AnimationConfig /* AnimationConfig<T> & `T$5` | AnimationConfig<T> & `T$6` */): AnimationItem
fun destroy(name: String = definedExternally)
fun registerAnimation(element: Element, animationData: Any = definedExternally)
fun setQuality(quality: String)
fun setQuality(quality: Number)
fun setLocationHref(href: String)
fun setIDPrefix(prefix: String)
fun updateDocumentData(path: Array<Any /* String | Number */>, documentData: TextDocumentData, index: Number)
}
@JsName("default")
external var Lottie: LottiePlayer
Then in the Kotlin/JS side, I'm trying to use these externals to play an animation inside a Div
generated with Kotlin compose for web. Here is my main()
function:
fun main() {
renderComposable(rootElementId = "root") {
Div( attrs = {
id("svgContainer")
}) {
Text("Hola mundo!")
@Suppress("DEPRECATION")
DomSideEffect {
val svgContainer = it
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
val animationConfig = js("{}") as AnimationConfig
animationConfig.container = svgContainer
animationConfig.loop = false
animationConfig.autoplay = true
animationConfig.assetsPath = "https://labs.nearpod.com/bodymovin/demo/markus/halloween/markus.json"
Lottie.loadAnimation(animationConfig)
}
}
}
}
The problem with this implementation is that the application throws a runtime error indicating that the variable Lottie
is undefined:
Uncaught TypeError: Cannot read properties of undefined (reading 'loadAnimation')
I haven't found a way to use the available variable Lottie
in order to get an instance of it and use it.
How should I use either the variable Lottie
or the external interface LottiePlayer
?
Thanks in advance.
Currently Dukat is incredibly experimental, and essentially never generates the correct bindings, if it works at all. It's best used as an initial step to generate a starting point, and then manually implementing the correct bindings by manually inspecting and understanding the actual JavaScript and TypeScript.
Looking at the index.d.ts
for lottie-web
I can see the definition for LottiePlayer
.
The relevant part is the loadAnimation()
function.
export type LottiePlayer = {
// ...
loadAnimation<T extends RendererType = 'svg'>(params: AnimationConfigWithPath<T> | AnimationConfigWithData<T>): AnimationItem;
// ...
}
The function accepts a union type with two different types,
AnimationConfigWithPath<T>
AnimationConfigWithData<T>
These both extend a base type, AnimationConfig<T>
.
However Dukat has generated a function that only accepts AnimationConfig
external interface LottiePlayer {
// ...
fun loadAnimation(params: AnimationConfig /* AnimationConfig<T> & `T$5` | AnimationConfig<T> & `T$6` */): AnimationItem
// ...
}
(Dukat has also generated a comment (/* AnimationConfig<T> & `T$5` | AnimationConfig<T> & `T$6` */
) indicating that it knows that the argument is a union type.)
<T extends RendererType>
The generated AnimationConfig
does not have a type parameter, which might usually be a problem. It's not surprising that Dukat failed because the parameter is bounded by a string union, which isn't something that's easily mappable to Kotlin.
export type RendererType = 'svg' | 'canvas' | 'html';
export type AnimationConfig<T extends RendererType = 'svg'> = {
// ...
}
However it is bounded to be a constant string, 'svg'
, so the Kotlin binding doesn't need to be aware of it - any instance of T
will just return a string. Fortunately the same is true of the subtypes, AnimationConfigWithPath
and AnimationConfigWithData
.
Kotlin doesn't have union types, which can be an issue when trying to bind functions that return unions. It's not a problem with functions arguments though, because that's the same as function overloading, which Kotlin already has.
external interface LottiePlayer {
// fun loadAnimation(params: AnimationConfig /* AnimationConfig<T> & `T$5` | AnimationConfig<T> & `T$6` */): AnimationItem
fun loadAnimation(params: AnimationConfigWithPath): AnimationItem
fun loadAnimation(params: AnimationConfigWithData): AnimationItem
}
And then you have to define externals for AnimationConfigWithPath
and AnimationConfigWithData
.
external interface AnimationConfigWithPath : AnimationConfig {
var animationData: dynamic
}
external interface AnimationConfigWithData : AnimationConfig {
var animationData: dynamic
}
I haven't tested this code - so if there are corrections please let me know.