How to avoid C++ models in Qt and why you should care

How to avoid C++ models in Qt and why you should care

By GT

Many tools rely on web technologies like JavaScript and HTML for mobile app development. But a web view does not offer the performance, features and user experience of native apps.

While the Qt framework is C++ based, you can also code with QML and JavaScript. In fact, you can create full apps without even touching C++.

This post shows how to save up to 80% of code by choosing QML as your main language. It holds many best practices and tips for creating QML-driven apps that offer the same performance as pure Qt C++ applications. This comprehensive QML development guide summarizes all the knowledge we gathered from creating more than 100 QML-based mobile apps for enterprise customers, startups and own projects.

By reading this guide, you will learn how to avoid C++ models in Qt and develop mobile apps using QML and JavaScript only. These are the main topics covered:

The Qt Architecture: Why use QML for iOS or Android Apps?

In case you have never heard of Qt: It is a powerful and feature-rich C++ framework used in many different industries. It supports development of apps for Desktop, mobile platforms and embedded systems – all from a single code base.

Qt - Supported Platforms

 

One of the best things about Qt is QML, a reactive programming language (sometimes also referred a declarative language) that extends JavaScript. It allows to create stunning apps that save up to 90% of code compared to native development for iOS or Android. With less code and a single code base for iOS, Android and Desktop, your maintenance costs decrease dramatically. You can release updates to different platforms faster and have one team sharing the knowledge.

Custom Rendering and QML Item Trees

In contrast to many other cross-platform solutions, Qt offers an architecture that does not rely on native platform rendering or a web view. It moves all controls and the rendering into the app itself. To show items on the device screen, it only requires a native view with a canvas to draw onto.

qt-architecture-renderingThis architecture breaks away from the so-called “cookie cutter” apps that have been common in the past. Instead of the native platform renderer, Qt uses app-driven custom rendering. This allows for a unified renderer and app experience across all supported platforms.

But don’t let the fact that Qt relies on C++ mislead you. Since the introduction of QML and JavaScript for rapid UI development, you can create item trees in a customizable and extensible way. You can combine and nest individual QML Items to form a tree that describes your UI. There’s no need to dive into complex C++ code!

This is how the code for a simple QML app with a “Hello World!” page looks like:

import Felgo 3.0
import QtQuick 2.0

App {
  NavigationStack {
    Page {
      title: "My First App"
      
      AppText {
        text: "Hello World!"
        anchors.centerIn: parent
      }
    }
  }
}

helloworld-qml-app

Native Performance and Compilation of View Code

In the background, each QML Item uses a performant native C++ implementation. When the Qt runtime parses your QML code, it creates all QML Items as C++ objects. With your QML markup and JavaScript logic, you can control and combine them. This makes it easy to build your UI.

There’s no need to create your UI with C++ widgets. Apps created with QML offer the same performance. Qt compiles your QML tree as soon as it parses the code the first time. You get this Just-in-Time (JIT) compilation of QML apps out-of-the-box.

qt-qml-compilation-jit-aot

With the Qt Quick Compiler, you can also compile your QML code Ahead-of-Time (AOT). It transforms your QML and JavaScript directly into bytecode. This architecture makes apps powered by QML super fast and performant.

Qt MVC: Models and Views in QML

For most apps, it is a basic task to populate UI views based on some application data. Qt offers a built-in separation into model, view and delegate components. The following example visualizes a set of data records in a list:

import Felgo 3.0
import QtQuick 2.0

App {
  // data model
  ListModel {
    id: fruitModel
    
    ListElement {
      name: "Banana"
      cost: 1.95
    }
    ListElement {
      name: "Apple"
      cost: 2.45
    }
    ListElement {
      name: "Orange"
      cost: 3.25
    }
  }
  
  // list page
  NavigationStack {
    Page {
      title: "List Page"
      
      AppListView {
        anchors.fill: parent
        model: fruitModel
        delegate: SimpleRow {
          text: name
          detailText: "cost: "+cost
        }
      } // AppListView
    } // Page
  } // NavigationStack
} // App

