What is new in Quick Flux 1.1?

Ben Lau
E-Fever
Published in
6 min readApr 18, 2017

In the article of QML Application Architecture Guide, it has introduced how to apply the concept of Flux in QML and the benefit of “Update”/“Query” separation. It gives a guideline on how to organise your QML files and write clean code by avoiding unnecessary signal propagation.

However, due to the use of singleton dispatcher and store, users may complain about the difficulty to write test cases. Moreover, store to store dependence is a bit trouble to manage due to the QTBUG-49370. The version 1.0 only provide a waitFor mechanism to manage the order of action message delivery, which is over-complicated.

Therefore, Quick Flux 1.1 is developed to resolve the above problems.

New Features:

1. Non-singleton dispatcher

2. Middleware — a mechanism to extend the functionality of dispatcher allowing for advanced asynchronous workflow and integration with visual components like FileDialog / Camera etc.

3. Store component type — A replacement of AppListener that could listen from a non-singleton dispatcher, and re-dispatch the action message to another Store components sequentially. It could avoid the over-complicated `waitFor` mechanism.

4. Hydration — Serialize and deserialize a store to/from a Json object.

Non-Singleton Dispatcher

The version 1.0 only offers a singleton object, AppDispatcher, as a global dispatcher for action delivery. Now QuickFlux will also provide a non-singleton dispatcher.

For example,

And you could assign a dispatcher to ActionCreator

Middleware

The middleware in Quick Flux is similar to the one in Redux and those from server libraries like Express and Koa. It is some code you can put between the Dispatcher and Stores. It could modify/defer /remove received actions and insert new actions that will dispatch to Store components. Users may use it for logging, crash reporting, talking to asynchronous components like FileDialog / Camera etc. So that Store components remain “pure”, it holds application logic only and always return the same result for the same input. It is easier to write test cases.

Quick Flux Application Data Flow with Middleware

Use-Case #1 — Logging

Sometimes you may want to log all the action dispatched for debugging. That could be done by a Logger middleware

Apply the middleware to AppDispatcher to take effect.

Use case 2 — Debouncer

There are many situations where you want a function is bound to a signal fired once only within a specific amount of time. For example, users may press “enter” key on a focused button for twice and of coz, you don’t want to handle the same task repeatedly. Moreover, you may want to execute a function with slightly delay to allow certain visual effects to happen. That is what the debounce function supposed to do.

Beside to put it in your visual component, it could also work as a middleware.

Use case 3 — Async Flow

It is suggested to let Store component to talk with async items like FileDialog / Camera via the dispatcher. That could make them decoupled so that it is easier to write a mock object to conduct unit tests.

Middleware offers an alternative way for Store and asynchronous items communication. That could eliminate the need for a mock object in writing unit test.

Assume you need to write a code to remove an item from a list. Before actually remove it, it should prompt a dialog for confirmation

Store Component Type

Store in Flux contains application data, state and logic. Somehow it is similar to View Model in MVVM. But it doesn’t have update function. It is read-only from view components.

In QF 1.0, user may use AppListener as the base type of the Store . But it does have few problems.

1. It could only listen from AppDispatcher. It is troublesome to write test case

2. AppListener uses a `waitFor` property to determine the dependence and the order of message delivery. Which is same as the original flux implementation from Facebook. But that is overcomplicated, which is dropped from most of the framework already.

3 AppListener is an Item(QQuickItem) type. It has too many properties which are not needed

Therefore, a new component type, Store, is added to replace AppListener in the v1.1 release.

The Different:

1. Store parent type is QtObject. It doesn’t contain unnecessary properties as a data store.

2. It could listen from non-singleton dispatcher and action creator

3. It could re-dispatch the received action to other stores sequentially. So that user may config the dependence and control the order of action message delivery. It is a replacement of AppListener.waitFor mechanism.

The action will be delivered in this order: subStore1 => subStore2 => rootStore

The store component may re-dispatch action to non-children component too. And that could be done by the “redispatchTargets” property

Hydration

Rehydration and dehydration are just another words for deserialize and serialize. It could be used to convert Store into JSON object, and vice versa.

Example

The Store Design Guide

The original Flux implementation uses multiple Stores while Redux only uses a single store for application state. What approach should we take in QML? Moreover, should we declare store as a singleton object? Since a singleton object is usually considered as a bad design.

Although one of the new features of QuickFlux 1.1 is non-singleton dispatcher and store, it is still suggested to use singleton store for the application store. Because it could help to keep the code of view components clean and easy to develop with tools like SparkQML.

So what is the point to offer non-singleton dispatcher? That is for writing unit tests.

Singleton object is bad because it is very easy to be overused and difficult to write a test case. However, it is undeniable that it is the best solution for several kinds of problem. (e.g logger, database connection pool) In fact, the original Flux implementation is also using a singleton dispatcher and store. It is not really a problem to use it. But you have to use it in a right way.

That is a suggested architecture for holding a singleton store.

1) MainStore — Use one and only one singleton store

Data dependence between singleton objects in the same package is hard in QML due to QTBUG-49370. Therefore, it should avoid using two singleton store objects.

