I'm sure many will disagree, but I have doubts that pure declarative-reactive is the "right" shape for an all-purpose native UI framework. In my experience, Kotlin+Compose shares many of the same warts… its main redeeming quality is that it's better than Android Framework (most of the time), which is low bar to clear.
These frameworks have a number of good ideas but they don't necessarily combine in a way that transcends high quality traditional imperative frameworks with declarative-reactive bits sprinkled throughout, at least for more complex apps. SwiftUI and its ilk work best for super simple tabs-and-flat-lists sorts of apps.
To make complex layouts (flex, ...) and have them run performantly you need some form of a retained backing state anyways.
The main difference between various retained, OOP, functional, immediate, declarative, ..., approaches is how they treat this state.
For imperative/retained/OOP libraries, you operate on this state itself. Your nodes/widgets know their own state, how to render themselves, their place in the hierarchy and so on.
For immediate/functional(?) libraries, this state is a cache. It's not something you work with directly.
Despite not really liking React itself, I think it has found the best model.
You take a retained core, possibly OOP, maybe ECS or whatever, and you write a declarative wrapper around it. This lets you escape the easy-mode declarative landscape when needed, but most UI can still be simple to write.
Do you have an example of where "the ideas don't combine well"? Or where an imperative approach is truly better?
In my experience the solution is often to use the appropriate architecture underneath your UI layer. Basically you build a data structure that represents your UI and always hand this entire structure to the declarative UI layer. Underneath the UI layer, you can still use imperative code to manipulate the data structures. The benefit of the reactive/declarative approach is that you don't have to think about how changes in the data need to be reflected in the UI. That's the framework's job.
Note: With "data structure" I don't mean "build a shadow DOM". I mean something specialized to your use case.
I agree, the first section "Data Flow" reflects my concern with Compose as well:
> Data flow in SwiftUI is indeed reactive—but sometimes I wish it wasn’t, because it’s reactive in all the wrong ways. It often reacts to changes it should ignore, and it ignores the changes you actually care about. To me, SwiftUI’s reactivity is a black box: It makes achieving predictable behavior almost impossible.
In Compose, seeing rememberXXX(key1, key2) sprinkled all over with keys makes following the code difficult.
Then add some side effects like LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { ... read permissions and render warning if we can't do something ... } and it becomes even more difficult to comprehend. Much of the Android framework APIs are not reactive, and polling on these UI events is still necessary (permission could have changed if the user put the app in the background, and you can't listen to them via Flow etc).
As the author says, these frameworks make the static data dependencies explicit but often at the expense of what is changing and why.
When I see rememberXXX(key1, key2) { } the first thing that comes to mind is, why would key1 and key2 change, and often that is not obvious.
I think open source is the right move here. The great thing about Flutter is you get the declarative UI to build a lot of standard screens easily but you can always drop down to lower levels, inspect the standard components, mix and match with your custom implementation. You can mix UKit and SwiftUI but there’s way more friction due to the closed nature of the SDK.