The AppListView presents you with a scrollable list view. For each ListElement in your ListModel, the view instantiates the delegate Item to visualize each element in your list. In this case, a SimpleRow item represents each list element.

listview-model-delegate-qml-app

The ListModel type is actually a C++ QAbstractListModel implementation exposed to QML. The above code does not only create a UI tree of C++ objects. It also handles the list data in a C++ data structure – pretty nice!

It is very fast to develop user interfaces this way. And as C++ is actually used under the hood, there is no performance drawback. Still, for long-running, heavy-weight and data-intense calculations it makes sense to move away from QML. In such cases, C++ will outperform JavaScript. For more examples when mixing both languages is worth the extra effort, keep on reading this article. We’ve got you covered with details how to combine C++ code with QML as well!

Why Choose a QML ListModel instead of a Qt C++ Model?

When using QML, many Qt developers tend to believe they should at least code the model in C++. This is not the case. QML is optimized to integrate seamlessly with C++.

All QML code gets compiled to native code with native performance. And when using the Qt Quick Compiler, this already happens during compilation of the app. The QML ListModel offers a simple API and perfectly works together with view types like ListView, GridView or Repeater.

QML Best Practice: Use REST Services and JSON for your ListView

There is another big advantage of using QML and JavaScript as your main coding language: It is super easy to work with REST services and JSON data.

The application logic for QML gets written in JavaScript. QML thus has built-in support to handle variant data types like JSON structures. With a simple HTTP request, you can fetch JSON data and use it in your views.

The following code snippet retrieves and shows a list of todo entries from a REST service:

import Felgo 3.0
import QtQuick 2.0

App {
  
  // on app start: fetch data from REST api
  Component.onCompleted: {
    HttpRequest.get("https://jsonplaceholder.typicode.com/todos")
    .timeout(5000)
    .then(function(res) { dataFetched(res.body) })
    .catch(function(err) { console.err("Fetch failed:"+err) });
  }
  
  // dataFetched gets called on success of http request
  function dataFetched(jsonData) {
    listView.model = jsonData // set retrieved json data as model for list
  }
  
  // list page
  NavigationStack {
    Page {
      title: "Todo List"
      
      AppListView {
        id: listView
        anchors.fill: parent
        
        delegate: SimpleRow {
          text: modelData.title
        }
      }
    }
  }
}

Instead of a ListModel, QML also allows you to assign JSON data structures as the model. This is very convenient when working with REST services. You can directly use the retrieved JSON result to display your data.

rest-service-json-todolist-app

Drawbacks of Using JSON data for the ListView Model

A JSON structure is no ListModel and thus no QAbstractListModel implementation. You do not have the benefits of a performant C++ model in this case. Also, a JSON array is a variant type. The list can thus not expect a defined data structure to create its elements.

This results in performance and usability drawbacks. For example, when you change or replace the JSON data. The view then parses the whole model again and redraws all items from scratch. This full redraw can take a while and is noticeable by the user. You also lose the current scroll position and advanced features like transition animations are not supported.

At this point, you could think: Let’s create a ListModel and fill it with the JSON data. While this solves the performance problem, another issue quickly pops up: How do you update the model when you fetch new data from the API at a later point?

If you re-create the ListModel from scratch, the view again requires a full redraw. To avoid this, we could compare each entry of the latest JSON data with the current state of the ListModel. This allows to only synchronize changed or new data entries to the model.

But such a manual synchronization requires a lot of extra effort. It also comes with some overhead, especially when you start to compare large data sets. JavaScript and QML are not well suited to perform such data-intense operations.

But don’t worry: The next section introduces a QML component that covers exactly this synchronization. The JsonListModel is a special ListModel implementation that can handle JSON data.

