QuickFlux

repository·master·Indexed 43 days ago

https://github.com/benlau/quickflux

A Flux implementation for QML designed to bring structure and modern architecture to applications. It provides a centralized Dispatcher, Middleware, and Store components to avoid monolithic QML files and complex signal-binding webs. Key features include guaranteed sequential message processing via AppDispatcher, action interception through Middleware, and state serialization via Hydration.

Tokens
6.8K
Snippets
18
Records
32
Agent score
56%

What's inside QuickFlux

  1. QuickFlux project file structure

    master

    A project using this template follows this directory structure:

    • App/actions/: Contains ActionTypes.qml and AppActions.qml.
    • App/adapters/: Contains StoreAdapter.qml.
    • App/constants/: Contains Constants.qml.
    • App/stores/: Contains your store implementations (e.g., MainStore.qml).
    • App/storeworkers/: Contains StoreWorker.qml and specific implementations (e.g., MainStoreWorker.qml).
    • appview.cpp/h: C++ logic for initializing the QML environment and handling dispatcher messages.
    • main.cpp & main.qml: Application entry points.
    • qpm.json: Configuration for installing QuickFlux via qpm.
    .
    ├── App
    │   ├── actions
    │   │   ├── ActionTypes.qml
    │   │   ├── AppActions.qml
    │   │   └── qmldir
    │   ├── adapters
    │   │   ├── StoreAdapter.qml
    │   │   └── qmldir
    │   ├── constants
    │   │   ├── Constants.qml
    │   │   └── qmldir
    │   ├── stores
    │   │   ├── MainStore.qml
    │   │   └── qmldir
    │   └── storeworkers
    │       ├── MainStoreWorker.qml
    │       ├── StoreWorker.qml
    │       └── qmldir
    ├── app.pri
    ├── app.pro
    ├── app.qrc
    ├── appview.cpp
    ├── appview.h
    ├── deployment.pri
    ├── main.cpp
    ├── main.qml
    └── qpm.json
  2. Core Features of QuickFlux

    master

    QuickFlux provides a structured Flux implementation for QML applications with the following core capabilities:

    • Dispatcher: A singleton Message Dispatcher/Message Queue per QML Engine that guarantees sequential message processing (First come, first served).
    • Middleware: Allows for injecting or modifying actions before they are dispatched to the Store components.
    • Hydration: Supports serializing and deserializing Store components to and from JSON formats.
  3. Design principles for loose coupling in QML

    master

    To build scalable QuickFlux applications, follow these design principles:

    1. Decomposition: Avoid large QML files; break the UI into smaller, manageable components.
    2. Avoid Signal Propagation: Do not pass signals or data bindings between custom view components. Instead, let components communicate via the Flux loop (Actions $\rightarrow$ Dispatcher $\rightarrow$ Stores $\rightarrow$ Views).
    3. Loose Coupling: Components (like a Header, TodoList, and Footer) should have no knowledge of each other. They should only interact with the global state via Actions and Stores.
  4. Manage state and logic using Stores

    master

    Stores are the central holders of application data, state, and logic, similar to View Models in MVVM. However, they follow strict Flux principles:

    • Read-only for Views: Views can query data from Stores but must never write to them directly.
    • Updates via Actions: All state changes must be triggered by dispatching Actions.
    • Separation of Concerns: Queries (reading state) and updates (changing state) are strictly separated.

    You implement a Store by using the Store base component and a Filter to react to dispatched actions.

    import QtQuick 2.0
    import QuickFlux 1.1
    import "../actions"
    
    Store {
        property bool showCompletedTasks: false
    
        Filter {
            type: ActionTypes.setShowCompletedTasks
            onDispatched: {
                showCompletedTasks = message.value;
            }
        }
    }
  5. Create action creators with AppActions.qml

    master

    An AppActions component acts as an action creator. It provides helper methods to create and dispatch actions via the central AppDispatcher.

    Key characteristics:

    • It has no knowledge of the data model or specific consumers.
    • It depends only on AppDispatcher, making it usable anywhere in the application.
    • Using AppActions alongside ActionTypes provides a clear, discoverable API for the entire application.
  6. Understand the Middleware pattern in QuickFlux

    master

    In QuickFlux, Middleware is used to intercept and process actions between the dispatch of an action and the update of the Store. The provided Photo Album example demonstrates a clean separation of concerns using the following architecture:

    • Actions (/actions): Define ActionTypes and ActionCreator functions to describe what happened in the application.
    • Stores (/stores): Hold the application state and respond to actions.
    • Middlewares (/middlewares): Intercept actions to perform side effects (e.g., logging, API calls, or data transformation) before they reach the store.
    • Dumb Components (/components): Pure UI components that have no application dependencies. They receive data via props and emit signals (events) when user interaction occurs.
    • Smart Components/Views (/views): Components that connect the UI to the application logic. They receive data from the Store and call ActionCreators to trigger state changes.
  7. How the Dispatcher ensures message order and manages dependencies

    master

    QuickFlux uses an AppDispatcher to solve common issues with standard QML signal propagation:

    1. Guaranteed Message Order

    Standard QML signals follow a "last come, first served" pattern. If a signal is emitted while a callback is already running, the new signal is processed immediately, potentially causing out-of-order execution.

    AppDispatcher prevents this by using a queue. It will not dispatch a new message until all current listeners have finished receiving the current message. This ensures a "First come, first served" sequence.

    2. Managing Store Dependencies with waitFor

    In complex applications, one Store (e.g., Store A) may depend on another (e.g., Store B) to update first.

    AppListener provides a waitFor property. This allows a listener to declare a dependency on another listener. The dispatcher will ensure the dependency receives the message and finishes its processing before proceeding with the dependent listener, allowing you to declaratively control the order of message delivery.

  8. Install QuickFlux without qpm

    master

    To install QuickFlux manually:

    1. Clone the repository or download a release into a folder within your source tree.
    2. Add the .pri file to your .pro file using an include statement. Ensure you provide the correct path to the quickflux.pri file.
    3. Import the module in your QML files.
    # In your .pro file
    include(quickflux/quickflux.pri) # Adjust path as necessary
    import QuickFlux 1.0
  9. Use the QuickFlux project template

    master

    The QuickFlux project template provides a pre-configured architecture for building Flux-based QML applications. It includes templates for ActionTypes, AppActions, Store/StoreWorker, and Adapters, as well as a C++ AppView to initialize the QML environment and listen to messages from the AppDispatcher.

    To use the template:

    1. Copy the template folder to your workspace.
    2. Rename app.pro to your desired project name.
    3. Start coding using the provided structure.
  10. Install QuickFlux using qpm

    master

    If you use the qpm package manager, you can install QuickFlux by running the following command. After installation, include the vendor directory in your .pro file to make the library available to your project.

    $ qpm install com.github.benlau.quickflux
    # In your .pro file
    include(vendor/vendor.pri)
  11. Define action types using ActionTypes.qml

    master

    In QuickFlux, ActionTypes is a singleton component used as a constant table to store all available action types. This centralizes the API and makes it easy for developers to discover available actions. It is implemented using KeyTable where each property represents a unique action key.

    Best Practices:

    • Do not name actions based on UI events (e.g., removeItemButtonClicked).
    • Instead, name them based on user intent (e.g., askToRemoveItem) or the intended outcome (e.g., removeItem).
    • Use prefixes for scoping if necessary (e.g., itemRemove).
    pragma Singleton
    import QtQuick 2.0
    import QuickFlux 1.0
    
    KeyTable {
        property string addTask;
        property string setTaskDone;
        property string setShowCompletedTasks;
    }
  12. Install QuickFlux with CMake

    master

    To integrate QuickFlux into a CMake-based project, use ExternalProject_Add to fetch and build the library. You must link the library directories, include the headers, and add a dependency to your target.

    Important Notes:

    • If you are using a local source instead of a remote Git repository, use SOURCE_DIR instead of GIT_REPOSITORY.
    • On some platforms, you may need to add "-DCMAKE_PREFIX_PATH=<your Qt install path>" to CMAKE_ARGS.
    • Ensure your C++ code calls registerQuickFluxQmlTypes();.
    • If you encounter QML linking issues, ensure you link the Qt5:: modules after linking quickflux.
    include(ExternalProject)
    
    ExternalProject_Add(QuickFlux
    	PREFIX "${PROJECT_BINARY_DIR}/QuickFlux-build"
    	GIT_REPOSITORY "https://github.com/benlau/quickflux.git"
    	CMAKE_ARGS "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
    	                "-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/QuickFlux"
    	                "-DCMAKE_INSTALL_LIBDIR=${PROJECT_BINARY_DIR}/QuickFlux/lib"
    	                "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
    )
    
    link_directories("${PROJECT_BINARY_DIR}/QuickFlux/lib")
    include_directories("${PROJECT_BINARY_DIR}/QuickFlux/include/quickflux")
    
    # ...
    
    add_dependencies(<your target> QuickFlux)
    target_link_libraries(<your target> debug quickfluxd optimized quickflux)