QML Application Architecture Guide with Flux

Ben Lau
7 min readMar 1, 2016

--

This guide is outdated. You may find a revised version at this link: “Revised QML Application Architecture Guide with Flux

Currently, I don’t see anything like a standard application architecture guide available for a QML application yet. People may write their software by MVC, MVVM and any other pattern else. The most popular choice should be MVVM like approach: Declare data model and logic in a QObject written by C++ and implement GUI in QML and JavaScript.

That is not a bad approach. Easy to understand and flexible enough. But it does not tell you how to manage your QML components. And probably people may also write their application logic in inline Javascript code too( those in onXXX code block). The result is a lot of fragmented code spread across view items. Once your application is getting more complicated. It is getting more difficult to maintain your code clean and testable.

This article intends to point out the problems and provides a solution of architecture design inspired by Facebook Flux.

The solution is based on my project, QuickFlux , a library provides central dispatcher and utility components for writing QML in a Flux way

What is Flux Application Framework?

It is an application architecture designed by Facebook. In Flux, have three major parts: the dispatcher, the stores, and the views (React components). Controller do not exist in Flux application.

Flux eschews MVC in favor of a unidirectional data flow. When a user interacts with a React view, the view propagates an action through a central dispatcher, to the various stores that hold the application’s data and business logic, which updates all of the views that are affected.

Architecture

Actions

The dispatcher exposes a method that allows us to trigger a dispatch to the stores, and to include a payload of data, which we call an action. The action’s creation may be wrapped into a semantic helper method which sends the action to the dispatcher. For example, we may want to change the text of a to-do item in a to-do list application.

A Single Dispatcher

The dispatcher is the central hub that manages all data flow in a Flux application. It is essentially a registry of callbacks into the stores and has no real intelligence of its own — it is a simple mechanism for distributing the actions to the stores. Each store registers itself and provides a callback. When an action creator provides the dispatcher with a new action, all stores in the application receive the action via the callbacks in the registry.”

Stores

Stores contain the application state and logic. Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they do not represent a single record of data like ORM models do. Nor are they the same as Backbone’s collections. More than simply managing a collection of ORM-style objects, stores manage the application state for a particular domain within the application.

The main different between Flux and MVC/MVVM design pattern is the separation of queries and updates. Although View components read from Store to render content, it doesn’t write to it directly. Instead, it asks Action to do so. Store is a read-only data model that support “query” only. It can only be “updated” through Action.

In traditional MV* application, View read data from Model and write to it directly. It is simple, if the relationship is one-to-one mapping. But usually It is not. User’s action on a View component may trigger the update of several models. And sometimes a model may need to update other models too. In this case, the data flow will be complicated and difficult to trace and debug.

Data flow of an MVC application — Just imaginary you are playing a ping pong game, but the no. of player is inconsistent on different side.

However, once you have separated the queries and updates using Action and Dispatcher, the data flow will become unidirectional. The data flow always begins at delivering an Action. If this action triggers another kind of update, it will dispatch a new action to let the data flow start over again. That will simplify your application’s data flow, such that it will be more easier to trace and debug.

Example of Unidirectional Data Flow

Remarks: The diagram above is just an example of particular data flow. It do not mean that you can’t dispatch a new action in store component.

For more information about Flux, I would recommend to read “A cartoon guide to Flux — Code Cartoons — Medium“. Next section will talk about how to implement a QML application in a Flux way.

Application Architecture

/constants/Constants.qml
/actions/ActionTypes.qml
/actions/AppActions.qml
/views/
/stores/
/adapters/
/main.qml

It is the proposed application structure of a QML application written in a Flux way. An example project is available at : https://github.com/benlau/quickflux/tree/master/examples/todo

actions/ActionTypes.qml

ActionTypes is a constant table (singleton component) to store all the available action types in an application

It is not recommended to name an action by combing sender and event (e.g removeItemButtonClicked). It is suggested to tell what users do (e.g. askToRemoveItem, remove an item but it need to prompt a dialog for confirmation) or what it should actually do (e.g. removeItem). You may add a prefix of scope to its name if needed. (e.g. itemRemove)

