While developing mobile applications can be relatively straight-forward, there is the occasional project that demands a greater knowledge of the technologies available. In our case, it was when a transport giant came to us seeking a solution for their driver’s Zebra devices. The proposal was to create a mobile application that can manage a ton of data, run on a Zebra device and most importantly, work regardless of a wireless connection. There are many ways to tackle these requirements; however, when a short deadline is present, we are forced to think a little harder about the technologies worthy to use. That being said, although the performance of native mobile applications is strong, we can immediately rule them out due to time constraints and the fact that performance is not our biggest worry. We found that the most suited path to success would be to create a Progressive Web Application (PWA) using a technology such as Quasar. A PWA offers all that is needed to create an offline-first application with less development time needed than a native application. Thus, after combining a few technologies to create the PWA, we managed to smash deadlines with an application that is on par in terms of performance and stability.
The desire for a powerful technology that is easy to manage was what led us to Quasar, a high performance frontend stack. The framework proved to be a good solution for quickly creating an effective PWA. It offers a well documented framework using Vue.js and is bundled with many preset styles that are available for customizing. This allowed us to focus on the functionality of the project rather than worrying about design. In addition, Quasar also has a plugin system which allowed us to add things such as internationalization without much effort.
It was not too long after that we decided to complete the stack with the addition of TypeScript. Although TypeScript is not fully supported within Quasar, we started by adding it where most valued. With the requirements in mind, it is to be noted that the application must store events that occur offline and synchronize them upon reconnection. That being said, TypeScript ensures that we are dispatching data of correct form to the API. Aside from that, our development speed greatly improved due to the increased readability of the code being written.
Storage options are limited to local storage, IndexedDB, and cookies due to being a web-based application. Considering the security and stability of each option, we decided to mainly use IndexedDB as it was the most secure solution to store our offline events. With the help of Dexie, an IndexedDB wrapper, using this storage system was relatively similar to most SQL database ORMs which further helped the readability of the code. Aside from that, our primary reason behind using IndexedDB was that it can be accessed by both the service worker and the client. This means, any events that we save to the IndexedDB within the client can be synchronized in the background by the service worker. This is an important feature that helps define our flow.
Considering the application must be offline-first, we made the decision that no service would directly talk to the API. Instead, services would talk to an
ActionService which knows how to save the actions to the IndexedDB in a table dedicated for pending action data. This allows our service worker to pick them up from the same table and attempt to dispatch them all-together upon an established connection. If the synchronization is a success, the actions are moved to another table as a precaution to the possibility that we can not fetch the updated actions after the synchronization. This happens because the business logic within the application has a high chance of needing the action data later on. That being said, moved actions are flagged and ready to be replaced by updated actions upon a successful fetch.
Actions are an append-only data structure where the most updated data of an identifier is determined by the previous chronological actions involving the same identifier. This allows the offline device to store actions chronologically with timestamps and once connected, synchronize them to the API. It is the API’s job to determine the end result of an identifier as another device could have intervened with the posted actions by updating it in a different way. The result of composing all these actions, disregarding the device they came from, results in the most up to date data of that identifier. This append-only structure of data is very important to the offline-first application as it is the least vulnerable to corrupting data when edited by multiple offline devices.
Although the service worker has the primary task of synchronizing event data, it is also a key component in caching the data needed to work offline. This happens during the login process as that is the only time where a connection is guaranteed. Throughout the development of the application, keeping an account for no wireless connection, we manage to create a stable and safe offline-first solution for posting data to the API.
With the construction of a well thought-out stack, our development process quickened with the outcome being a fast and stable application. In addition, the PWA allows for easy management as versions can be updated without reinstalling on devices. That in mind, it was a perfect fit for this project as the intended use of the application was to be on many Zebra devices used by drivers who are always on the go. This goes to show that although we could’ve made a native mobile application, sometimes there is a less known alternative which proves to be more worthy to get the job done.