How Does the JsonListModel Work?

The JsonListModel offers an easy way to transform JSON data into a QML ListModel for usage with e.g. an AppListView. This is how you can use the model for our todo list example:

import Felgo 3.0
import QtQuick 2.0

App {
  
  // on app start: fetch data from REST api
  Component.onCompleted: {
    HttpRequest.get("https://jsonplaceholder.typicode.com/todos")
    .timeout(5000)
    .then(function(res) { dataFetched(res.body) })
    .catch(function(err) { console.err("Fetch failed:"+err) });
  }
  
  // dataFetched gets called on success of http request
  function dataFetched(jsonData) {
    listView.jsonData = jsonData // set retrieved json data as model for list
  }
  
  // list page
  NavigationStack {
    Page {
      title: "Todo List"
      
      AppListView {
        id: listView
        anchors.fill: parent
        
        // property for json data, used as source for JsonListModel
        property var jsonData: []
        
        // use JsonListModel as model
        model: JsonListModel {
          source: listView.jsonData
          keyField: "id"
          fields: ["id", "title"]
        }
        
        // delegate
        delegate: SimpleRow {
          text: title
        }
      }
    }
  }
}

The JsonListModel holds a local copy of the specified JSON data. Whenever the JSON source changes, the data gets compared to the local copy of the list model. To identify each unique data record, it is important to specify the keyField of your data objects. After diffing the old and new data sets, the JsonListModel applies all detected changes individually. It thus synchronizes the model with the changed JSON data step by step.

qsyncable_jsonlistmodel_architecture

The JsonListModel type implements the full QML ListModel API and fires separate events for all changes. The list view can thus only update relevant entries or apply transition animations. This is super useful, as you can e.g. fetch new data and simply replace the old JSON. The JsonListModel will detect all changes, and the ListView updates changed items accordingly – without a full redraw.

Benefits of JsonListModel

You thus get a much better performance and scrolling stays smooth when the list updates:

qsyncable_jsonmodel_scroll qsyncable_jsonlistmodel_scroll
Json Model: The list jumps to the top after a model update. JsonListModel: The list keeps its scroll position.
(the GIF only jumps to the top when it restarts)

To summarize, with the JsonListModel you can:

  • Fetch JSON data from REST APIs with QML and JavaScript.
  • Pass your JSON to the model, which synchronizes the data to a ListModel and prepares it for usage in your view.
  • Show the model data in your QML view, which now only updates items that changed.

You do not require to create a custom model in C++ when working with REST APIs and JSON data. The JsonListModel itself is your C++ model. It is fully usable from QML and can work with JSON objects of any format:

qsyncable_jsonlistmodel_cpp_qml

Apart from list views, the JsonListModel also supports the GridView and Repeater types to display model data. It is based on QSyncable, a syncable C++ model implementation by Ben Lau. You can find the full project on GitHub.

The JsonListModel is also available with the free Felgo SDK. The Felgo Apps module used in the above example holds many such useful components. Felgo focuses on making mobile app development with Qt as easy as possible. For example, the HttpRequest type for networking or UI types like NavigationStack are also part of the SDK.

Dynamically Sort or Filter Data with SortFilterProxyModel

To sort and filter list models, C++ Qt offers the QSortFilterProxyModel class. In the same way, you can use the SortFilterProxyModel QML type with any list model in QML. It also works in conjunction with the JsonListModel:

import Felgo 3.0
import QtQuick 2.0

