B4A Library B4A - DeclarativeUI Library

Hi everyone,

After too many long power cuts, I apparently had the questionable idea of
writing a small declarative UI wrapper for B4A.

DeclarativeUI is an experiment inspired by Flutter and other modern UI
frameworks. It lets you describe a screen as a tree of widgets instead of
creating and positioning every native view manually.

For example::
Dim body As UIColumn
body.Initialize _
    .Spacing(12dip) _
    .AddChild(title) _
    .AddChild(description) _
    .AddChild(actionButton)

The library currently includes state binding, natural layout, themes,
navigation inside one Activity, dialogs, snackbars, animations and native
view interoperability.

It is not intended to replace B4A or B4X, and it is definitely not a complete
Flutter implementation. It is just a small beta project with rough edges,
created to explore whether this style of UI development feels useful in B4A.

Is it revolutionary? Probably not. Is it useful? Maybe.

I am sharing it mainly to get feedback. If you try it, I would be interested
in hearing what feels clear, what feels unnecessarily complicated and which
parts should be removed rather than expanded.

Purists may want to look away now.
 

Attachments

  • b4a-counter.zip
    7.7 KB · Views: 79
  • b4a-template.zip
    12.1 KB · Views: 74
  • DeclarativeUI.b4xlib
    88.2 KB · Views: 99
  • DeclarativeUI-ForumGuide.pdf
    58.3 KB · Views: 110

Mashiane

Expert
Licensed User
Longtime User
Hi

From someone who has been doing webdev in this forum forever and is now relearning everything about b4a I like this, a lot. I have always been fascinated by flutter and still believed that it can still be done with something like b4x without one having to learn dart, same with reactive native.

The State component is very interesting and I like the consistency you maintained when writing the components, the licensing is generous too. A personal thanks again for your contribution in this forum, Brilliant stuff. You are really here to change the world!

Q, Are you planning to make this fully cross platform?
Q, When can we see a fully working app? besides the two examples?
Q. Can you include more examples in the future, with screenshots please?

C. Just a heads up... something that I've also picked up when I was doing the B4XDaisyUIKit because I was so focused on UI creation with code too and are experiencing some layout issues here and there.

Building the layout programmatically -> using the designer when possible:
If you are only developing with B4A then building the layout programmatically is a mistake but not a huge one.
B4J and B4i handle screen resizes differently and it is much more difficult to handle the changes programmatically (there is video tutorial about it).
Most custom views can only be added with the designer (there are workarounds that allow adding them programmatically).
It is very simple to copy and paste designer layouts between different platforms and projects.

You will recall that there was a question previously about challenges experienced when creating long layouts or something.

That quote was sourced from...


All the best and keep up the good work you are doing!.
 

Mashiane

Expert
Licensed User
Longtime User
Counter example error... with b4a 13.50

B4X:
Unknown member: bottom
Unknown member: right
Unknown member: top
Unknown member: left
Unknown member: getcontentrect

B4X:
Error compiling program.
Error description: Unknown member: getcontentrect
Error occurred on line: 155
Dim contentRect As Rect = mIme.GetContentRect
Word: getcontentrect
 

Mashiane

Expert
Licensed User
Longtime User
So instead of getters and setters you use the "State" which does everything! Wow.

This seems like some kind of speed-demon framework.

Would be interesting to see benchmarks from normal b4x code, abstract designer and this. #Wishful thinking... ignore me on this, ha ha ha...

So the nova is multi-page app. pLease fix the error above otherwise I cant run.

I understand you are running the beta version of b4a with edege to edge. Not all of us are... ;)
 

Maxcfgos

Member
Licensed User
So instead of getters and setters you use the "State" which does everything! Wow.

This seems like some kind of speed-demon framework.

Would be interesting to see benchmarks from normal b4x code, abstract designer and this. #Wishful thinking... ignore me on this, ha ha ha...

So the nova is multi-page app. pLease fix the error above otherwise I cant run.

I understand you are running the beta version of b4a with edege to edge. Not all of us are... ;)
Thanks for such detailed feedback and the kind words!
  1. Please re-download the library
  2. Comment out this line: #EdgeToEdgeOldDevices: True
  3. You can ignore the warnings about the SDK and the logs — the IME library isn't needed for any B4A version anymore
  4. The examples should work fine now
Regarding the layout/measurements point: the library works similarly to Flutter, so since it's B4A-only it doesn't need manual measurements or the kind of cross-platform adjustments you mentioned.
  • I'm still not sure if I'll make it cross-platform
  • I'll be showing more complex/complete examples soon
 