actions/AppActions.qml

AppActions is an action creator, a helper component to create and dispatch actions via the central dispatcher. It has no knowledge about the data model and who will use it. As it only depends on AppDispatcher, it could be used anywhere.

AppDispatcher is the central dispatcher. It is also a singleton object. Actions sent by dispatch() function call will be placed on a queue. If there has no pending actions, dispatcher will emit a“dispatched” signal immediately. It is designed to avoid out-of-order message processing.

Moreover, there has a side benefit in using ActionTypes and AppActions. Since they contains all the actions in an application, when a new developer joins the project. He/she may open theses two files and know the entire API.

AppActions Example

EDIT: In case you feel trouble to implement the dispatch function in the AppActions.qml, you may try ActionCreator component that was added since QuickFlux 1.0.5 . It is a component that listens on its own signals, convert to message then dispatch via AppDispatcher. The message type will be same as the signal name. There has no limitation on number of arguments and their data type.

Therefore, you could rewrite the AppActions.qml above in this way:

/stores

Stores contain application data, state and logic. Somehow it is similar to View Model in MVVM. But due to the principle of unidirectional flow. It doesn’t export update methods to Views. It is read-only to Views. “Updates” should only be done via Actions.

Moreover, Store is supposed to be a singleton object.

/adapters

Quoted from Why We Need a Dispatcher:

As an application grows, dependencies across different stores are a near certainty. Store A will inevitably need Store B to update itself first, so that Store A can know how to update itself. We need the dispatcher to be able to invoke the callback for Store B, and finish that callback, before moving forward with Store A. To declaratively assert this dependency, a store needs to be able to say to the dispatcher, “I need to wait for Store B to finish processing this action.” The dispatcher provides this functionality through its waitFor() method.

However, Store can not set dependence to another store by itself due to QTBUG-49370 — Using another singleton object in the same package may hang.

Moreover, singleton object is not instantiated by importing the package. It is created only if the object is referred.

And therefore you need an external component to load singleton object and setup their dependence, and that is “adapter”. It is used to setup data dependence and handle asynchronous event flow across stores.

In this adapter, it loads TodoStore and UserPrefsStore by refer them and setup their dependence.

Adapter is not an element in Flux. And unlike Store, it should not be a singleton object. And probably you need to use it at the entry point of your application.

/views

It is the folder for view components. It is not recommended to put your application logic within view components. Because inline code for handling user event can be hard to trace and test. They are not centralized in one source file. The dependence is not obvious and therefore it is easy to be broken during refactoring.

In order to simplify your inline code, you may follow the “Tell, Don’t Ask” principle.

That is , whatever you have received an UI event, you should tell Action Creator what you want to do, do not ask Store for questions and make a decision.

However, there has an exception case. If the inline code is responsible for handling animation / transition effect, it will be better to leave it to view components.

/constants/Constants.qml

Constants.qml stores contant values in your application.

/main.qml

That is the entry point of a QML application. You may place your adapter items there.

Conclusion

When people talk about Flux, they usually emphasis on unidirectional data flow. But in fact, the principle of “queries” and “updates” separation is easier to understand for QML developers. By using the principle with Actions, Dispatcher and Stores, it could improve QML code with the following benefits :

  • Clean up the code by removing unnecessary signal propagation. (More information at this article )
  • List of all available user actions in a file. (at ActionTypes.qml)
  • Reduce no. of inline Javascript code in Views. (Those in onClicked signal)
  • Loose coupling design. Easy refactoring.
  • Improving testability through Action Creator component.
  • Unidirectional Data Flow (Simple Message Flow)

If you have any questions / suggestions for this guide, please feel free to leave a comment here.

FAQ

Q1. Why use AppDispatcher instead of listening from AppActions directly?

A1. See this article

References:

  1. What the Flux? (On Flux, DDD, and CQRS) — Jack Hsu
  2. Action-Dispatcher Design Pattern for QML
  3. A cartoon guide to Flux — Code Cartoons
  4. Flux: Qt Quick with unidirectional data flow

--

--