Joost Klitsie
07/06/2020, 2:04 PMclass App : RComponent<RProps, RState>() {
override fun RBuilder.render() {
// typesafe HTML goes here!
}
}
Usually this is build using a static helper method to add a child to the parent component. In spirit of this, I did manage to put a component class into a state and call a render method upon it. Also hooking it up to the lifecycle of the parent:
private class SomeButtonComponent(private val props: SomeButtonProps) :
BaseComponent<SomeButtonProps>(props) {
override fun DI.MainBuilder.initModule() {
// Nothing to do here
}
@Composable
override fun render() {
Button(onClick = {
lifecycleScope.launchWhenCreated {
props.handler()
delay(200)
}
}) {
Text(text = "I am a button")
}
}
override fun cleanUp() {
// Nothing to do here
}
companion object : BaseComponentRenderer<SomeButtonComponent, SomeButtonProps>() {
override fun provideComponent(props: SomeButtonProps): SomeButtonComponent {
return SomeButtonComponent(props)
}
}
}
data class SomeButtonProps(
override val parent: ComposableComponent, // Contains Kodein DI and is a lifecycle owner
val handler: () -> Unit // Callback method after button click
) : BaseProps
This is similar as the render
method is called just like the react component. Also the lifecycleScope hooks into its parents/the activity's lifecycle scope (and it gets killed in onDispose). You can call it like this:
setContent {
SomeButtonComponent.render(SomeButtonProps(this@MainActivity) {
viewModel.onLoggedIn()
})
}
I couldn't really find anything on how I could create 1 component and keep its instance, but it could have been that I was looking in the wrong place. In React you create a child and then put it in the graph. Therefore I tried to copy the kotlin react example and make it more compose-y. This will call in the same way a static function, which under the hood creates a child. If I am correct, the state {...}
is only called once (if it doesn't change of course) so creating the component there, do some injection etc. should be fine in the State initialization block. So this code here basically creates my composable component and then renders it and nudges it to destroy itself once it can be disposed of
abstract class BaseComponentRenderer<C : BaseComponent<P>, P : BaseProps> {
protected abstract fun provideComponent(props: P): C
@Composable
fun render(props: P) {
state {
provideComponent(props)
}.value.also {
it.render()
onDispose {
it.destroy()
}
}
}
}