I read <@UHAJKUSTU>'s article <Fully cross-platfo...
# compose-desktop
v
I read @Arkadii Ivanov's article Fully cross-platform Kotlin applications and liked the idea of wrapping Composable in components. This is probably more familiar to me... So, I want to ask how ideologically correct this approach is and what pitfalls await me on this path? For example, I converted the interface Component to an open class (for add logging of rendering and may be later other service information). And I make derived abstract class to make it easier to create child Windows:
Copy code
abstract class DialogComponent(
    componentContext: ComponentContext,
    name: String,
) : Component(componentContext, name) {
    private val _opened = mutableStateOf(false)
    protected val _title = mutableStateOf(name)

    fun open() { _opened.value = true }
    fun close() { _opened.value = false }

    @Composable
    override fun render() {
        if (!_opened.value) return
        super.render()
        Dialog(_title.value, onDismissEvent = ::close
        ) {
            content()
        }
    }

    @Composable
    abstract fun content()
}
Now I can create a derived components with the implementation of content:
Copy code
class LoginDialog(
    componentContext: ComponentContext,
    private val onLogged: (Operator) -> Unit
) : DialogComponent(componentContext, "Login") {
    val password = remember { mutableStateOf("") }
    // ...
    @Composable
    override fun content() {
        // ...
        Button(
            onClick = {
                if (controller.authorize(operator, password.value))
                    onLogged(operator)
                    password.value = ""
                    close()
                }
            }
        ) {
            Text("Login")
        }
    }
}
Then I can call window rendering in the root component:
Copy code
//...
    private val dialog = LoginDialog(componentContext,onAuthorize)

    @Composable
    override fun render() {
        // ...
        Button(onClick = dialog::open) { Text("Login") }
        dialog.render()
        // ...
    }
So, what do you think about this approach?
j
Certainly you can attach composables to classes, and you'll get something analogous to lambdas in the process (ie. a composable with captured data that you can pass around).  Sometimes it's useful, but usually it's people trying to shim Compose into their old mental models, and usually it doesn't end well. In the very early days of Compose (before first public release), we actually had full support for Composable classes.  We made a conscious decision to remove that functionality, for a variety of reasons that we might go into in a blog post at some point.  But at a high level, be aware that attempting to use this pattern will encourage code styles that end up harming long-term readability and maintainability by creating complexity in the wrong places.  Using functions helps break people of their imperative prior inclinations. I'd suggest using top-level Composable functions until you've fully mastered Compose, and then iterate on other architectures after you've had the aha moments that lead us to design Compose in the first place.
👍 4
👀 1
v
Thanks for the answer! Compose is very unusual for me, but I will try to get used to it :)
a
There are reasons of such an approach with classes, I tried to describe them in this article. TLDR: The most important reasons are: • You can attach any UI, not only Compose, so components can be really multiplatform. E.g. SwiftUI or React. • Components, when moved to back stack, are not destroyed. They continue working in background. Just like Fragments. • There is clear boundary between UI and non-UI stuff. • You can test everything, but UI, with pure multiplatform unit tests.
👀 1
But I agree, Composable functions are better to keep top-level. So it is clear what is being captured, and what is not.