All Projects → manuel-mauky → FluxFX

manuel-mauky / FluxFX

Licence: Apache-2.0 License
Flux architecture with JavaFX

Programming Languages

java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to FluxFX

DashboardFx
JavaFx Dashboard
Stars: ✭ 272 (+1033.33%)
Mutual labels:  javafx, javafx-library
JasperViewerFX
The JasperViewerFX is a free JavaFX library which aims to avoid use of JasperReport's swing viewer
Stars: ✭ 27 (+12.5%)
Mutual labels:  javafx, javafx-library
Grid
A grid component for javafx
Stars: ✭ 23 (-4.17%)
Mutual labels:  javafx, javafx-library
medusa
A JavaFX library for Gauges
Stars: ✭ 605 (+2420.83%)
Mutual labels:  javafx, javafx-library
advanced-bindings
Collection of Binding helpers for JavaFX(8)
Stars: ✭ 63 (+162.5%)
Mutual labels:  javafx, javafx-library
MaskedTextField
MaskedTextField is an component similar to JFormmatedText field and can be used in same way.
Stars: ✭ 21 (-12.5%)
Mutual labels:  javafx, javafx-library
lib-preferences
Lib-Preferences is a library for easy storing simple data to a Preferences.properties file in a Java(FX) & Maven desktop application.
Stars: ✭ 12 (-50%)
Mutual labels:  javafx, javafx-library
Awesomejavafx
A curated list of awesome JavaFX libraries, books, frameworks, etc...
Stars: ✭ 2,488 (+10266.67%)
Mutual labels:  javafx, javafx-library
Bank-Account-Simulation
A Bank Account Simulation with JavaFX and SQLite back-end. Material UX|UI.
Stars: ✭ 19 (-20.83%)
Mutual labels:  javafx, javafx-library
animated
🌊 Implicit animations for JavaFX.
Stars: ✭ 79 (+229.17%)
Mutual labels:  javafx, javafx-library
RxReduxK
Micro-framework for Redux implemented in Kotlin
Stars: ✭ 65 (+170.83%)
Mutual labels:  flux
FlexBoxFX
FlexBoxFX is a JavaFX implementation of CSS3 flexbox.
Stars: ✭ 65 (+170.83%)
Mutual labels:  javafx
k3s-gitops
My home Kubernetes (k3s) cluster managed by GitOps (Flux)
Stars: ✭ 26 (+8.33%)
Mutual labels:  flux
FxEditor
JavaFX rich text editor able to handle billions of lines (WORK IN PROGRESS)
Stars: ✭ 21 (-12.5%)
Mutual labels:  javafx
k3s-gitops
GitOps principles to define kubernetes cluster state via code
Stars: ✭ 103 (+329.17%)
Mutual labels:  flux
FlashCards
Learning Blazor By Creating A Flash Cards Application
Stars: ✭ 17 (-29.17%)
Mutual labels:  flux
ballade
For unidirectional data flow.
Stars: ✭ 44 (+83.33%)
Mutual labels:  flux
flux-kustomize-example
Flux v1: Example of Flux using manifest generation with Kustomize
Stars: ✭ 71 (+195.83%)
Mutual labels:  flux
ChatRoom-JavaFX
This is a client/server chatroom(client for JavaFX and server for Workerman), supporting personal and global chat.
Stars: ✭ 53 (+120.83%)
Mutual labels:  javafx
GoBang
JAVA五子棋
Stars: ✭ 13 (-45.83%)
Mutual labels:  javafx

FluxFX - Flux architecture with JavaFX

This is an experimental implementation of the Flux Architecture with JavaFX. "Experimental" means that the API is likely to be changed in the future and it's not production ready (yet) in terms of tests, documentation and feature set. If you are looking for a production ready application framework for JavaFX have look at mvvmFX.

Build Status

Flux architecture

The flux architecture is an alternative to Model-View-Controller build by Facebook for their JavaScript library React.

The key concept of flux is unidirectional data flow:

The application state is located in so called Stores. Views are displaying the data of a the stores but they can't change the state pf the stores. Instead the View has to publish Actions. These actions are managed by a Dispatcher that dispatches all actions to all stores. Each store can decide if it likes to handle an incoming action and can change it's internal state accordingly. After a state change the store will emit a "change" event to the views so that the view can update itself.

FluxFX

FluxFX follows this idea with some small exceptions to make it more suitable for the usage with JavaFX:

  • Stores can "subscribe" to specific action types. Other actions aren't dispatched to the store.
  • There is no explicit mechanism for "change events" from the Stores to trigger the re-rendering of views. Instead it's recommended to use the Properties and unidirectional Data-Binding provided by JavaFX. Another great tool for this purpose are the EventStream/EventSource classes from ReactFX.

Internally FluxFX uses the great library ReactFX which is the only dependency.

To use FluxFX in your own project you can us the following config:

Gradle:

dependencies {
    compile 'eu.lestard:fluxfx:0.1'
}

Maven:

<dependency>
    <groupId>eu.lestard</groupId>
    <artifactId>fluxfx</artifactId>
    <version>0.1</version>
</dependency>

Another example of the flux architecture with JavaFX (but without fluxfx) can be seen here.

Tutorial

In this short tutorial we are creating a simple notes app.

Create a basic Store

We start with the store class that will hold our notes data. In our case we will use a simple list of strings:

package notesapp;

import eu.lestard.fluxfx.Store;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

public class NotesStore extends Store {

    private ObservableList<String> notes = FXCollections.observableArrayList();

    public ObservableList<String> getNotes() {
        return FXCollections.unmodifiableObservableList(notes);
    }
}

The store class extends from eu.lestard.fluxfx.Store. Internally we use an observable array list. But the public getter for the list returns an unmodifiable wrapper of our list. This way we can be sure that the state can only be modified by the Store itself and not from other classes.

Create a FXML file for the View (with SceneBuilder)
<?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 maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="5.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1"
    fx:controller="notesapp.NotesView">
   <children>
      <HBox spacing="5.0">
         <children>
            <TextField fx:id="input" HBox.hgrow="ALWAYS" />
            <Button defaultButton="true" mnemonicParsing="false" onAction="#add" text="Add" />
         </children>
      </HBox>
      <ListView fx:id="list" />
   </children>
   <padding>
      <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
   </padding>
</VBox>

This FXML file produces this GUI:

NotesApp

Note that we have set the attribute fx:controller to notesapp.NotesView. We will create this class in the next step.

Create a View class

The view class acts as a connector between the Java logic and the FXML file. We can inject references to the UI elements declared in the FXML file and fill them with data.

In the first step the view class looks like this:

package notesapp;

import eu.lestard.fluxfx.View;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;

public class NotesView implements View {

    @FXML
    private ListView<String> list;

    @FXML
    private TextField input;

    private NotesStore store = new NotesStore();

    public void initialize() {
        list.setItems(store.getNotes());
    }

    public void add() {
        // todo
    }
}

The intersting part is the initialize method. This method will be called automatically by JavaFX when the FXML file is loaded. With the statement list.setItems(store.getNotes()); the ListView will show the notes from the Store. As the notes list is observable the ListView will update itself automatically when the state in the store is changed.

Another thing to notice is that we implement the eu.lestard.fluxfx.View interface. In the next steps we will see why.

Define an Action to add notes

With flux all changes to the applications state are triggered by actions. In our case we like to add new notes so we create a class AddNoteAction. An action implements the eu.lestard.fluxfx.Action interface:

package notesapp;

import eu.lestard.fluxfx.Action;

public class AddNoteAction implements Action {

    private final String noteText;

    public AddNoteAction(String noteText) {
        this.noteText = noteText;
    }

    public String getNoteText() {
        return noteText;
    }
}