App {
  Page {
    id: page

    // property with json data
    property var jsonData: [
      {
        "id": 1,
        "title": "Apple",
        "type": "Fruit"
      },
      {
        "id": 2,
        "title": "Ham",
        "type": "Meat"
      },
      {
        "id": 3,
        "title": "Bacon",
        "type": "Meat"
      },
      {
        "id": 4,
        "title": "Banana",
        "type": "Fruit"
      }
    ]

    // list model for json data
    JsonListModel {
      id: jsonModel
      source: page.jsonData
      keyField: "id"
      fields: ["id", "title", "type"]
    }

    // SortFilterProxyModel for sorting or filtering lists
    SortFilterProxyModel {
      id: sortedModel
      // use the Component.onCompleted handler to configure SortFilterProxyModel for JsonListModel
      Component.onCompleted: sourceModel = jsonModel
      // add a sorter to sort the list by type
      sorters: StringSorter { id: typeSorter; roleName: "type"; ascendingOrder: true }
    }

    // list view
    AppListView {
      anchors.fill: parent
      model: sortedModel
      delegate: SimpleRow {
        text: model.title
      }
      section.property: "type"
      section.delegate: SimpleSection { }
    }

    // Button change the sorting order
    AppButton {
      anchors.horizontalCenter: parent.horizontalCenter
      anchors.bottom: parent.bottom

      text: "Change Order"
      onClicked: typeSorter.ascendingOrder = !typeSorter.ascendingOrder
    }
  } // Page
}

This example shows a list of different fruit and meat entries. The list entries get sorted by type with the SortFilterProxyModel. You can also change the sorting order by pressing the AppButton.

qml-json-sortfilterproxymodel

Similar to JsonListModel, the SortFilterProxyModel is a C++ type exposed to QML. So for this simple QML example, all data and model-related tasks are actually performed by C++ types. When working with REST services and JSON data, you can fully handle all model and view code in QML!

Offline Caching, File Downloads and Native Integrations for your Mobile App

It is very simple to create stunning views and handle application data with QML. But why stop there? Let’s take it even one step further!

Control Your Application from QML

As QML and C++ integrate seamlessly, it is possible to expose any native feature for usage with QML. Available Qt components already allow to work with sensors, SQLite databases and much more. And the Felgo SDK offers lots of additional features. For example to fetch assets at runtime with the DownloadableResource item. It allows you to keep the initial app size small and dynamically load content if required – all with QML.

You can create your own C++ components and register them with QML as well. For iOS and Android apps, you can even add components that

  • weave-in native iOS code with Obj-C
  • or run Java Android code over JNI.

Please note that such native code requires custom implementations for each platform then. Felgo already has you covered with the NativeUtils component. It offers lots features like native dialogs, camera and image picker or sharing. The SDK also comes with many Felgo Plugins that integrate 3rd party frameworks for:

  • server-triggered or local notifications
  • in-app purchases and monetization
  • analytics and crash reporting
  • Firebase authentication and database
  • and much more.

There is no limit to what you can do – it’s possible to manage your full app logic purely with QML. You can even perform calculations in different threads with a WorkerScript.

App Example with JsonListModel and Offline Caching

Mobile apps have to work properly even without internet access. The Storage component allows to cache data in a simple key-value store. It can save full JSON structures as well. The data gets serialized and converted to string format automatically.

You can thus use the Storage to initialize the JsonListModel with previously cached JSON data. This way, your app can show the latest state of the data even if a fetch request fails:

import Felgo 3.0
import QtQuick 2.0

App {
  
  // on app start: fetch data
  Component.onCompleted: {
    fetchData()
  }
  
  // list page
  NavigationStack {
    Page {
      title: "Todo List"
      
      AppListView {
        id: listView
        anchors.fill: parent
        
        // property for json data, used as source for JsonListModel
        property var jsonData: []
        
        // use JsonListModel as model
        model: JsonListModel {
          source: listView.jsonData
          keyField: "id"
          fields: ["id", "title"]
        }
        
        // delegate
        delegate: SimpleRow {
          text: title
        }
      }
    }
  }
  
  // storage for caching
  Storage {
    id: cache
  }
  
  // fetch data from cache or from api
  function fetchData() {
    // check cached value first
    var cachedData = cache.getValue("todos")
    if(cachedData)
      listView.jsonData = cachedData
    
    // load new data from api
    HttpRequest.get("https://jsonplaceholder.typicode.com/todos")
    .timeout(5000)
    .then(function(res) { dataFetched(res.body) })
    .catch(function(err) { console.err("Fetch failed:"+err) });
  }
  
  // dataFetched gets called on success of http request
  function dataFetched(jsonData) {
    // cache new data before updating the model 
    cache.setValue("todos", jsonData)
    listView.jsonData = jsonData // set retrieved json data as model for list
  }
}

