The Need for Simple & Modular QR Generation in QML
Recently, our designer Nuno Pinheiro needed to generate QR codes for an Android app in QML and started asking around about a simple way to do this. The best existing QML solution was QZXing, a Qt/QML wrapper for the 1D/2D barcode image processing library ZXing. He felt this was too much.
QZXing is quite a large and feature-rich library and is a great choice for something that requires a lot more rigorous work with encoding and decoding barcodes and QR. However, this application wasn’t focused around barcode processing. It just needed to display a few QR codes below its other content; it didn’t need something so heavy-duty. It seemed like too much work to build and link this library and register QML types in C++ if there was something simpler available.
Finding A JavaScript Library to Wrap in QML
There are plenty of minimal QR Code libraries in JS, and JS files can be imported natively in QML. Why not just slap a minified JS file into our Qt resources and expose its functionality through a QML object? No compiling libraries, no CMake, simple setup for a simple task.
My colleague, the one and only Javier O. Cordero Pérez attempted to tackle this first using QRCode.js. He found a few issues, which I’ll let him explain. This is what Javier contributed:
Why Most Browser Libraries Don’t Work With QML
Not all ECMAScript or JavaScript environments are created equal. QML, for example, doesn’t have a DOM (Document Object Model) that represents the contents on screen. That feature comes from HTML, so when a JS library designed for use in the browser attempts to access the DOM from QML, it can’t find these APIs. This limits the use of JS libraries in QML to business logic. Frontend JS libaries would have to be ported to QML in order to work.
In my first approach to using qrcodejs
, I tried using the library from within a QML slot (Component.onCompleted
) and found that QRCode.js
calls document.documentElement
, document.getElementById
, document.documentElement
, and document.createElement
, which are undefined, because document
is typically an HTMLDocument
, part of the HTML DOM API.
I then began attempting to refactor the code, but quickly realized there was no easy way to get the library to use QtQuick’s Canvas element. I knew from past experiences that Canvas performs very poorly on Android, so, being pressed for time and Android being our target platform, I came up with a different solution.
Embedding and Communicating With A Browser View
My second approach was to give QRCode.js
a browser to work with. I chose to use QtWebView
, because on mobile, it displays web content using the operating system’s web view.
To keep things simple, I sent the QRCode’s data to the web page by encoding it to a safe character space using Base64 encoding and passing the result as a URL attribute. This attribute is then decoded inside the page and then sent to the library to generate a QR code on the Canvas. The WebView dimensions are also passed as attributes so the image can be produced at the width of the shortest side.
This is what my solution looked like at this point:
Why You Should Avoid QtWebView On Mobile
If you read the comments in the code, you’ll notice that “due to platform limitations, overlapping the WebView with other QML components is not supported. Doing this will have unpredictable results which may differ from platform to platform.” Additionally, there is so much overhead in loading an embedded site that you can see the exact moment the QR code appears on screen.
Unfortunately for Nuno, QtWebView
is unable to load pages embedded as Qt resources on mobile systems. This is because by default, Qt resources become a part of the app’s binary, which can’t be read by the embedded browser. If the site was stored online or the app hosted its own web server, we could load our resources from there. Since this app didn’t do either of those things, all resources had to be copied into a temporary folder and accessed via the file://
protocol. Even then, the embedded browser would fail to locate or load the resources, making it necessary to inline all of our resources into the HTML for this to work.
As you can see, what started as a simple way to use a JS library on the desktop, quickly became cumbersome and difficult to maintain for mobile devices. Given more time, I would’ve chosen to instead re-implement QRCode.js
's algorithm using QtQuick’s Shapes API. The Shapes API would allow the QR code to be rendered in a single pass of the scene graph.
Fortunately, there’s a better, simpler and more practical solution. I will defer back to Matt here, who figured it out:
Proper JS in QML Solution
I decided to expand on Javier’s idea and try qrcode-svg. This library uses a modified version of QRCode.js and enables creation of an SVG string from the QR Code data.
Here’s an example snipped from the project’s README:
Since the data is SVG, it can be used with QML’s Image
item natively by transforming it into a data URI and using that as the source for the image. There’s no need to write or read anything to disk, just append the string to "data:image/svg+xml;utf8,"
and use that as the source file.
Starting Our Wrapper
We can just wrap the function call up in a QML type, called QR, and use that wherever we need a QR code. Let’s make a ridiculously basic QtObject
that takes a content string and uses the library to produce an SVG:
So, whenever we make a QR
object, the string bound to content
is used to make the SVG and store it in svgString
. Then we can render it in an Image
item:
This is basically effortless and works like a charm.
Finishing Up The Wrapper
Now let’s completely wrap the QRCode constructor, so all the options from qrcode-svg are exposed by our QML object. We just need to set all options in the constructor through QML properties and give all the unrequired properties default values.
While we’re at it, let’s go ahead and connect to onContentChanged
, so we can refresh the SVG automatically when the content changes.
Nice and Easy
With these 45 lines of QML and the minified JS file, we have a QML wrapper for the library. Now any arbitrary QML project can include these two files and generate any QR Code that qrcode-svg can make.
Here I use it to re-generate a QR code as you type the content into a TextInput:
This runs well when deployed on Android, and the image re-renders on content change in under 30 milliseconds, sometimes as low as 7.
Hopefully this code will be useful to those looking for the simplest no-frills method to generate a QR code in QML, and maybe the post can inspire other QML developers who feel like they’re overcomplicating something really simple.
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.
2 Comments
22 - Feb - 2024
Carl Schwan
There is a more proper solution available KDE Prison: https://invent.kde.org/frameworks/prison
It's LGPLv2, has proper QML bindings, has a stable API/ABI, is maintained by two KDE long timers (Sune Vuorela and Volker Krause) and doesn't introduce the usage of a vendored JS lib which might break with the coming QML type compiler.
22 - Feb - 2024
Javier Cordero
Hi Carl,
Thank you for this insight. I had heard of KDE Prison a while ago. Unfortunately, Prison wouldn't have met Nuno's requirement, being written in C++. Remember, it was not about the bindings, he was looking something he wouldn't have to compile. Third party C++ libraries can be tricky when targeting Android, with cross-compilation and static linking being involved.
As for QMLTC, you're absolutely right in that it wouldn't work. To optimize JS code, the developer would have to use the QML Script Compiler (QMLSC), which is only available under a Qt Commercial license.
Thanks, Javier