Mashiane

Expert
Licensed User
Longtime User
My... the simplicity of your demos is amazing.

The fact that one is not thinking about Top, Left, Height, Width is so amazing. The Nova example pushed the bar.

So you didnt include images in your posts... I think you didnt do justice to your post. I would have, but then again its absolutely your choice.

It would be interesting to see this working with b4xpages.

The Nova Tasks is a simple app, large apps?

You have indeed brought flutter code styling to b4x.

1. The number in the first example needs padding, it clipped at the bottom.
2. The task descriptions could use multi-line if long, they wrap but clip.
3. Consider adding helpers e.g. VerticalAlignment, HorizontalAlignment.
4. Brilliant theming.
5. Redrawing the UI each time? e.g. theme changing... its not noticeable... might be expensive #just a thought.

Will be watching this attentively.
 

Mashiane

Expert
Licensed User
Longtime User
This is amazing... Did this just to get clarity on how this works... hope you dont mind. Im just still fascinated by this... 😍




 

Maxcfgos

Member
Licensed User
Library update v0.6

THE COUNTER IS INSANE
The canonical example (examples/b4a_declarative_counter) takes code reduction
to the extreme: the entire screen (app bar with theme action, bound label,
FAB, centered column) is ONE single expression, 13 lines:

example:
    Screen = UI.Scaffold(UI.Center(UI.Column(Null) _

        .Spacing(12dip).MainAxisAlignment("center").CrossAxisAlignment("stretch") _

        .AddChild(UI.Text("You have pushed the button this many times:") _

            .Size(AppTheme.BodyLarge).Color(AppTheme.SecondaryText)) _

        .AddChild(UI.Text("").BindText(CounterState) _

            .Size(52).Color(AppTheme.PrimaryText)))) _

        .AppBar(UI.AppBar("Declarative Counter") _

            .Action(UI.IconFA(Chr(IIf(AppTheme.IsDark, 0xF185, 0xF186))).Size(24) _

                .Color(AppTheme.DashboardBarText).OnClick(Me, "ToggleTheme_Click"))) _

        .FloatingActionButtonRight(UI.Fab("+") _

            .OnClick(Me, "Increment_Click")) _

        .ApplyTheme(AppTheme)

    UI.Show(Root, Screen)

No Dim + Initialize, no SetParent/SetPosition/SetSize by hand: UI.Show mounts
everything. The whole Activity (counter + theme + buttons) is 68 lines, most
of them project attributes.

A TRUE WIDGET TREE
The code is no longer a list of AddView calls: it is a real hierarchical
composition (Scaffold > Column > Text > bindings...). Every widget has a
lifecycle (SetParent/SetPosition/SetSize/Render/Detach/Unmount) and natural
measurement; containers (Column/Row/Stack) own the layout of their children.

New in 0.6:
- UIButton renders FontAwesome glyphs in its text (U+F000..U+F8FF) with the
correct typeface: UI.Button(Chr(0xF04B) & " Run") = play icon + label, no
extra API.
- Automatic safe area in UIScaffold (WindowInsets, zero per-screen code).
- Automatic re-layout of bound widgets (a label grows from "9" to "10"
without clipping).
- One-way bindings with precedence: the static setter cancels the binding.
- UIState/UIAsyncState, Material 3 themes, snackbars, dialogs, animations,
virtualized UIListView with pooling, virtual navigation inside one Activity.

No longer a demo

Feedback, bugs and ideas are welcome.
 

Maxcfgos

Member
Licensed User
Thing is, does it make the code readable and easy for beginners to follow for understanding & comprehension?
I completely understand your point. For someone coming from the imperative paradigm, the declarative approach can feel unnatural at first—even a bit cryptic. The initial learning curve is real: it takes time to get used to thinking about what you want to achieve rather than how to do it step by step.

However, once you get past that initial hurdle and start thinking in terms of data transformations, function composition, and expressions, the code becomes much cleaner, more predictable, and easier to reason about. What used to be loops and mutable state turns into clear statements of intent. And that's where the declarative paradigm truly shines: it reduces accidental complexity and makes code more maintainable and less error-prone.

In short: yes, it feels uncomfortable at first, but with a little practice, it becomes enormously simpler and more productive. It's like switching from a manual screwdriver to an electric one—it takes some getting used to at first, but once you do, you never want to go back.
 

Mashiane

Expert
Licensed User
Longtime User
User Manual - 💯, that answers my readability questions.