In addition to fetched data, you can locally cache new entries as well. As soon as the app has internet connection again, save them to your server and remove the local entries.

We prepared a full demo app for you, which allows to browse and create todo list entries. You can find the full example with caching, local draft entries, list filtering and more on GitHub:

todolist-demo

When to Use C++ and When to Use QML

Application Development with QML is simple and powerful. But Qt C++ can be more performant, offers many features and is less error-prone. So it’s important to know when to use which.

Coding in QML has several advantages over development with C++:

  • Coding with QML + JavaScript is very easy to learn and allows to reduce the required amount of code a lot.
  • Language concepts like states, signals or property bindings are a huge time-saver.
  • QML makes adding animations simple. You can animate every property of your QML types with simple Animation components.
  • QML is extensible and flexible. For example, you can extend objects with new properties and features in-line. No need to create a new re-usable type for small extensions.
  • The QML Rendering Engine offers a great performance. The renderer uses C++ Qt and relies on a hardware accelerated scene graph. This makes it fast enough to power even high-performance games.

Qt app development with C++ has advantages as well. For some scenarios you need features that are only available with Qt C++. Also, C++ is fast and type-safe. This allows to provide the best possible performance for long-running and data-intense calculations.

For these examples, you would choose C++ over QML:

  • Native C++ code is the right choice for data-intense operations. It will outperform interpreted QML/JavaScript code.
  • C++ code is type-safe and compiled into object code. For parts where stability and security are important, using C++ helps to make your app less error-prone.
  • The Qt C++ components offer different and in some cases more features than the QML types. For example, advanced networking features.
  • It is also possible to mix C++ with native code for Android (over JNI) or iOS (Obj-C or Swift). This allows to provide such native functionality for QML as well.
  • You might need a 3rd party library that is only available in C++, or have existing code in C++ which you’d like to reuse.

It is possible to code most parts of the application without the need to touch C++. The Felgo SDK and Qt have many QML components available. For a starting point to integrate Qt C++ or make native features available for QML, you can see the guide How to Expose a Qt C++ Class with Signals and Slots to QML.

QML MVC: App Architecture Best Practices and Design Patterns

With all this knowledge and QML benefits in mind, we will use QML for more than only view code. We handle data-related tasks and application logic as well. It thus becomes very important to think about a clean app architecture and component structure for such QML-driven apps. We want to keep our code clean, maintainable and extensible.

Why care about separation of concerns?

The easiness and flexibility of QML can lead to problems. When using signals and property bindings a simple value change can affect many components. Properties that rely on other properties update automatically. They handle the signal and update their value as well. This is even possible across multiple QML Items:

qml-mvc-dependency-graph-simple

 

This doesn’t look complex now. But imagine that we add a few new components with different properties and cross-dependencies:

qml-mvc-dependency-graph-complex

Different connections between all the items form. You can no longer say for sure what effect a single property change may have for directly or indirectly connected components. In the worst case, this can result in circular dependencies or loops. If you face such issues, this is an indicator for bad component architecture.

This problem gets bigger and bigger the more your project grows. Each component should thus have a clean interface and only manage its own data. This is what separation of concerns means.

Design Patterns like MVC, MVVM or Flux (React Native)

A bad component architecture can quickly lead to unwanted side-effects or corrupted data. Imagine lots of signals firing and event handlers running in unknown order. Your code at different parts of the app changes data seemingly random or with duplicated code. This is a nightmare for debugging, maintenance or refactoring.

