Trusted Software Excellence across Desktop and Embedded
Take a glance at the areas of expertise where KDAB excels ranging from swift troubleshooting, ongoing consulting and training to multi-year, large-scale software development projects.
Find out why customers from innovative industries rely on our extensive expertise, including Medical, Biotech, Science, Renewable Energy, Transportation, Mobility, Aviation, Automation, Electronics, Agriculture and Defense.
High-quality Embedded Engineering across the Stack
To successfully develop an embedded device that meets your expectations regarding quality, budget and time to market, all parts of the project need to fit perfectly together.
Learn more about KDAB's expertise in embedded software development.
Where the capabilities of modern mobile devices or web browsers fall short, KDAB engineers help you expertly architect and build high-functioning desktop and workstation applications.
Extensible, Safety-compliant Software for the Medical Sector
Create intelligent, patient-focused medical software and devices and stay ahead with technology that adapts to your needs.
KDAB offers you expertise in developing a broad spectrum of clinical and home-healthcare devices, including but not limited to, internal imaging systems, robotic surgery devices, ventilators and non-invasive monitoring systems.
Building digital dashboards and cockpits with fluid animations and gesture-controlled touchscreens is a big challenge.
In over two decades of developing intricate UI solutions for cars, trucks, tractors, scooters, ships, airplanes and more, the KDAB team has gained market leading expertise in this realm.
Build on Advanced Expertise when creating Modern UIs
KDAB assists you in the creation of user-friendly interfaces designed specifically for industrial process control, manufacturing, and fabrication.
Our specialties encompass the custom design and development of HMIs, enabling product accessibility from embedded systems, remote desktops, and mobile devices on the move.
Legacy software is a growing but often ignored problem across all industries. KDAB helps you elevate your aging code base to meet the dynamic needs of the future.
Whether you want to migrate from an old to a modern GUI toolkit, update to a more recent version, or modernize your code base, you can rely on over 25 years of modernization experience.
KDAB offers a wide range of services to address your software needs including consulting, development, workshops and training tailored to your requirements.
Our expertise spans cross-platform desktop, embedded and 3D application development, using the proven technologies for the job.
When working with KDAB, the first-ever Qt consultancy, you benefit from a deep understanding of Qt internals, that allows us to provide effective solutions, irrespective of the depth or scale of your Qt project.
Qt Services include developing applications, building runtimes, mixing native and web technologies, solving performance issues, and porting problems.
KDAB helps create commercial, scientific or industrial desktop applications from scratch, or update its code or framework to benefit from modern features.
Discover clean, efficient solutions that precisely meet your requirements.
Boost your team's programming skills with in-depth, constantly updated, hands-on training courses delivered by active software engineers who love to teach and share their knowledge.
Our courses cover Modern C++, Qt/QML, Rust, 3D programming, Debugging, Profiling and more.
The collective expertise of KDAB's engineering team is at your disposal to help you choose the software stack for your project or master domain-specific challenges.
Our particular focus is on software technologies you use for cross-platform applications or for embedded devices.
Since 1999, KDAB has been the largest independent Qt consultancy worldwide and today is a Qt Platinum partner. Our experts can help you with any aspect of software development with Qt and QML.
KDAB specializes in Modern C++ development, with a focus on desktop applications, GUI, embedded software, and operating systems.
Our experts are industry-recognized contributors and trainers, leveraging C++'s power and relevance across these domains to deliver high-quality software solutions.
KDAB can guide you incorporating Rust into your project, from as overlapping element to your existing C++ codebase to a complete replacement of your legacy code.
Unique Expertise for Desktop and Embedded Platforms
Whether you are using Linux, Windows, MacOS, Android, iOS or real-time OS, KDAB helps you create performance optimized applications on your preferred platform.
If you are planning to create projects with Slint, a lightweight alternative to standard GUI frameworks especially on low-end hardware, you can rely on the expertise of KDAB being one of the earliest adopters and official service partner of Slint.
KDAB has deep expertise in embedded systems, which coupled with Flutter proficiency, allows us to provide comprehensive support throughout the software development lifecycle.
Our engineers are constantly contributing to the Flutter ecosystem, for example by developing flutter-pi, one of the most used embedders.
KDAB invests significant time in exploring new software technologies to maintain its position as software authority. Benefit from this research and incorporate it eventually into your own project.
Start here to browse infos on the KDAB website(s) and take advantage of useful developer resources like blogs, publications and videos about Qt, C++, Rust, 3D technologies like OpenGL and Vulkan, the KDAB developer tools and more.
The KDAB Youtube channel has become a go-to source for developers looking for high-quality tutorial and information material around software development with Qt/QML, C++, Rust and other technologies.
Click to navigate the all KDAB videos directly on this website.
In over 25 years KDAB has served hundreds of customers from various industries, many of them having become long-term customers who value our unique expertise and dedication.
Learn more about KDAB as a company, understand why we are considered a trusted partner by many and explore project examples in which we have proven to be the right supplier.
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications.
Read more about the history, the values, the team and the founder of the company.
When working with KDAB you can expect quality software and the desired business outcomes thanks to decades of experience gathered in hundreds of projects of different sizes in various industries.
Have a look at selected examples where KDAB has helped customers to succeed with their projects.
KDAB is committed to developing high-quality and high-performance software, and helping other developers deliver to the same high standards.
We create software with pride to improve your engineering and your business, making your products more resilient and maintainable with better performance.
KDAB has been the first certified Qt consulting and software development company in the world, and continues to deliver quality processes that meet or exceed the highest expectations.
In KDAB we value practical software development experience and skills higher than academic degrees. We strive to ensure equal treatment of all our employees regardless of age, ethnicity, gender, sexual orientation, nationality.
Interested? Read more about working at KDAB and how to apply for a job in software engineering or business administration.
Writing mobile apps can be a lot of fun, especially with Qt and QML. But if your background is all about C++ and Qt, writing the entire required stack for a mobile app can be challenging. Adding push notifications, storing information, and having to write that with another language and framework might make you give up. But we got you covered!
This is going to be a series of tutorials on how to build a simple mobile chat application with push notifications. On the server side, it will will have a REST API powered by Cutelyst, featuring a database with async communication for storing conversations. Additionally, it will communicate with Firebase to send the notifications.
As you might have already noticed, we are using Qt everywhere (hence, Qt Allstack).
The client application will try to register a nickname. Then, it will connect to a global chat room. For the purpose of simplicity, it’s going to be a single chat room. Once someone sends a message with @some_nick within the string, the push notification will go only to that user app. Thanks to PostgreSQL IPC notifications, we get it in real time.
Setup
I’ll list here what I’m using on this project, but you can also adjust it to your OS or distro:
Kubuntu 21.04
PostgreSQL 13.3
Qt 5.15 from the distro for the backend and Qt 5.15 from online installer for the mobile app to have Qt libraries for Android
The --sever option starts a Cutelyst server listening by default on all addresses at port 3000. --restart will keep an eye on the Cutelyst application and restart the server when it changes, and --app-file specifies where our application file is located.
Open QtCreator and “Open New Project”, selecting the CMakeLists.txt file. Then, on the “Configure” step, choose the “build” directory in which we compiled the application. This way, QtCreator can compile in the same directory.
Now let’s add ASql dependency. In ChatAppBack/CMakeLists.txt, we add after find_package:
find_package(ASqlQt5 0.43 REQUIRED)
Then on src/CMakeLists.txt, make sure our target links to ASql as well:
target_link_libraries(ChatAppBack
Cutelyst::CoreASqlQt5::Core# link to ASqlASqlQt5::Pg# link to ASql Postgres driverQt5::CoreQt5::Network)
We are going to use the PostgreSQL database, as it’s very easy to get started, has great JSON support and, most importantly, it has IPC built-in -- a critical feature for a real-time chat that even commercial players lack.
In order to have the nicest development environment, it’s recommended to create a database user with the same name as your machine login name, and grant it rights to create new databases. That can be done with this command:
sudo-u postgres createuser --createdb$USER
We can now create and manipulate databases without needing a password or changing configuration files. Just issue the following command as your regular user and we will have a new database to use:
createdb chat
ASql comes with a handy tool called migrations; we will use it to have our database versioned. On QtCreator, click to create a New File → Template “General” → “Empty File”. Name the new file db.sql at ChatAppBack/root, with the following content:
-- 1 upCREATETABLE users ( id serial PRIMARY KEY, nick text NOTNULLUNIQUE,data jsonb
);-- 1 downDROPTABLE users;-- 2 upCREATETABLE messages ( id serial PRIMARY KEY, created_at timestampwithtimezoneDEFAULTnow(), user_id integer NOTNULL REFERENCES users(id), msg text NOTNULL);-- 2 downDROPTABLE message;-- 3 upCREATEOR REPLACE FUNCTION messages_notify() RETURNS trigger AS $$
BEGIN PERFORM pg_notify('new_message', json_build_object('id',NEW.id,'msg',NEW.msg,'nick', nick,'created_at',NEW.created_at)::text)FROM users
WHERE id =NEW.user_id;RETURNNEW;END;$$ LANGUAGE plpgsql;CREATE TRIGGER messages_notify
AFTER INSERTON messages
FOR EACH ROWEXECUTEPROCEDURE messages_notify();-- 3 downDROPFUNCTION messages_notify();
As you can see, there are 6 scripts. When version goes from 0 to 3, all up scripts are executed. If you want to rollback a migration, it uses the down scripts.
The scripts create a users table with a unique nick and some extra JSON metadata, then a messages table with each message, the user_id that sent the message, and the time the message was created. Script 3 creates a trigger that will notify all front-end servers (this way we can scale our simple app horizontally) that a new message is available.
ASql migrations can be used from the command line or by writing C++ code that makes use of its class. It’s easier to just issue it:
This will create a table called asql_migrations and track the version under the name “chat”. You can take a look at your tables with:
psql chat
chat=>\d users
Now let’s configure our backend to create a pool of database connections. We will override to postFork(). This method is called for each thread or process that is created. An ASql pool has thread affinity with the thread that created the pool, add in chatapp.h:
Compile and make sure the application restarts properly.
REST API
The REST API will be responsible for creating a new user and posting new messages. We won’t worry about authentication or nice URL APIs. Adding a JWT and properly naming methods is left as an exercise to the reader. So in root.h, we will add:
The first :Local method will create a /users URL end point, since there are no more arguments after Context*. :AutoArgs will know the URL can’t have additional arguments. The special action class, REST, will take care of issuing the methods. It will look for users_METHOD. Once a new HTTP request arrives, it will know to which method to call,
In our case, we are only accepting POST and PUT methods. So if you try to GET/DELETE, it will fail but it will respond with the proper reply if the OPTIONS method is requested. The same applies to messages end point (ie /messages accepting POSTs only).
Now that Cutelyst knows how to route HTTP requests, it’s time to write real code in root.cpp:
#include<apool.h>#include<aresult.h>#include<QDebug>#include<QJsonObject>voidRoot::users_POST(Context*c){const QJsonObject data = c->request()->bodyJsonObject(); ASync a(c);APool::database().exec(u"INSERT INTO users (nick, data) VALUES ($1, $2) RETURNING id",{ data["nick"], data,},[a, c](AResult &result){auto firstRow = result.begin();if(!result.error()&& firstRow != result.end()){// RETURN the new user ID c->res()->setJsonObjectBody({{"id", firstRow[0].toInt()},});}else{qWarning()<<"Failed to create user"<< result.errorString(); c->res()->setStatus(Response::InternalServerError); c->res()->setJsonObjectBody({{"error_msg","failed to create user"},});}}, c);}
When creating a user, we first get the JSON sent by the client. Then, we create a scoped ASync object that must be explicitly captured by the lambda. It’s responsible for telling Cutelyst that it should not send a reply for the client immediately. It will do so when the last ASync object goes out of scope.
Then, a ADatabase object is retrieved from pool, which we'll then call exec(). Notice that we pass a QJsonObject to the database and ASql properly handles it. After the user is created, we get an ID and will use it on our app to send messages. That's not safe! I know:
voidRoot::users_PUT(Context*c){const QJsonObject data = c->request()->bodyJsonObject(); ASync a(c);APool::database().exec(u"UPDATE users SET nick=$1, data=$2 WHERE id=$3",{ data["nick"], data, data["user_id"],},[a, c, data](AResult &result){if(!result.error()&& result.numRowsAffected()){ c->res()->setJsonObjectBody({{"id", data["user_id"]},});}else{qWarning()<<"Failed to create user"<< result.errorString(); c->res()->setStatus(Response::InternalServerError); c->res()->setJsonObjectBody({{"error_msg","failed to create user"},});}}, c);}
The PUT method is about updating our already-created user. Later, we can use this to send the Firebase token that the server will need to send push notifications.
voidRoot::messages_POST(Context*c){const QJsonObject data = c->request()->bodyJsonObject();const QString msg = data["msg"].toString(); ASync a(c);APool::database().exec(u"INSERT INTO messages (user_id, msg) VALUES ($1, $2) RETURNING id",{ data["user_id"], msg,},[a, c, msg](AResult &result){auto firstRow = result.begin();if(!result.error()&& firstRow != result.end()){// RETURN the new message ID c->res()->setJsonObjectBody({{"id", firstRow[0].toInt()},});}else{qWarning()<<"Failed to create message"<< result.errorString(); c->res()->setStatus(Response::InternalServerError); c->res()->setJsonObjectBody({{"error_msg","failed to create message"},});}}, c);}
The message's POST method is very similar, except we select which values we want from the JSON object. For a careful observer, it’s passing a QJsonValue and not an int or QString. This method also returns the new message ID. This way, our client can ignore its own message when it gets notified.
Websockets
An important part of any chat application is working in real-time. We can do this with WebSockets. The client app will stay connected to receive new messages. As soon as the database says there is a new one, we send it to the connected clients. In root.h, we add:
The :Path(‘ws’) tells Cutelyst to call this method when the URL is /ws, even though the method name is websocket. :AutoArgs now sees the QString argument. So, the end point is /ws/<user_id>. Finally, QHash will keep a pointer to the Context objects, using their user_id as key.
voidRoot::websocket(Context*c,const QString &user_id){if(!c->response()->webSocketHandshake()){ c->response()->webSocketClose(Response::CloseCodeNormal,QStringLiteral("internal-server-error"));return;}if(m_wsClients.contains(user_id.toInt())){ c->response()->webSocketClose(Response::CloseCodeNormal,QStringLiteral("already-logged-in"));return;} m_wsClients.insert(user_id.toInt(), c);connect(c,&Context::destroyed,this,[=]{ m_wsClients.remove(user_id.toInt());});APool::database().exec(uR"V0G0N(
SELECT m.id, m.created_at, u.nick, m.msg
FROM messages m
INNER JOIN users u ON m.user_id=u.id
ORDER BY 2 DESC
)V0G0N",[c](AResult &result){if(result.error()){ c->response()->webSocketClose(Response::CloseCodeNormal,QStringLiteral("error-getting-msgs"));}else{ c->response()->webSocketTextMessage(QJsonDocument(result.jsonArray()).toJson());}}, c);}
Our new method needs to change the protocol HTTP → WebSocket. Then, it checks if the user is connected and drops the connection when logged in. It also removes the client connection when the client goes away. Last, it gets all server messages. Notice that the ASync class is not needed here. The WebSocket protocol is async by definition. So, now you need to work on its signals.
Another important point here is that the last parameter of ADatabase::exec() is a QObject pointer. This allows for cancellation of the query when the object gets destroyed, as well as not calling this lambda at all, which would crash if called with an invalid Context pointer.
Postgres Notifications
Now we need to listen for database notifications, which should sound when a new message is added. For this, we will need a dedicated DB connection for monitoring for states changes, so our clients don’t miss a notification due a broken connection with the database. On root.h, we will add a postFork() override. We won’t use the other one, as we need access to the QHash containing our connected clients. So, it’s easier to add it on root.h:
boolpostFork(Application *app)override;
Notice the controller’s postFork() take an application pointer, then add the following code on root.cpp:
Here, we create a named lambda to subscribe to “new_message” notification. We do this in a lambda because, if the connection is closed, the subscription will be lost. So, we'd need to subscribe again. Once we get a new message, we send it to all of our connected clients. If the database connections is lost, we close the connection with all our clients. Notice that we don’t release their pointers from m_wsClients because that will happen with the code added on the WebSocket connection.
The backend code now only misses sending a notification with Firebase, which will be added later.
Client App
The client app now needs to talk to our backend server. It’s going to be a simple stack application with material design. When it’s done, we will be adding support for push notifications. The code that I currently have working with this was created on top of QMake. So to avoid issues, we won’t be using CMake for the mobile app.
Create a new project in Qt Creator. I always open a new Qt Creator, as switching between projects is slower than switching windows. Choose “Application (Qt Quick)” → “Qt Quick Application – Empty”, then name it “ChatApp” and choose qmake build system.
Since we chose the empty template, we need to manually define the Qt Quick Controls 2 theme. To do so, right click on the qml.rc resource and choose “Add new…” → “General” → “Empty file”, then name it qtquickcontrols2.conf and add the following content:
[Controls]
Style=Material
We also need to set the application and organization values, so we can store settings. Add the following to main.cpp:
The client app will have only 3 QML files, 2 of which you can now create with Qt Creator as Qt → QML File named PageUser.qml and PageMessages.qml. The third one will be main.qml, which already exists.
We'll start with the main.qml code, which will be like this:
Here we have a Settings object to hold our registered user_id, which defaults to zero when not registered, our server, nick, and fullname. When the app starts, if not registered, it shows the PageUser.qml for registration. Once registered, we will see PageMessages.
For PageUser, we will have:
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import com.kdab 1.0
Page{title:settings.user_id===0?"Create User":"Update User"ColumnLayout{anchors.fill:parentanchors.margins:10Label{text:"Server:Port"}TextField{Layout.fillWidth:trueid:serverFtext:settings.server}Label{text:"Nick"}TextField{Layout.fillWidth:trueid:nickFtext:settings.nick}Label{text:"Full Name"}TextField{Layout.fillWidth:trueid:fullnameFtext:settings.fullname}Button{text:settings.user_id===0?"Create":"Update"onClicked:{ var nick = nickF.text
var fullname = fullnameF.text
var server = serverF.text
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { var json = JSON.parse(xhr.responseText)
settings.user_id = json.id
settings.nick = nick
settings.fullname = fullname
settings.server = server
stackView.pop()
} else { console.error("Error creating/updating user: ", xhr.statusText)
}}} xhr.open(settings.user_id === 0 ? "POST":"PUT","http://"+ server +"/users"); xhr.setRequestHeader("Content-Type","application/json"); xhr.send(JSON.stringify({user_id:settings.user_id,nick:nick,fullname:fullname}));}}Item{Layout.fillHeight:true}}}
The important part is the Button::clicked(). It will create a XMLHttpRequest that will POST or PUT, depending on whether we have a user_id. Set the proper Content-Type and send the JSON object with our data. The XMLHttpRequest.onreadystatechange callback will store the returned ID on our Settings object, as well as both nick and fullname.
The server string is also very important, as when testing on the mobile phone you will want to set it to your machine IP:Port instead of localhost:3000.
Now that we have a user_id, we are allowed to leave the PageUser and finally see the chat room. PageMessages.qml will feature the following code:
Here we have a WebSocket object. It'll will try to connect to the end point for WebSockets that we created. Once connected, the TextField will be enabled to send messages. From the WebSocket object, we only care about receiving messages. It could also be used to send messages, but we are sending them with plain HTTP so that you can see that the messages are being pushed by the server itself. The TextField is disabled while sending the message, to avoid sending duplicated messages.
Here is a demonstration video for this blog, in case it would help you to see the steps above being done:
To watch this video on our website please or view it directly on YouTube
The full code for the snippets shown in this blog can be found in GitHub.
Part two of this blog series walks you through adding Firebase. That's another goodie that's in store for you!
About KDAB
Trusted software excellence across embedded and desktop platforms
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
Our hands-on Modern C++ training courses are designed to quickly familiarize newcomers with the language. They also update professional C++ developers on the latest changes in the language and standard library introduced in recent C++ editions.