Checked the examples - love it!

UI.Text appears clipped at the bottom on my phone.

1787014839781.png


Brilliant Stuff!
 

Mashiane

Expert
Licensed User
Longtime User
The Nova Tasks is a simple app, large apps?
You are using a single activity for multiple pages, granted, this is how b4xpages does things anyway.

The code however to create the various screens in your example is in this same activity, being functions.

Q. Would you recomend a code module / class module where one wants to have separation of concerns?
Q. Is it possible for a kitchen sink app that will showcase all the components?
 

Mashiane

Expert
Licensed User
Longtime User
@Maxcfgos, you still working on this?

Will you make it available via github so that one can do pull/push requests?

Your library makes it very easy for anyone coming from kotlin / compose to use b4a with the same frameworks in mind.

I have not used those but read a little and after seeing the similaries with what you have done here, its worth the energy investment.

Please check the questions I left in the previous post also.
 

Mashiane

Expert
Licensed User
Longtime User
I'm working on those aspects, including integrating your libraries to make them compatible. Please review the 2 existing branches and feel free to make corrections and push directly—I will review them. I'm also working on making it cross-platform: https://github.com/maxcubecfgos/b4x_declarative
Awesome, heads up, the b4a wrappers for the native components are thin and not all native components have been wrapped. That has given rise to the many additional libraries in the forum by forum members, a working team effort anyway.

The good thing here is, you are not creating additional components per ser, but creating a new system of creating UIs and the rest of the world is using similar to flutter, compose etc.

Change Management 101.

If one is more towards writing code than creating abstract designer layouts, then this makes sense as it will cut coding time tremendously due to ease of use.
 

Jerryk

Active Member
Licensed User
Longtime User
Click counter in B4X: "imperative" vs "declarative" style
Below we have the same logic written in a typical imperative B4X way (as it is usually written) and then an example of what it would look like if we tried to write as close to declarative thinking as possible (state separated from UI, UI as a "view of state").

1) Typical imperative B4X code
This is the style you are probably familiar with: global variables, manual view initialization, you directly change Text, Visible, etc. in events.
B4X:
Sub Process_Globals
    Private count As Int = 0
End Sub

Sub Globals
    Private lblCount As Label
    Private btnClick As Button
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main") ' Main.bal contains Label lblCount and Button btnClick

' Initialization (if you are not using Designer script)
    lblCount.Initialize("lblCount")
    btnClick.Initialize("btnClick")
    
    ' Setting initial state
    lblCount.Text = count
End Sub

Sub btnClick_Click
    count = count + 1 ' change state
    lblCount.Text = count ' MANUAL UI update
End Sub
What is "imperative" here:
We have state (count) and UI (lblCount) as separate entities.
In btnClick_Click we explicitly say: "increment the counter and then set the label text".
When expanding (more labels, conditions, animations) we have to manually synchronize the UI with the state in multiple places.

2) B4X with more declarative thinking (state → render)
B4X does not have a full-fledged declarative UI framework like Compose, but we can organize the code so that the state is central and the UI is "rendered" from it. This brings us closer to a declarative style: the UI is a function of the state.
B4X:
Sub Process_Globals
    Private count As Int = 0
End Sub

Sub Globals
    Private lblCount As Label
    Private btnClick As Button
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main") ' Main.bal: lblCount, btnClick

    lblCount.Initialize("lblCount")
    btnClick.Initialize("btnClick")

    RenderUI() ' first rendering from state
End Sub

' --- The more declarative part: UI as a function of state ---
Sub RenderUI()
    lblCount.Text = count ' UI derived from state
    ' There would be more rules here:
    ' btnClick.Enabled = count < 10
    ' lblCount.Visible = count > 0
    ' etc.
End Sub

Sub btnClick_Click
    count = count + 1 ' only changing state
    RenderUI() ' "redrawing" UI from state
End Sub
What is more "declarative" here:
The state (count) is the only source of truth.
The UI is not changed directly anywhere else; all changes go through RenderUI, which describes how the UI should look for the current state.
We only add new rules (color, visibility, button text) in RenderUI, not in each event handler separately.

We can go even further and have one central method for the entire "screen":
B4X:
Sub RenderMainScreen()
    lblCount.Text = count
    lblCount.TextColor = IIf(count Mod 2 = 0, Colors.Black, Colors.Red)
    btnClick.Text = IIf(count = 0, "Start", "Click again")
    btnClick.Enabled = count < 100
End Sub
Then in the events we just change the state and call RenderMainScreen.
 
Top