For most design patterns, the golden rule is: Keep the code that displays data (view) separate from the code that reads or modifies data (model).

Model-View Separation in QML

Each content view of a Felgo app is usually composed as a Page. The Page type is a view controller, which provides and displays data for the user. By introducing a DataModel component, we can move data handling away from the view:

qml-model-view-separation-simple

The DataModel is your central storage for application data. Pages can only access application data with the DataModel. It manages your data in a way that makes sense for your use-case and views. For different views and use-cases, the usage of several DataModel components is possible.

For the app logic, you will also add many functions and signal handlers to your view or model. If not done properly, this results in a lot of fragmented code spread across view items. Once your application gets more complex, it also gets more difficult to maintain your code clean and testable. It gets hard to decide where to perform which action, and duplicate code spread across pages is inevitable.

With the above architecture, the data flow between model and page is bi-directional. This means, pages do not only show model data, but can also write to the model directly. Modern application frameworks like React Native use a different approach. For example, the Flux architecture designed by Facebook favors an unidirectional data flow.

Create a clean Data Flow: QML Architecture inspired by Flux

With Flux, each user interaction propagates the action through a central dispatcher. The dispatcher forwards the action to various stores that hold application data and business logic:

mvc-flux-model-view-logic-separation

You can read more about a quite sophisticated implementation of such a pattern for QML in this post by Ben Lau: Revised QML Application Architecture Guide with Flux

It’s hard to say if the overhead of a complex solution is worth the effort. For most mobile app projects, a more relaxed implementation with the same advantages is sufficient.

Simple Flux-like Example for Model View Controller Separation in QML

To create a one-directional data flow, we can also take advantage of QML’s built-in signal mechanism. We can ensure a loose coupling between different components. You can find a basic example architecture that applies this pattern in this guide: Separation of Model, View and Logic Code in your Qt App using QML

It introduces a Logic component to dispatch actions from Pages to the DataModel:

qml-mvc-model-view-logic-separation

Doing a lot of work in QML can quickly result in unreadable and fragmented code. With a clean component architecture and clear responsibilities, you can avoid this. Using an architecture like above helps to provide a consistent app structure that simplifies testing and refactoring.

With the principles shown in this guide, you can create well-structured apps that handle app logic, data-related tasks and view code purely with QML.

 

Not sure how to get started?
Felgo offers many demos and examples you can use to build a first prototype of your project:

vplay-demos-and-examples

We can also help you out with Qt Trainings and App Development Workshops to get your team started!

vplay-qt-training-consulting-services

 

The Felgo SDK is free to use, so make sure to check it out!

 

Do you already have a project or app idea in mind?
The Felgo team is here to support you! We can cover the whole process from the first idea up to the release of your app in the stores. Take advantage of our Mobile App Development Services and join the ranks of satisfied Felgo customers:

vplay-customers

 

If you enjoyed this post, feel free to share it on Facebook or Twitter.

More Relevant App Development Resources

The Best App Development Tutorials & Free App Templates

All of these tutorials come with full source code of the mobile apps! You can copy the code to make your own apps for free!

App Development Video Tutorials

Make Cross-Platform Apps with Qt: Felgo Apps

How to Add In-App Chat or Gamification Features to Your Mobile App

How to Make a Mobile App with Qt Quick Designer (QML Designer) & Felgo

 

Voted #1 for:

  • Easiest to learn
  • Most time saving
  • Best support

Develop Cross-Platform Apps and Games 50% Faster!

  • Voted the best supported, most time-saving and easiest to learn cross-platform development tool
  • Based on the Qt framework, with native performance and appearance on all platforms including iOS and Android
  • Offers a variety of plugins to monetize, analyze and engage users
FREE!
create apps
create games
cross platform
native performance
3rd party services
game network
multiplayer
level editor
easiest to learn
biggest time saving
best support