# decouple


02/22/2023, 5:04 PM
Hi, everyone! I've been reading about Compose every since the first announcements, and I've been using Compose for Web (DOM) in production for the past 6 months or so. When I started using it, I was disappointed it was not possible to share UI code between Android and Web, but I understand it's not really possible as the ecosystems are too different from each other. My understanding is that JetBrains' goal for the next few years of Compose is to provide a multiplatform library based on Skia that has identical components for all platforms (as in, both in code, and visually pixel-perfect the same). I think there is an alternative approach to multiplatform UI, and Decouple is my exploration of it. Currently, it is very experimental, but at least proves that the idea actually works. What's left to do is to actually grow it into a proper component library that can be used in the real world. In a simplified manner, Decouple declares components in interfaces, allowing to abstract over multiple component implementations. Thanks to the power of composition and context receivers, this creates a new paradigm: writing the application UI in a common module, and having component implementations elsewhere. Essentially, this decouples (eh) the application UI from the components themselves, which are expected to live in another library. This provides many benefits: • Write a single UI and run it on multiple platforms that are completely different in implementation, as long as they run Compose: (Android, Compose for Web DOM, Compose for Desktop, Jewel for IDEA plugins, Mosaic for CLI applications…) • Start writing your application with one of the built-in themes and replace any component by anything else at any development stage without touching the application itself (e.g. your design team can work asynchronously without ever touching the application code) • Powerful headless UI tests, in which you only test your UI at a very high level (you can directly access each composable's arguments) Context receivers are not available for JS yet, so I've had to hack them with CompositionLocal that's not great, but the end goal is that each app provides a single interface that declares what components the project uses:
Copy code
interface MyComponents : UIMetadata, Buttons, Chips…
you can expect-actual (or just implement) this interface via delegation to use existing implementations, or just create your own:
Copy code
class MaterialComponents : MyComponents, Buttons by MaterialButtons, Chips by MaterialChips…
and then, you can typesafely declare components that work for any implementation you provided:
Copy code
fun UserList(users: List<User>) {
    Column {
        for (user in users) …
Because of the receiver, this allows to easily know if all components used are available for all implementations:
Copy code
fun UserList(…) {
    // Here, you can call only components that are declared to be implemented for CLI applications
Currently, we have a few dozen components implemented, but they prove this structure works. I'm very interested in having other people's opinions on where this can go.