Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Writing QML Application in a Flux way
QtWS25 Last Chance

Writing QML Application in a Flux way

Scheduled Pinned Locked Moved QML and Qt Quick
fluxdesign pattern
33 Posts 10 Posters 20.1k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • B Offline
    B Offline
    benlau
    Qt Champions 2016
    wrote on 12 Jun 2015, 09:15 last edited by benlau 1 Apr 2016, 19:47
    #1

    (Edit: A new version of this article is available on Medium. It comes with better diagram , example code and explanation.

    Action-Dispatcher Design Pattern for QML — Medium
    )

    After coding QML for a year, I think QML is a great concept. However, it is not a perfect solution. There still has plenty of room for improvement. On the other hand,I also think about can I write QML in a better way.

    The first problem is testability. How can I write test code in a more easy way?

    The second problem is writing clean code. Generally speaking, break down big QML file into smaller files should be more readable and reusable. But it is not always true. I found that I did a lot of copy & paste in .qml file for signal propagation / property passing. Refactoring is very troublesome.

    Finally, I come up with an idea inspirited by React’s Flux Application Framework. I would like to share my idea of writing QML application in Flux way. Any suggestion of this idea will be highly welcomed.

    The Problem

    Signal propagation in QML can be troublesome. Let’s take a simple example. Suppose you have a list of item, each item has a delete button to remove it from the list. Once it is pressed, it will show a confirmation dialog.

    Hierarchy :

    YourWindow.qml -  YourListView.qml - YourItem.qml
    

    Usually you should avoid making a super big QML file by down break component into separate files to keep it simple and reusable. However, sometimes it do not simplify the architecture due to signal propagation.

    YourItem should be the component that receive mouse event. But what component should prompt a dialog and perform removal? YourListView.qml? YourWindow.qml?

    YourListView.qml :

    ListView {
       delegate: YourItem {
           onRemoveClicked: alertDialog.open();
       }
    }
    

    or

    YourListView.qml (Signal Propagation) :

    ListVIew {
      id: listView
       signal removeClicked(int index);
       delegate: YourItem {
           onRemoveClicked: listView.removeClicked(model.index);
       }
    }
    

    And then imaginary you got a new requirement: "Press on an item to launch another window."

    Obviously, it is not duty of YourListView. And probably it will become:

    ListVIew {
       id : listView;
       signal removeClicked(int index);
       signal clicked(int index);
       delegate: YourItem {
           onRemoveClicked: listView.removeClicked(model.index );
           onClicked: listView.clicked(model.index);
       }
    }
    

    More and more signal will be added in product life cycle. (e.g Sorting, Edit, Clone, tag ….) It is fine if you don’t refactor the code. Otherwise, you should be aware of breaking a working function.

    What happen if you are asked to add a TabBar to hold multiple list? The filtering rule of list in each tab is different.

    Hierarchy:

    YourWindow.qml -  YourTabBar.qml - YourListView.qml - YourItem.qml
    

    YourTabBar.qml:

    TabBar {
      id: tabBar
       signal removeClicked(string id);
       signal clicked(string id);
    
       Tab {
         ListView {
             onRemoveClicked: { tabBar.removeClicked(id); }
             onClicked: { tabBar.clicked(id) };
         }
       }
    
       Tab {
         ListView {
             onRemoveClicked: { tabBar.removeClicked(id); }
             onClicked: { tabBar.clicked(id) };
         }
       }
    }
    

    It is just too terrible! Too many C&P codes there!

    An alternative approach to solve this problem is passing reference of YourWindow component or callback to YourItem and let its execute directly. But that means you still need to pass the value from YourWindow to YourItem via YourTabBar and YourListView. And YourItem can not be used out of YourWindow.

    Therefore, I would like to propose another method inspired by Facebook’s React Flux Application Framework.

    What is Flux Application Framework?

    Flux | Application Architecture for Building User Interfaces

    In Flux Application framework, have three major parts: the dispatcher, the stores, and the views (React components). Controller do not existed 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. This works especially well with React's declarative programming style, which allows the store to send updates without specifying how to transition views between states."

    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."

    Architecture:

    Architecture

    View component read from Store but do not write to it directly. It ask Action to do so. The data flow is unidirectional.

    Architecture

    The “Action” component is in fact a singleton component which is accessible from anywhere in the code. Usually it just provide helper function to send message to Dispatcher.

    A store can response to multiple messages. A message may also be processed by multiple stores. It is very flexible.

    So how Flux application framework could improve our code?

    First of all, we could declare an ItemAction singleton component.

    ItemAction.qml:

    pragma Singleton;
    
    QtObject {
     function open(id) { AppDispatcher.dispatch(“ItemOpen”, { id : id })  };
     function remove(id) { AppDispatcher.dispatch(“ItemRemove”, { id: id})  }
    }
    

    The ItemAction component provides a set of helper functions to dispatch message. But it has no knowledge about receiver.

    As it is singleton component, it could be called in anywhere. So YourItem may call its method directly.

    YourItem.qml:

    Item {
      Button {
        onClicked: ItemAction.remove(id);
      }
      Button {
        onClicked: ItemAction.open(id);
      }
    }
    

    Receiver can be anywhere too. Dependence is not a matter.

    YourWindow.qml

    Connections {
       target: AppDispatcher
       onDispatched : {
        if (name === “ItemRemove”} { /// do something
       } else if (name === “ItemOpen” } {
       }
      }
    }
    

    The signal propagation flow before using Flux Application Framework
    Before Using Flux

    The signal propagation flow after using Flux Application Framework

    After Using Flux

    Remarks : Dispatcher is not shown in the above diagram. Because you don’t need to modify dispatcher to add signal.

    The immediate benefit of using Flux is the removal of unnecessary signal propagation / property passing.

    So how about Store? It is not shown in the above example.

    Since QT support data binding for every component derived from QObject. A delegated store component may not be necessary. A QML item can be a mixture of store / model / view. How to use the data received from Dispatcher should be up to programmer’s choice.

    Therefore, architecture may become:

    architecture.png (353×182)
    Architecture of Action-Dispatcher design pattern for QML.

    Conclusion

    Pros

    • Clean up code by remove unnecessary signal propagation and property passing.
    • Loose coupling design. Refactoring is easy.
    • Better code reuse
    • Your code is more testable via the Action component.

    Cons

    • You need to declare Action components before use it. Sometimes it is overkilled solution for a problem.

    An implementation of Dispatcher is available at : benlau/quickflux

    What do you think about this approach? Any question / feedback is highly welcomed.

    1 Reply Last reply
    4
    • G Offline
      G Offline
      greenhouse
      wrote on 12 Jun 2015, 11:14 last edited by
      #2

      "Architecture of Action-Dispatcher design pattern for QML" could be a nice title for a book by the way... :)

      1 Reply Last reply
      0
      • SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on 14 Jun 2015, 19:38 last edited by
        #3

        Hi,

        Looks pretty interesting ! Did you try to submit that to the Qt devs ?

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        B 1 Reply Last reply 28 Jun 2015, 08:37
        0
        • SGaistS SGaist
          14 Jun 2015, 19:38

          Hi,

          Looks pretty interesting ! Did you try to submit that to the Qt devs ?

          B Offline
          B Offline
          benlau
          Qt Champions 2016
          wrote on 28 Jun 2015, 08:37 last edited by
          #4

          @SGaist Should be interesting. But I never tried to submit. Where should I submit to?

          1 Reply Last reply
          0
          • SGaistS Offline
            SGaistS Offline
            SGaist
            Lifetime Qt Champion
            wrote on 28 Jun 2015, 21:45 last edited by
            #5

            I'd start on the interest mailing list

            Interested in AI ? www.idiap.ch
            Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

            1 Reply Last reply
            0
            • B Offline
              B Offline
              benlau
              Qt Champions 2016
              wrote on 10 Sept 2015, 13:51 last edited by
              #6

              QuickFlux v1.0.1 has been released.

              Changes:

              AppListener

              Added new properties, “filter” and “filters”. Once it is set, only message with name matched will emit “dispatched” signal.

              By using “filter" property, users could write their listener in a more simple way:

              New Method:

              AppListener {
                  filter: “NewItem"
                  onDispatched: {
                     // Code to handle “NewItem” message here.
                 }
              }
              

              Old method:

              // Listen for multiple messages.
              AppListener {
                  onDispatched: {
                      switch (name) {
                          case "messageType1":
                              // ...
                              break;
                          case "messageType2":
                              // ...
                              break;
                      }
                  }
              }
              

              `
              or

              AppListener {
              
                Component.onComponented: {
                  on("messageType1",function() {
                     /// Your code here.
                  });
                  on("messageType2",function() {
                     /// Your code here.
                  });
                }
              }
              
              1 Reply Last reply
              0
              • clogwogC Offline
                clogwogC Offline
                clogwog
                wrote on 28 Sept 2015, 01:36 last edited by
                #7

                thanks for posting this.

                in the qmlunittests main.cpp references
                #include <QtQuickTest/quicktest.h>

                but i can't find it in the https://github.com/benlau/quickflux project.
                do you have an example app ?

                B 1 Reply Last reply 28 Sept 2015, 06:46
                0
                • clogwogC clogwog
                  28 Sept 2015, 01:36

                  thanks for posting this.

                  in the qmlunittests main.cpp references
                  #include <QtQuickTest/quicktest.h>

                  but i can't find it in the https://github.com/benlau/quickflux project.
                  do you have an example app ?

                  B Offline
                  B Offline
                  Ben Lau
                  wrote on 28 Sept 2015, 06:46 last edited by
                  #8

                  @clogwog The qmlunittests program is an test suite to verify the correctness of the program. Things like header of "<QtQuickTest/quicktest.h>" is not needed in your program. So you may just ignore this folder.

                  Instead, I just made an example program to demonstrate how to use it:

                  quickflux/examples/todo at master · benlau/quickflux

                  Please feel free to ask if you have any questions.

                  clogwogC 1 Reply Last reply 28 Sept 2015, 23:09
                  0
                  • R Offline
                    R Offline
                    RockAndCode
                    wrote on 28 Sept 2015, 17:45 last edited by
                    #9

                    Great article. Thanks. This will help me a lot.

                    1 Reply Last reply
                    0
                    • B Ben Lau
                      28 Sept 2015, 06:46

                      @clogwog The qmlunittests program is an test suite to verify the correctness of the program. Things like header of "<QtQuickTest/quicktest.h>" is not needed in your program. So you may just ignore this folder.

                      Instead, I just made an example program to demonstrate how to use it:

                      quickflux/examples/todo at master · benlau/quickflux

                      Please feel free to ask if you have any questions.

                      clogwogC Offline
                      clogwogC Offline
                      clogwog
                      wrote on 28 Sept 2015, 23:09 last edited by
                      #10

                      @Ben-Lau thank you for the example ! it will help a lot.

                      1 Reply Last reply
                      0
                      • B Offline
                        B Offline
                        benlau
                        Qt Champions 2016
                        wrote on 25 Nov 2015, 17:45 last edited by
                        #11

                        v1.0.3 has been released.

                        Changes:

                        New Components

                        1. AppScript

                        AppScript is a helper component to handle asynchronous sequential workflow. The immediate benefit of using AppScript is the centralisation of code in a place. Instead of placing them within onXXXX code block in several components in several places.

                        1. AppListenerGroup

                        AppListenerGroup collects listener ID from all of its child AppListener and initialize their waitFor property.
                        It could be used as the base type of a Store component for setup dependence between them.

                        MyStore1.qml

                        AppListenerGroup {
                            AppListener {
                            }
                            AppListener {
                            }
                        }
                        

                        MyStore2.qml

                        AppListenerGroup {
                           waitFor: MyStore1.listenerIds
                        }
                        

                        Changes

                        AppDispatcher

                        1. Added addListener() function

                        Registers a callback to be invoked with every dispatched message. Returns a listener ID that can be used with waitFor().

                        1. Added removeListener() function

                        Remove a callback by the listenerId returned by addListener

                        1. Added waitFor() function

                        Waits for the callbacks specified to be invoked before continuing execution of the current callback. This method should only be used by a callback in response to a dispatched message.

                        AppListener

                        1. Added “listenerId” property

                        The listener ID of this component. It could be used with AppListener.waitFor/AppDispatcher.waitFor to control the order of message delivery.

                        1. Added “waitFor” property

                        If it is set, it will block the emission of dispatched signal until all the specified listeners invoked.

                        Example code:

                        AppListener {
                          id: listener1
                        }
                        
                        AppListener {
                          id: listener2
                          waitFor: [listener1.listenerId]
                        }
                        
                        1 Reply Last reply
                        0
                        • V Offline
                          V Offline
                          Vincent007
                          wrote on 24 Dec 2015, 11:37 last edited by
                          #12

                          Hi Ben,

                          Thank for your contributions!

                          Did you discuss about writing QML Application in a Flux way with developers of Qt via Qt development mailing list?

                          If not, why not?

                          Merry Christmas!

                          B 1 Reply Last reply 24 Dec 2015, 14:06
                          0
                          • V Vincent007
                            24 Dec 2015, 11:37

                            Hi Ben,

                            Thank for your contributions!

                            Did you discuss about writing QML Application in a Flux way with developers of Qt via Qt development mailing list?

                            If not, why not?

                            Merry Christmas!

                            B Offline
                            B Offline
                            benlau
                            Qt Champions 2016
                            wrote on 24 Dec 2015, 14:06 last edited by
                            #13

                            @Vincent007 no. hmmm... Just I can't think of any reason to send to dev mailing list. Ask them for comment? or ask them to include related class in next version? Ofcoz it will be better if more people can comment / raise suggestion, but I just not sure should I send to dev mailing list.

                            Merry Christmas!

                            1 Reply Last reply
                            0
                            • V Offline
                              V Offline
                              Vincent007
                              wrote on 24 Dec 2015, 14:42 last edited by Vincent007
                              #14

                              Hi Ben,

                              I think your work can help Qt developers think how QML should be evolved in feature. Therefore you can discuss with them about the evolution of QML by your work.

                              B 1 Reply Last reply 28 Dec 2015, 05:17
                              0
                              • V Vincent007
                                24 Dec 2015, 14:42

                                Hi Ben,

                                I think your work can help Qt developers think how QML should be evolved in feature. Therefore you can discuss with them about the evolution of QML by your work.

                                B Offline
                                B Offline
                                benlau
                                Qt Champions 2016
                                wrote on 28 Dec 2015, 05:17 last edited by
                                #15

                                @Vincent007 hmm. Let's me think about it. By the way, I am going to publish another article with similar topic but better explanation, code and diagram on a blog in this week.

                                B 1 Reply Last reply 2 Jan 2016, 21:26
                                0
                                • clogwogC Offline
                                  clogwogC Offline
                                  clogwog
                                  wrote on 2 Jan 2016, 15:01 last edited by
                                  #16

                                  Hi Ben,

                                  i'm not sure if this is a bug but i'm trying to figure out how to use quickflux by making a small change to the todo example.

                                  I added a 'mark all items done' button and mark all items as done. however the ui doesn't always update.

                                  example: 1) start application (3 items in list 1 in done)
                                  2) press 'all done' -> all items disappear (expected)
                                  3) show all items by checking 'show completed' (expecteD)
                                  4) unmark one item (for example 'Task A')
                                  5) press 'all done' -> 'Task A' doesn't update to done ?

                                  if i uncheck and re-check 'show completed' the model and view are back in sync.

                                  https://github.com/clogwog/fluxtest1

                                  am i missing something ?

                                  B 1 Reply Last reply 2 Jan 2016, 17:27
                                  0
                                  • clogwogC clogwog
                                    2 Jan 2016, 15:01

                                    Hi Ben,

                                    i'm not sure if this is a bug but i'm trying to figure out how to use quickflux by making a small change to the todo example.

                                    I added a 'mark all items done' button and mark all items as done. however the ui doesn't always update.

                                    example: 1) start application (3 items in list 1 in done)
                                    2) press 'all done' -> all items disappear (expected)
                                    3) show all items by checking 'show completed' (expecteD)
                                    4) unmark one item (for example 'Task A')
                                    5) press 'all done' -> 'Task A' doesn't update to done ?

                                    if i uncheck and re-check 'show completed' the model and view are back in sync.

                                    https://github.com/clogwog/fluxtest1

                                    am i missing something ?

                                    B Offline
                                    B Offline
                                    benlau
                                    Qt Champions 2016
                                    wrote on 2 Jan 2016, 17:27 last edited by
                                    #17

                                    @clogwog I can't reproduce your problem in my example program and in your code. When it is set to "Show Completed". It will show every tasks. So if you set a task done in "Show Completed" mode. It won't disappear. The behaviour should be correct.

                                    1 Reply Last reply
                                    0
                                    • clogwogC Offline
                                      clogwogC Offline
                                      clogwog
                                      wrote on 2 Jan 2016, 21:14 last edited by clogwog 1 Mar 2016, 00:22
                                      #18

                                      no, i was expecting it to show all tasks as you said, i just expected it to switch back to checked when i pressed the 'all done' button

                                      so when you are in the 'show completed' mode in step 5 with a task you just set to 'uncompleted' and then press 'all done' your tasks goes back to checked ?

                                      B 1 Reply Last reply 3 Jan 2016, 07:47
                                      0
                                      • B benlau
                                        28 Dec 2015, 05:17

                                        @Vincent007 hmm. Let's me think about it. By the way, I am going to publish another article with similar topic but better explanation, code and diagram on a blog in this week.

                                        B Offline
                                        B Offline
                                        benevo
                                        wrote on 2 Jan 2016, 21:26 last edited by
                                        #19

                                        @benlau

                                        Hi Ben,

                                        Thank for your sparking idea about this area, It's charming & meaningful to do this.
                                        BTW, could U show the link of Ur another article with better description if possible.

                                        Thanks !

                                        Happy New Year !

                                        1 Reply Last reply
                                        0
                                        • clogwogC clogwog
                                          2 Jan 2016, 21:14

                                          no, i was expecting it to show all tasks as you said, i just expected it to switch back to checked when i pressed the 'all done' button

                                          so when you are in the 'show completed' mode in step 5 with a task you just set to 'uncompleted' and then press 'all done' your tasks goes back to checked ?

                                          B Offline
                                          B Offline
                                          benlau
                                          Qt Champions 2016
                                          wrote on 3 Jan 2016, 07:47 last edited by
                                          #20

                                          @clogwog said:

                                          no, i was expecting it to show all tasks as you said, i just expected it to switch back to checked when i pressed the 'all done' button

                                          so when you are in the 'show completed' mode in step 5 with a task you just set to 'uncompleted' and then press 'all done' your tasks goes back to checked ?

                                          oh, you mean in your program. I didn't realize that you have added a "All Done" button. The problem is about data binding in Qt. Qt use one-way data binding. Although it has binded CheckBox.checked to model.done , the binding will lost whatever it is toggled by user event. It is overridden by system. And that is why step 5 failed.

                                          You may solve this problem in this way:

                                          At TodoVisualDataModel.qml:

                                              delegate: TodoItem {
                                                  id:item
                                                  uid: model.uid
                                                  title: model.title
                                                  property bool done: model.done
                                          
                                                  onDoneChanged: {
                                                      checked = model.done;
                                                  }
                                          
                                                  Component.onCompleted: {
                                                      item.VisualDataModel.inNonCompleted = Qt.binding(function() { return !model.done})
                                                  }
                                              }
                                          

                                          @benevo: thx. The article is not ready yet. I will post it within this few days.

                                          1 Reply Last reply
                                          1

                                          • Login

                                          • Login or register to search.
                                          • First post
                                            Last post
                                          0
                                          • Categories
                                          • Recent
                                          • Tags
                                          • Popular
                                          • Users
                                          • Groups
                                          • Search
                                          • Get Qt Extensions
                                          • Unsolved