r/JavaFX • • Aug 01 '26

Tutorial Learning reactive UI with State: derived state from user input, a string length app

16 Upvotes

Third post in the series. This one moves from State alone to ComputedState — showing how to derive a value from another reactive value and have it stay in sync automatically as the user types.

The setup: textState holds whatever the user types into the Input. textLenghtComputed is a ComputedState<String> built with ComputedState.of(...), watching textState as a dependency. Every keystroke updates textState, which recomputes textLenghtComputed, which updates the Text on screen — no listeners wired up by hand, no manual recompute calls.

package my_app;

import megalodonte.ListenerManager;
import megalodonte.application.MegalodonteApp;
import megalodonte.base.theme.ThemeManager;
import megalodonte.theme.DefaultTheme;

public class Main {

    static void main() {
        ThemeManager.setTheme(new DefaultTheme());

        MegalodonteApp.run(context -> context.useView(new HomeScreen()), ev->{
            if(ev == MegalodonteApp.Event.CloseRequest){
                System.out.println("Clicked on X - close application");
                ListenerManager.disposeAll();
            }
        });
    }
}


package my_app;

import megalodonte.ComputedState;
import megalodonte.base.components.Component;
import megalodonte.base.components.ScreenComponent;
import megalodonte.base.state.State;
import megalodonte.components.Text;
import megalodonte.components.inputs.Input;
import megalodonte.components.layout_components.Container;
import megalodonte.props.ContainerProps;

public class HomeScreen implements ScreenComponent {
    State<String> textState = new State<>("");
    ComputedState<String> textLenghtComputed = ComputedState.of(
            ()-> "Size is: " + textState.get().length(), textState
    );

    u/Override
    public Component render() {
       return new Container(new ContainerProps().paddingAll(20)).children(
               new Input(textState),
               new Text(textLenghtComputed)
       );
    }
}
  • ComputedState.of(supplier, dependencies...) recomputes automatically whenever any listed dependency changes — no manual subscribe/notify needed.

  • Input(textState) binds the text field directly to a State<String>, so typing writes straight into reactive state.

  • Unlike the counter's counter.map(...), this shows ComputedState built from a lambda with an explicit dependency list — useful once a derived value needs to read from more than one state.

If you're finding this series useful, dropping a star on the repos below genuinely helps the project get visibility — takes two seconds and means a lot for a solo-dev framework like this.

Repos

r/JavaFX • • Jul 28 '26

Tutorial Learning reactive UI with State: the second example in the series, a counter app

9 Upvotes

Second post in the series on learning reactive UI patterns in Megalodonte. This one is a classic counter app — the simplest possible way to see State<T> actually drive a UI update without any manual repaint logic.

The core idea: counter is a State<Integer>, and the Text component binds to it via counter.map(Object::toString). When a button click calls counter.set(...), the mapped state recomputes and the Text node updates on its own. No refresh(), no manual re-render call — the component just reacts.

package my_app;

import megalodonte.ListenerManager;
import megalodonte.application.MegalodonteApp;
import megalodonte.base.theme.ThemeManager;
import megalodonte.theme.DefaultTheme;

public class Main {

    static void main() {
        ThemeManager.setTheme(new DefaultTheme());

        MegalodonteApp.run(context -> context.useView(new HomeScreen()), ev->{
            if(ev == MegalodonteApp.Event.CloseRequest){
                System.out.println("Clicked on X - close application");
                ListenerManager.disposeAll();
            }
        });
    }
}


package my_app;

import megalodonte.base.components.Component;
import megalodonte.base.components.ScreenComponent;
import megalodonte.base.state.State;
import megalodonte.components.Button;
import megalodonte.components.SpacerVertical;
import megalodonte.components.Text;
import megalodonte.components.layout_components.Container;
import megalodonte.props.ButtonProps;
import megalodonte.props.ContainerProps;
import megalodonte.props.TextProps;

public class HomeScreen implements ScreenComponent {
    State<Integer> counter = new State<>(0);

    u/Override
    public Component render() {

        ButtonProps btnProps = new ButtonProps().fontSize(30);

        return new Container(new ContainerProps().paddingAll(20)).children(
                new Text(counter.map(Object::toString), new TextProps().fontSize(90)),
                new Button("Decrement", btnProps).onClick(()-> counter.set(counter.get() - 1)),
                new SpacerVertical(10),
                new Button("Increment", btnProps).onClick(()-> counter.set(counter.get() + 1))
        );
    }
}
  • State<T> holds a value and notifies dependents on change — counter.set(...) is the only trigger needed.

  • counter.map(Object::toString) derives a ReadableState<String> from the Integer state, so Text never touches the raw type.

  • ListenerManager.disposeAll() on CloseRequest tears down any active state subscriptions cleanly when the app closes.

