My first attempt to use Redux in a QML application

Ben Lau
E-Fever
Published in
4 min readNov 30, 2016

After I have written the article of QML Application Architecture guide with Flux, I got few enquiries about Redux. Can it be used in QML application too?

At first glance, it does not quite make sense to use Redux in QML application. Because Redux store all application state in a single JavaScript object and you can’t establish a property based data binding to a JavaScript object. Therefore, it won’t notify the changes of data from Redux to QML items.

What if we store application state in a QtObject like QuickFlux ?

That will cause another problem in reducer function.

Reducer is the fundamental concept of Redux. It is a pure function that takes the previous state and action as input parameter then return the next state:

var nextState = reducer(prevState, action)

Since it creates a new object on an update, your data binding will be lost.

Therefore , I thought redux is not suitable for QML. But now it is proven to be wrong. Redux do works with QML. I have verified this in a mini-game and SparkQML project.

What it needs is to apply the concept of synchronization like the QSyncable project. And the cost is not expensive. I will talk about what I did to get it works in this article.

Prerequisites

To get started , you only need to have the Redux library be installed. React library is not needed. But I would recommend you to get Lodash / immutability helper too. That could save your time in writing reducers.

Qmlified Javascript libraries:

  1. Redux
  2. Lodash
  3. Immutability-Helper

Setup

To create a store , you must have a reducer .

An example reducer:

Then create store by

import "./js/redux”...var store = Redux.createStore(reducer);...

That is almost same as regular Redux program except for the import statement. But next section will be different.

Data Binding

Currently, it has no way to establish a property based data binding from a plain Javascript object. Even you have applied any dirty hack and get it works. It will violate the design principle of reducer for returning a new state object on updates.

So just forget it. Let’s think it in another way. Is it really necessary to setup data binding from the state object directly? Of coz not .

Instead, we may create an extra QtObject which keep a copy of state locally. I call it as Store Provider. Because it is used to provide store data (the state) for visual items .

Whatever the state object changed, it will perform a one way synchronization by applying the diff between previous and next state. The idea is similar to the concept of QSyncable.

An example of StoreProvider

QtObject {  property int counter: 0}

Synchronization

The first step of synchronization is to find out the diff from previous to next state, then create a patch to update the data provider (QtObject). I have implemented a shallowDiff() function for this purpose:

I know you may doubt the cost of this operation. In fact, it is low cost as long as you have followed the Immutable Update Patterns.

But I am not going to explain how the pattern works in this article. You may get further information from the link above.

Once you have the patch object, you could update the data provider by using a custom assign()

Now you could update your UI by using Redux. But instead to perform the synchronization manually. I would recommend to use a middleware for this task.

Middleware

According to the design principle of redux, reducer must be a pure function that will not perform side-effects like API calls and routing transitions.

Therefore, it can only handle asynchronous workflow as a state machine. But somehow it is quite a trouble to manage a state machine within reducer. People usually use another mechanism to handle asynchronous workflow. That is “Middleware”

It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. People use Redux middleware for logging, crash reporting, talking to an asynchronous API, routing, and more.

Middleware · Redux

Log middleware

The code above is an example of middleware as a logger. It log all the action dispatched before passing to reducer. The code look a bit ugly. Because it is originally designed to use with ES6 but unfortunately QML do not support ES6 yet.

I use middleware for following use-cases:

  1. Synchronize StoreProvider [Code]
  2. Convert action into signal [Code]
  3. Open a file dialog and ask user where to save a file. [Code]

Conclusion

I enjoy writing QML application in a Redux way. Because application logic and user interface / system component are completely separated. Such that writing test case is much easier. To simulate a specific condition, you only need to modify the state object directly. It just takes few lines of code to have everything ready.

Moreover, it works perfectly with QML state system. You could control how your UI component looks like by providing the state name from reducer.

Flux architecture may offer the same benefit. But it misses a solidly designed way for handling an asynchronous workflow. People may do it in store such that application logic and system API is not separated. I always feel troubled in writing test case for that kind of stores.

That is my first attempt to use Redux in QML. You could find the source code from SparkQML project. If you have any suggestion / feedback for related topics. Please feel free to leave a comment here and discuss together.

--

--