JavaFX · CDI · MVC-B · ReactFX
ContextFX is a minimalist dependency injection and context management library for JavaFX applications, designed specifically for multiplatform environments (Web/WASM, Mobile, Desktop) with native ReactFX support.
Why ContextFX?
| Feature | ContextFX | SupernautFX | Gluon Ignite |
|---|---|---|---|
| External dependencies | None | Micronaut | Spring/CDI |
| ReactFX support | ✅ Native | ❌ No | ❌ No |
| Binary size | Minimal | High | Medium/High |
| GluonFX (WASM/Mobile) | ✅ Optimized | ⚠️ Limited | ⚠️ Configuration |
| Ease of use | ✅ Simple | Complex | Requires framework |
Usage modes
| Feature | Classic Mode | Reactive Mode |
|---|---|---|
| Requirements | GluonFX | GluonFX + ReactFX |
| Main class | ContextApplication |
ReactiveContextApplication |
| Manager | Manager |
ReactiveManager |
| Ideal use | Traditional applications | Reactive applications |
| GraalVM / GluonFX mobile | ✅ via ContextLoader | ✅ via ContextLoader |
Minimal examples
<!-- SampleView.fxml --> <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <BorderPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" fx:controller="com.myapp.views.SampleView"> <center> <VBox alignment="CENTER" spacing="5.0"> <Label fx:id="greet" /> <Button fx:id="redirect" /> </VBox> </center> </BorderPane>
// 1. Initialization public class MyApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) throws Exception { ContextApplication context = new ContextApplication(MyApp.class); stage.setTitle("MyApp"); stage.setHeight(600); stage.setWidth(800); Manager.getManager(context.getContainer().getComponents()).init(stage, MyApp.class); } }
// 1. ContextLoader public class MyContextLoader extends ContextLoader { public MyContextLoader() { super(List.of( GreetService.class, SampleController.class, SampleView.class )); } } // 2. Initialization public class MyApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) throws Exception { ContextApplication context = new ContextApplication(new MyContextLoader()); stage.setTitle("MyApp"); stage.setHeight(600); stage.setWidth(800); Manager.getManager(context.getContainer().getComponents()).init(stage, MyApp.class); } }
// Service — business logic @Service public class GreetService { public String greet() { return "Hello World from JavaFX with ContextFX"; } } // Controller — coordination @Controller public class SampleController { @Injectable private GreetService service; public String greet() { return service.greet(); } public void redirect() { Manager.getManager().switchView("OtherView"); } } // View — UI binding @View(main = true) public class SampleView implements Initializable { @FXML private Label greet; @FXML private Button redirect; private SampleController controller; public SampleView() { controller = (SampleController) Container.getComponent(SampleController.class); } @Override public void initialize(URL url, ResourceBundle rb) { greet.setText(controller.greet()); redirect.setOnAction(e -> controller.redirect()); } }
<!-- CounterView.fxml --> <?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.layout.BorderPane?> <?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.Button?> <BorderPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" fx:controller="com.myapp.views.CounterView"> <center> <VBox alignment="CENTER" spacing="5.0"> <Label fx:id="message" /> <HBox alignment="CENTER" spacing="5.0"> <Button fx:id="count" /> <Button fx:id="clean" /> </HBox> </VBox> </center> </BorderPane>
// 1. Initialization public class MyApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) throws Exception { ReactiveContextApplication context = new ReactiveContextApplication(MyApp.class); stage.setTitle("MyApp"); stage.setHeight(600); stage.setWidth(800); ReactiveManager.getReactiveManager(context.getReactiveContainer().getComponents()).init(stage, MyApp.class); } }
// 1. ContextLoader public class MyContextLoader extends ContextLoader { public MyContextLoader() { super(List.of( CounterModel.class, CounterService.class, CounterController.class, CounterView.class )); } } // 2. Initialization public class MyApp extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) throws Exception { ReactiveContextApplication context = new ReactiveContextApplication(new MyContextLoader()); stage.setTitle("MyApp"); stage.setHeight(600); stage.setWidth(800); ReactiveManager.getReactiveManager(context.getReactiveContainer().getComponents()).init(stage, MyApp.class); } }
// Model — reactive state @ReactiveModel public class CounterModel { private Var<Integer> count; public CounterModel() { count = Var.newSimpleVar(0); } public void setCount(Integer count) { this.count.setValue(count); } public Val<Integer> getCount() { return count; } } // Service — business logic @ReactiveService public class CounterService { @ReactiveInjectable private CounterModel model; public void count() { model.setCount(model.getCount().getValue() + 1); } public void clean() { model.setCount(0); } } // Controller — coordination @ReactiveController public class CounterController { @ReactiveInjectable private CounterService service; public void count() { service.count(); } public void clean() { service.clean(); } } // View — UI binding @ReactiveView(main = true) public class CounterView implements Initializable { @FXML private Label message; @FXML private Button count; @FXML private Button clean; private CounterController controller; private CounterModel model; public CounterView() { model = (CounterModel) ReactiveContainer.getComponent(CounterModel.class); controller = (CounterController) ReactiveContainer.getComponent(CounterController.class); } @Override public void initialize(URL url, ResourceBundle rb) { message.setText("You've clicked 0 times"); count.setText("Count"); clean.setText("Clean"); model.getCount().changes() .subscribe(e -> message.setText("You've clicked " + e.getNewValue() + " times")); EventStreams.eventsOf(count, ActionEvent.ACTION) .subscribe(event -> controller.count()); EventStreams.eventsOf(clean, ActionEvent.ACTION) .subscribe(event -> controller.clean()); } }
Documentation
API Reference (JavaDoc)
Full class and method documentation, always pointing to the latest published version.
View latest →Quickstart
Get a ContextFX project running from scratch.
Classic Mode
ContextApplication and
@Controller/@Injectable.
Reactive Mode
ReactiveContextApplication and native ReactFX injection.
MVC-B (MVC Binding)
How the View binds the UI to the rest of the layers.
Known Limitations
CDI is not available inside the View, and the workaround.
Installation
<dependency> <groupId>page.codeberg.rrangelo</groupId> <artifactId>contextfx</artifactId> <version>1.0.0</version> </dependency>
Support the project
If you find ContextFX useful, consider supporting its development.