Kube::Fabric

“Kube is a modern communication and collaboration client built with QtQuick on top of a high performance, low resource usage core. It provides online and offline access to all your mail, contacts, calendars, notes, todo’s and more. With a strong focus on usability, the team works with designers and UX experts from the ground up, to build a product that is not only visually appealing but also a joy to use.”

For more info, head over to: kube.kde.org

One of the key features of QML is to be able to describe the UI in a declarative fashion, resulting in code that closely represents the visual hierarchy.
This is nice for writing UI’s because you can think very visually and then just turn that into code.
A couple of trial and error cycles later you typically end up with a result that reflects approximately what you sketched out on paper.

However, interactions with the UI are also handled from QML, and this is where this becomes problematic. User interactions with QML result in some signals or API calls on some component, typically some signal handler, and that’s where your application code takes over and tries to do something useful with that interaction. Perhaps we trigger some visual change, or we call a function on a controller object that does further work. Because we try to encapsulate and build interfaces we might build an API for the complete component that can then be used by the users of the component to further interact with the system.
This can quickly get complex with a growing number of interactions and components.

The straightforward implementation is to just forward the necessary API through your components, but that creates a lot of friction in the development process. A simple move of a button may result in drastic changes where that button is within the visual hierarchy, and if we have to forward the API for that we end up writing a lot of boilerplate code for what ought to be a trivial change.

Item {
    id: root
    property signal reply
    Button {
        onClicked: root.reply()
    }
}

One alternative approach is to abuse the QML Context to avoid forwarding the API (so we just call to some magic component id), but this results in components that are not self-contained and that just call to some magic “singleton” objects to communicate with the rest of the application.
Not great in my opinion.

Item {
    id: root
    Button {
        onClicked: applicationController.reply() //Let's hope applicationController is defined somewhere
    }
}

The approach we’ve chosen in Kube is to introduce a mechanism that is orthogonal to the visual hierarchy. Kube::Fabric is a simple messagebus where messages can be published and subscribed to. A clicked button may for instance post a message to the bus that is called “Kube.Messages.reply”. This message may then be handled by any component listening on the messagebus. Case in point; the main component has a handler that will open a composer and fill it with the quoted message so you can fill in your reply.

So the “Fabric” creates a separate communication plane that “weaves” the various UI components together into something functional. It allows us to concentrate on the user experience when working on the UI without having to worry how we can eventually wire up the component to the other bits required (and it gives us an explicit point where we interconnect parts over the fabric), and thus allows us to write cleaner code while moving faster.

Item {
    id: root
    Button {
        onClicked: Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": model.mail, "isDraft": model.draft})
    }
}

API vs. Protocols

The API approach tightly couples components together and eventually leads to situations where we i.e. have multiple versions of the same API call because we’re dealing with some new requirements but we have to maintain compatibility for existing users of the API. This problem is then further amplified by duplicating API’s throughout the visual hierarchy.

The Fabric rather builds a protocol approach. The fabric becomes the transport layer, the messages posted form the protocol. While the messages form the contract and thus also require that no parameter is removed or changed, they can be expanded without affecting existing users of the message.
New parameters don’t bother any existing users and thus allow for much more flexibility with little (development) overhead.

Other applications

Another part where we started to use the fabric extensively is for notifications. A component that listens for notifications from Sink (Error/Progress/Status/…), simply feeds those notifications into the fabric, allowing various UI components to react to those notifications as a appropriate. This approach allows us to feed notifications from a variety of sources into the system, so they can be dealt with in a uniform way.

Code

In actual code it is used like this.

Posting a message from QML:

 Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": model.mail, "isDraft": model.draft})

Posting a message from C++ (message being a QVariantMap):

Fabric::Fabric{}.postMessage("errorNotification", message);

Listening for messages in QML:

Kube.Listener {
    filter: Kube.Messages.reply
    onMessageReceived: kubeViews.openComposerWithMail(message.mail, false)
}

Lookout

The fabric is right now rather simplistic and we’ll keep it that way until we see actual requirements for more. However, I think there is interesting potential in separating message-buses by assigning different fabrics to different components, with a parent fabric to hold everything together. This would enable components to first handle a message locally before resorting to a parent fabric if necessary. For the reply button that could mean spawning an inline-reply editor instead of switching the whole application state into compose mode.

Author: cmollekopf

Christian Mollekopf is an open source software enthusiast with a special interest in personal organization tools. He started to contribute actively to KDE in 2008 and currently works for Kolab Systems leading the development for the next generation desktop client.

2 thoughts on “Kube::Fabric”

Leave a comment