Implement your own Redux in Java. Part 2: Simple FXML Views

javaredux

In this series of blog posts I'm implementing my own Redux in Java to use with JavaFX.

However, the purpose is not to create a production-ready library (because there is already ReduxFX available) but instead to learn by re-implementing it.

In the previous part I've showed how the basic Redux functionality can be implemented. In this part I will show how to use it with JavaFX and how to combine a simple FXML based JavaFX View with our Redux store.

Topics in this blog series (maybe I will adjust this in the process of writing):

The UI

A typical JavaFX UI consists of a FXML file and a controller class. This can look like this:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>


<VBox fx:controller="example.TodoViewController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity"
      minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1">
    <HBox spacing="5.0">
        <padding>
            <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
        </padding>
        <TextField fx:id="input" HBox.hgrow="ALWAYS"/>
        <Button mnemonicParsing="false" text="Add" onAction="#addItem"/>
    </HBox>
    <ListView fx:id="items" VBox.vgrow="ALWAYS"/>
</VBox>
package example;

import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

public class TodoViewController {
	@FXML
	private ListView items;
	@FXML
	private TextField input;

	public void addItem() {
		// todo implement
	}
}

Now we need to solve two problems: 1) we need to create and dispatch a new action when the add-button is clicked and 2) we need to get the data from the store and update our UI when the data has changed. The easiest way to solve this is to give the controller access to our Redux store. This is a typical use-case for dependency-injection and for this example I'm using my own dependency-injection library Easy-DI. This way we can inject an instance of the Redux store into the controller. The setup code in the main app class looks like this:

public void start(Stage primaryStage) throws Exception {
	// create dependency injection context
	EasyDI context = new EasyDI();

	// create the initial state with an empty item list
	TodoState initialState = new TodoState(Collections.emptyList());

	// create the store by passing the initial state and the reducer to the constructor
	Store<TodoState> store = new Store<>(initialState, new TodoReducer());

	// configure the DI context so that the store instance can be injected
	context.bindInstance(Store.class, store);

	// the location of the fxml file to load
	final URL location = this.getClass().getResource("TodoView.fxml").toURI().toURL();
	FXMLLoader fxmlLoader = new FXMLLoader(location);

	// tell JavaFX how to get new instances from the DI context
	fxmlLoader.setControllerFactory(context::getInstance);

	final VBox parent = fxmlLoader.load();

	primaryStage.setScene(new Scene(parent));
	primaryStage.show();
}

EasyDI only supports constructor-injection but if you are using other DI frameworks maybe you can also use field injection. Our controller now looks like this:

public class TodoViewController {
	@FXML
	private ListView items;
	@FXML
	private TextField input;

	private final Store<TodoState> store;

	public TodoViewController(Store<TodoState> store) {
		this.store = store;
	}

	public void addItem() {
    // todo implement
	}
}

Dispatching actions

Now that we have access to the store it's easy to dispatch new actions with the dispatch method:

public void addItem() {
	final String text = input.getText();

	Action action = new AddItemAction(text);
	store.dispatch(action);

	input.setText("");
}

Update the UI on state changes

With the subscribe method we have a way to get notified about state changes.

public TodoViewController(Store<TodoState> store) {
	this.store = store;

	store.subscribe(newState -> {
		items.getItems().setAll(newState.getItems());
	});
}

For this small example updating the UI is easy. We throw away the existing items in the JavaFX ListView and overwrite them with the new ones. However, for more complex use-cases and UIs it can become really tricky. This style of development can be a problem because JavaFX itself is not optimized for this kind of usage. The reason for this is that JavaFX has an imperative API - at least when you compare it to the fully declarative API of React.js for which the original Redux was invented. In a future post I will go into more detail what I mean with "fully declarative API" and why in my opinion JavaFX is not fully declarative. And I will also show an alternative declarative and functional API for JavaFX that is more similar to react.js and is a good fit for a Redux based app.

However, even with the "normal" FXML based approach you can create good UI's with Redux. Last year I have written some useful helpers to support the combination of FXML and Redux as a contribution to the ReduxFX project. In one of the next blog posts I will give a more detailed explanation of these tools and show some patterns for Redux+FXML.


You can find the code for redux example in this github repository. This commit is pointing directly to the state of this blog post.