Actions can contain data but they should be immutable (i.e. data can't be changed after creation).

Trigger action in the View

We can now implement the add method in our view class. When the user clicks the add button we will take the text from the input field and publish a new AddNoteAction.

...
public class NotesView implements View {

    ...

    public void add() {
        publishAction(new AddNoteAction(input.getText()));
    }
}

The View interface has a method publishAction that we are using to bring our action into the system.

Process the action in the store

In our store we can subscribe to our action and perform the needed changes to the applications state.

public class NotesStore extends Store {
    ...
    public NotesStore() {
        subscribe(AddNoteAction.class, new Consumer<AddNoteAction>() {
            @Override
            public void accept(AddNoteAction addNoteAction) {
                final String noteText = addNoteAction.getNoteText();

                if(noteText != null && !noteText.trim().isEmpty()) {
                    notes.add(noteText);
                }
            }
        });
    }
    ...
}

In the constructor we use the subscribe method from the Store class. The first param is the class type of the action we are interested in. The second param is a Consumer function that is processing the incoming action.

With Java8 of cause we can use a lambda instead of the anonymous inner class for the consumer. Even better is to create a method in the store and use a method reference in the subscribe statement. The method should be private or package scope (no access modifier) to prevent direct invocation from outside of the store:

public NotesStore() {
    subscribe(AddNoteAction.class, this::processAddNoteAction);
}

private void processAddNoteAction(AddNoteAction action) {
    final String noteText = action.getNoteText();

    if(noteText != null && !noteText.trim().isEmpty()) {
        notes.add(noteText);
    }
}
Create an application class and load the View

The last step is to create an application class with a main method and load the FXML file. If your FXML file has the same name as the View class (except for the file ending of cause) and is contained in the same package, you can use the ViewLoader class from FluxFX to load it like this:

package notesapp;

import eu.lestard.fluxfx.ViewLoader;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class App extends Application {

    public static void main(String... args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        final Parent root = ViewLoader.load(NotesView.class);

        stage.setScene(new Scene(root));
        stage.show();
    }
}
Testing

Testing code is crucial for real applications. With flux architecture most logic is located in the store so let's write a unit test for our store. If you are a fan of test-driven-design you could write the test even before implementing the store.

import eu.lestard.fluxfx.Dispatcher;
import org.junit.Before;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class NotesStoreTest {

	private NotesStore store;

	@Before
	public void setup() {
		store = new NotesStore();
	}

	@Test
	public void testAddASingleNote() {

		// given
		assertThat(store.getNotes()).isEmpty();

		// when
		Dispatcher.getInstance().dispatch(new AddNoteAction("something"));

		// then
		assertThat(store.getNotes()).contains("something");
	}

	@Test
	public void testAddMultipleNotes() {

		// given
		assertThat(store.getNotes()).isEmpty();

		// when
		Dispatcher.getInstance().dispatch(new AddNoteAction("something"));
		Dispatcher.getInstance().dispatch(new AddNoteAction("second"));
		Dispatcher.getInstance().dispatch(new AddNoteAction("third"));

		// then
		assertThat(store.getNotes()).containsExactly("something", "second", "third");
	}

	@Test
	public void testAddEmptyNotes() {
		// given
		assertThat(store.getNotes()).isEmpty();


		// when
		Dispatcher.getInstance().dispatch(new AddNoteAction(""));
		// then
		assertThat(store.getNotes()).isEmpty();


		// when
		Dispatcher.getInstance().dispatch(new AddNoteAction(null));
		// then
		assertThat(store.getNotes()).isEmpty();


		// when
		Dispatcher.getInstance().dispatch(new AddNoteAction("    "));
		// then
		assertThat(store.getNotes()).isEmpty();
	}
}

In the example I'm using JUnit and AssertJ for testing. As there is no View available in a Unit-Test we are directly using the Dispatcher to create new Actions. The publishAction method that we have seen above in the View class is just a shortcut that delegates the actions to the singleton Dispatcher in the same way.

If we had made our processAddNoteAction method in the store to be package scoped we could use it directly in the test class and could skip the Dispatcher. It's up to you which option you choose.

The complete code for this example can be seen in the repository.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].