It is plenty out there of barcode/QR code scanner apps (both on Android Play Store and Apple AppStore [and iOS 11 include native code scanner in the camera app]) so it may seem there is no need to build a new one but this is not true!
In this blog post I will describe how I built a barcode scanner app (ScannerMApp, currently available in a beta stage on the Play Store) in Delphi using the Edward Spelt’s Delphi port of the ZXing library and my TFrameStand component.
The project only depends on Delphi (10.2 Tokyo used) and two OSS libraries: ZXing and TFrameStand. Full source code of ScannerMApp is available on my GitHub.com repository.
Sometimes, a video is better than a thousand words: here it is a 45 seconds video showcasing ScannerMApp 🙂
QR codes (barcodes, in general) are really handy when you need to join the physical world with the digital one, simply put a QR code on an item or in a physical place and that item/place is now digitally tagged (and you can take advantage of this in your applications).
While preparing material for the last Delphi mobile training course I gave, I received questions about building a solution to read barcodes / QR codes in a Delphi mobile app. I already covered this argument on my blog, at XE5 time and later with XE7, but with a different approach: launch a third-party barcode scanner app and get results through clipboards or Android intent infrastructure.
This time I tried to have more control and decided to integrate a library for QR code recognition directly into my FMX app. This approach has some advantages like:
- it does not create a dependency with a third party app (or/and the way to communicate with it);
- it gives you full control on all details of the scanning phase: you can provide a bitmap to the library and ask for a search of desired code types so you may want to get a frame from a camera (deciding resolution if you care), add some pre-processing if needed and get some additional informations about the result, like the exact position of the found code in the bitmap;
- if the barcode recognition library is written 100% Delphi code, you achieve a great portability of your app through different platforms (Android, iOS, Windows, Mac OS X and Linux!)
So I decided to build this FMX application trying to provide a tangible example about how to structure a simple Delphi mobile app that access sensors (camera can be considered a sensor), stores values and present them to the user through a minimal UI.
The library is very easy to use, fast, has good support to all major barcode formats and has been recently updated with a farther performance improvement tied to inverted barcode recognition (now optional, so you can save time if you don’t need to read inverted barcodes).
I want to thank Edward for the kind suggestions (and corrections to my code) he gave me during the development of this app.
For the ScannerMApp, I decided to implement two use cases:
- start the device camera (using a low resolution setting), grab each available frame but scan only each X milliseconds (currently X=133, to give something like 7 attempts per second);
- pick a picture from the library (or filesystem on Desktop platforms) and scan once (generally sufficient since there is no focusing problems and of course no targeting is possible).
The app structure
I tend to structure my Delphi mobile apps using datamodules, a single main form and several frames. Using TFrameStand is easy to orchestrate how the frames get shown to compose the user experience.
Here is a simple diagram of ScannerMApp:
All the important code is in the main datamodule (MainDM) including the TCameraComponent used to grab frames from the device camera, the TFDMemTable used as storage for result data and all the ZXing.Delphi related code.
Each frame is used to present a different piece of UI to the user, reflecting the app status and data available:
- TScanningFrame is responsible to help the user acquire the barcode by painting the camera’s frames on a TRectangle (faster than a TImage) and hosts an informative label about current camera resolution (and FPS even if it seems to be hardcoded in Android) and a button to toggle the torch mode (where available) that may help when there is no sufficient light in the room; during the scanning, you may see some TRectangle(s) being drawn over the camera’s frame, to indicate the ZXing.Delphi library detected something possibly being a barcode there.
- TDataFrame has a TListView with DynamicAppearance and is used to present to the user the so far scanned barcodes, including details per each item like a thumbnail, the barcode format, the content and a counter of how many times that specific barcode has been scanned (since the start of the app); clicking on an item will lead to the details (see next point in this list);
- TScanResultFrame is used to present the user the successfully scanned barcode informations like format, date of (first) acquisition, a picture of the frame with some markers indicating the barcode position and a couple of actions to let the user delete the current item or share the content of the barcode through the system’s ShareSheet;
- TInfoFrame (not listed in the above diagram) is used to show a simple credits layout about the app and libraries used.
On the MainForm you can see the TFrameStand instance that will orchestrate the UI through all the app.
I decided to take advantage of the built-in System.Messaging publish/subscribe mechanism using the default TMessageManager to send and receive messages through all the app. This way, the dependencies across the modules (forms, datamodule, frames) of the app are minimal (many knows the MainDM, the MainForm knows most of the frames but no crossed dependecies) and code can be properly separated (no UI code in the datamodule, no application code in the UI frames). You can see, in the above diagram, who is publishing messages and who is subscribed to them by looking at the small mail icon (green = subscribed, red = publish). As you can see in the code of Data.Main.pas and here below, a bunch of custom messages have been defined and used across the app:
TScanningMessage = TMessage<Boolean>; TScanResultMessage = TObjectMessage<TScanResult>; TScanPointMessage = TMessage<TPointF>; TScanSettingMessage = TMessage<TVideoCaptureSetting>; TTorchModeMessage = TMessage<Boolean>; TCameraBufferMessage = TMessage<TBitmap>; THardwareBackMessage = TMessage<Integer>;
- each time a frame is available from the device camera (see TMainDM.CameraComponent1SampleBufferReady), a TCameraBufferMessage is sent (and the TScanningFrame, subscribed to that message, will receive the bitmap to be shown to the user);
- each time the ZXing.Delphi library detects a possible result point of a barcode (during scan) a TScanPointMessage is sent (and TScanningFrame will inform the user by drawing a rectangle over the frame);
- when a barcode is successfully found, the MainDM will send a TScanResultMessage and the MainForm, the MainDM and the TDataFrame will be informed about the newly available result (the MainForm will ensure the user can see the TDataFrame, the MainDM will store the result in the TFDMemTable and the TDataFrame will refresh the ListView).
It has been a pleasure to build this app, using a lot of different technologies included in Delphi or available as OpenSource projects.
- RTL provides (among many other features) the System.Messaging mechanism
- FMX provides(among many other features) easy access to the device camera
- FireDAC is used to easily store results (using a memory table and including bitmaps)
- LiveBindings are used to bind the memory table to the TListview on the TDataFrame
- Parallel programming library is used to perform barcode scanning in the background of the application, ensuring a great user experience
- ZXing.Delphi implements barcode detection and decoding
- TFrameStand orchestrates the UI, providing visual continuity and nice effects/transitions easily
What’s next and conclusion
As I said, this app was initially built to be shown and explained in a training course of mine (as a proof of concept), then I tried to polish it a bit, improve performance and user experience (thanks again to Edward Spelt for some hints and suggestions) and now I am releasing it as an example for all Delphi developers. Of course it can be greatly improved in some areas and of course it is far from perfect but seems nice so far 😉
There are a few things I’d like to do:
- register the app for SEND action intent-filter on Android (would make ScannerMApp to show up in the ShareSheet, also from other applications). This is a work in progress but not yet finished.
- need to test a bit on iOS (if anyone can give feedback it would be great)
- define a storage strategy (locally save the memory table or send data to a remote server, depending on needs)
- provide a sound feedback on successful scans
- filtering desired barcode format (the user may be looking only for QR codes or EAN13… this should improve precision in result acquisition reducing false positives)
I hope you’ll enjoy this example as I do! I think this is a good example how easy it is to build a not-so-trivial mobile app with Delphi (and have it running on multiple platforms!).
Have a nice day and good work!