2) RootStore — Declare the content of Main Store in an non-singleton object

Don’t implement the property fields in the singleton object directly. It should hold the implementation detail on its parent type. Because you could instantiate in a unit test. The instance is flashy created and doesn’t be influenced by another test case.

̶3̶)̶ ̶U̶I̶ ̶F̶o̶r̶m̶ ̶(̶o̶p̶t̶i̶o̶n̶a̶l̶)̶
̶U̶I̶ ̶F̶o̶r̶m̶ ̶i̶s̶ ̶a̶ ̶k̶i̶n̶d̶ ̶o̶f̶ ̶f̶i̶l̶e̶ ̶w̶i̶t̶h̶ ̶e̶x̶t̶e̶n̶s̶i̶o̶n̶ ̶.̶u̶i̶.̶q̶m̶l̶.̶ ̶I̶t̶ ̶c̶o̶n̶t̶a̶i̶n̶s̶ ̶a̶ ̶p̶u̶r̶e̶ ̶d̶e̶c̶l̶a̶r̶a̶t̶i̶v̶e̶ ̶s̶u̶b̶s̶e̶t̶ ̶o̶f̶ ̶t̶h̶e̶ ̶Q̶M̶L̶.̶ ̶W̶h̶e̶n̶ ̶y̶o̶u̶ ̶c̶r̶e̶a̶t̶e̶ ̶a̶n̶ ̶U̶I̶ ̶f̶o̶r̶m̶ ̶f̶i̶l̶e̶ ̶v̶i̶a̶ ̶Q̶t̶ ̶C̶r̶e̶a̶t̶o̶r̶,̶ ̶i̶t̶ ̶w̶i̶l̶l̶ ̶c̶r̶e̶a̶t̶e̶ ̶t̶w̶o̶ ̶f̶i̶l̶e̶s̶
̶Y̶o̶u̶r̶I̶t̶e̶m̶.̶q̶m̶l̶ ̶/̶/̶ ̶I̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶ ̶I̶t̶e̶m̶.̶ ̶I̶t̶s̶ ̶p̶a̶r̶e̶n̶t̶ ̶t̶y̶p̶e̶ ̶i̶s̶ ̶Y̶o̶u̶r̶I̶t̶e̶m̶F̶o̶r̶m̶.̶u̶i̶.̶q̶m̶l̶ ̶/̶/̶ ̶F̶o̶r̶ ̶d̶e̶c̶l̶a̶r̶a̶t̶i̶v̶e̶ ̶c̶o̶m̶p̶o̶n̶e̶n̶t̶.̶ ̶R̶e̶c̶o̶m̶m̶e̶n̶d̶ ̶t̶o̶ ̶e̶d̶i̶t̶ ̶i̶n̶ ̶D̶e̶s̶i̶g̶n̶ ̶m̶o̶d̶e̶
̶I̶t̶ ̶i̶s̶ ̶s̶u̶g̶g̶e̶s̶t̶e̶d̶ ̶t̶o̶ ̶m̶a̶k̶e̶ ̶t̶h̶e̶ ̶U̶I̶ ̶f̶o̶r̶m̶ ̶f̶i̶l̶e̶ ̶a̶s̶ ̶a̶ ̶p̶u̶r̶e̶ ̶c̶o̶m̶p̶o̶n̶e̶n̶t̶ ̶w̶h̶i̶c̶h̶ ̶h̶a̶s̶ ̶n̶o̶ ̶d̶e̶p̶e̶n̶d̶e̶n̶c̶e̶ ̶t̶o̶ ̶y̶o̶u̶r̶ ̶s̶i̶n̶g̶l̶e̶t̶o̶n̶ ̶s̶t̶o̶r̶e̶.̶ ̶O̶n̶l̶y̶ ̶s̶e̶t̶u̶p̶ ̶t̶h̶e̶ ̶d̶a̶t̶a̶ ̶b̶i̶n̶d̶i̶n̶g̶ ̶i̶n̶ ̶y̶o̶u̶r̶ ̶i̶m̶p̶l̶e̶m̶e̶n̶t̶a̶t̶i̶o̶n̶ ̶i̶t̶e̶m̶.̶ ̶T̶h̶a̶t̶ ̶m̶e̶a̶n̶ ̶y̶o̶u̶ ̶c̶o̶u̶l̶d̶ ̶c̶r̶e̶a̶t̶e̶ ̶a̶n̶d̶ ̶p̶r̶e̶v̶i̶e̶w̶ ̶y̶o̶u̶r̶ ̶i̶t̶e̶m̶ ̶w̶i̶t̶h̶o̶u̶t̶ ̶t̶h̶e̶ ̶i̶n̶f̶l̶u̶e̶n̶c̶e̶ ̶f̶r̶o̶m̶ ̶M̶a̶i̶n̶S̶t̶o̶r̶e̶.̶

Reference: Qt Quick UI Forms | Qt Creator Manual

Example

An example project is available at https://github.com/benlau/quickflux/tree/master/examples/middleware

If you have any question/comment, please feel free to ask here.

--

--