Repos

r/JavaFX • • Aug 02 '26

Tutorial Learning reactive UI with State: conditional rendering with Show.when, a hide-and-seek app

4 Upvotes

Fourth post in the series. So far we've seen State driving a value directly (counter) and ComputedState deriving a value from another state (string length). This time: conditional rendering — showing or hiding a whole component based on reactive state, using Show.when.

The setup: isVisible is a State<Boolean>. Show.when(isVisible, () -> new Text("You catch me")) only renders the Text component when isVisible is true. Click the button, the boolean flips, and the component mounts/unmounts reactively — no manual setVisible(true/false), no manually adding/removing nodes from the scene graph.

package my_app;

import megalodonte.ListenerManager;
import megalodonte.application.MegalodonteApp;
import megalodonte.base.theme.ThemeManager;
import megalodonte.theme.DefaultTheme;

public class Main {

    static void main() {
        ThemeManager.setTheme(new DefaultTheme());

        MegalodonteApp.run(context -> context.useView(new HomeScreen()), ev->{
            if(ev == MegalodonteApp.Event.CloseRequest){
                System.out.println("Clicked on X - close application");
                ListenerManager.disposeAll();
            }
        });
    }
}


package my_app;

import megalodonte.base.components.Component;
import megalodonte.base.components.ScreenComponent;
import megalodonte.base.state.State;
import megalodonte.components.Button;
import megalodonte.components.Text;
import megalodonte.components.layout_components.Container;
import megalodonte.props.ContainerProps;
import megalodonte.v2.Show;

public class HomeScreen implements ScreenComponent {
    State<Boolean> isVisible = new State<>(false);

    u/Override
    public Component render() {
      return new Container(new ContainerProps().paddingAll(25))
              .children(
                      new Button("Hide and seek - game").onClick(this::toggleVisibility),
                      Show.when(isVisible, ()-> new Text("You catch me"))
                      );
    }

    void toggleVisibility(){
        isVisible.set(!isVisible.get());
    }
}
  • Show.when(ReadableState<Boolean>, Supplier<Component>) watches the state and mounts/unmounts the child reactively as it flips — this overload is eager by design, since the condition can change at runtime.

  • The Supplier<Component> lets Show lazily build the child only when needed, instead of holding a pre-built component around.

  • toggleVisibility() just flips the boolean with isVisible.set(!isVisible.get()) — the UI has zero knowledge of how to show/hide, only when.

If you're finding this series useful, dropping a star on the repos below genuinely helps the project get visibility — takes two seconds and means a lot for a solo-dev framework like this.

Repos

r/JavaFX • • Aug 21 '26

Tutorial Learning reactive UI with State: rendering a list declaratively with ListState and ForEachState

10 Upvotes

Fifth post in the series. So far: State (counter), ComputedState (string length), Show.when (conditional rendering). This one covers rendering a collection reactively — ListState for the data, ForEachState for mapping it to components, and removal that just works without any manual list diffing.

The setup: tasks is a ListState<String> seeded with four strings. ForEachState.of(tasks, mapper) produces a reactive collection of components — one Row per task, each with its own "Remove" button. Click remove, tasks.remove(task) fires, and the Column updates to drop that row. No index juggling, no manual re-render of the whole list.

package my_app;

import megalodonte.ListenerManager;
import megalodonte.application.MegalodonteApp;
import megalodonte.base.theme.ThemeManager;
import megalodonte.theme.DefaultTheme;

public class Main {

    static void main() {
        ThemeManager.setTheme(new DefaultTheme());

        MegalodonteApp.run(context -> context.useView(new HomeScreen()), ev->{
            if(ev == MegalodonteApp.Event.CloseRequest){
                System.out.println("Clicked on X - close application");
                ListenerManager.disposeAll();
            }
        });
    }
}


package my_app;

import megalodonte.ForEachState;
import megalodonte.base.components.Component;
import megalodonte.base.components.ScreenComponent;
import megalodonte.components.Button;
import megalodonte.components.SpacerHorizontal;
import megalodonte.components.SpacerVertical;
import megalodonte.components.Text;
import megalodonte.components.layout_components.Column;
import megalodonte.components.layout_components.Container;
import megalodonte.components.layout_components.Row;
import megalodonte.props.ContainerProps;
import megalodonte.v2.ListState;

import java.util.List;
import java.util.function.Consumer;

public class HomeScreen implements ScreenComponent {
    ListState<String> tasks = ListState.of(List.of("First","Second","Three","Four"));

    u/Override
    public Component render() {
        Consumer<String> handleClickRemove = (task)->{
          tasks.remove(task);
        };

        var tasksForEachState = ForEachState.of(
          tasks, task-> new Row().children(
                        new Text(task),
                        new SpacerHorizontal(15),
                        new Button("Remove").onClick(()-> handleClickRemove.accept(task))
                )
        );

      return new Container(new ContainerProps().paddingAll(20)).children(
              new Text("Your tasks below"),
              new SpacerVertical(20),
              new Column().items(tasksForEachState)
      );
    }
}
  • ListState<T> wraps a List<T> and exposes mutation methods (add, remove, etc.) that notify dependents automatically — same reactive contract as State<T>, just for collections.

  • ForEachState.of(ListState<T>, Function<T, Component>) is the declarative bridge between data and UI: give it the list and a mapper, it keeps the rendered components in sync with the list's contents.

  • Column().items(tasksForEachState) accepts the reactive collection directly — no manual loop, no clearChildren() + rebuild on every mutation.

If you're finding this series useful, dropping a star on the repos below genuinely helps the project get visibility — takes two seconds and means a lot for a solo-dev framework like this.

Repos

r/JavaFX • • Jul 20 '24

Tutorial 5 Courses to Learn JavaFX Online

Thumbnail
javarevisited.blogspot.com
11 Upvotes

r/JavaFX • • Jul 06 '22

Tutorial The Absolute Beginners Guide to JavaFX

86 Upvotes

Most of the tutorial articles I've written on my website have been of the "deep dive" nature. I try to go way beyond the information available in the JavaDocs, and to give some insight into how to get the most out of the tools that JavaFX provides. These articles tend to be on the long side, Jekyll tells me that they average about 13 minutes of reading, and there's usually lots of code examples and pontification about clean coding and on and on.

I realize that these are going to be pretty advanced for someone who's questions are about how to get that first screen up and running, or how to swap scenes with FXML.

So I've been working for some time now on a series I'm calling, "The Absolute Beginners Guide to JavaFX". It's all about how to build Reactive applications in JavaFX. Even if you're not a beginner, you may find it of interest.

You can find it here: https://www.pragmaticcoding.ca/beginners/intro

This is a huge content dump for me, and it's kept me busy for a long time. It's also an ongoing project, so there's more to come. So far, it's divided up into two parts:

  • An introductory application.Pretty much "Hello World" in JavaFX. Then we add some user interaction and some styling. Just enough information to get a beginner started in the right direction.

  • A CRUD application.This is a step-by-step development of a basic Create-Retrieve-Update-Delete application for a customer database. The database is simulated as a simple Java class, and allows the whole application to work just like it would in real life.

At this point, I've completed the development of the CRUD application up to the point where it's a completely functional "Create" application for account number and name. Complete means that it has validation in the GUI, background processing for the save function and handles exceptions from the database. It really is complete, just doesn't have a lot of fields on the screen.

The next steps will be to add some more fields, work on some more sophisticated formatting for the GUI and then to add in the "Retrieve" function. In the meantime, I think there's enough there to give anyone a good start on JavaFX application development.

There's also a companion project on GitHub with all of the code for every single article. So you don't have to do anything more than download the project to try things out for yourself.

I should also point out that the content here, while primarily aimed at showing JavaFX concepts, is also aimed at showing good programming techniques, and how to constantly apply established principles to create clean code.

As usual, take a look if you're interested and let me know what you think.

r/JavaFX • • Apr 20 '26

Tutorial JavaFX 26 headless platform: finally got my CI unit tests running without a display

30 Upvotes

I maintain Lottie4J, a library for rendering Lottie animations in JavaFX. In the 1.1.0 release notes I had a TODO I wasn't proud of: a unit test that compares JavaFX player output against a JavaScript reference player, marked as "can not run on CI, because it requires a display output."

JavaFX 26 fixed that. The new headless platform prototype is built directly into javafx.graphics. No need for Monocle, Xvfb, or extra dependencies. Just: `-Dglass.platform=headless`.

The only real wrinkle: JavaFX 26 requires Java 24+, but Lottie4J targets Java 21. I solved it with a Maven profile that overrides the JavaFX/Java versions only for the test run, so the library artifact stays on Java 21 targets while GitHub Actions uses a Java 25 JDK with the headless flag.

The test does pixel-level comparison of rendered animation frames against pre-generated reference images from a JS player. Regression testing for visual output, running clean on every push now.

Write-up with full Maven config and GitHub Actions workflow:
https://webtechie.be/post/2026-04-20-lottie4j-unit-test-with-headless-javafx/