Discreet Log #2: Qwtch to Flwtch: Porting Cwtch's Go/Qt Frontend to Go/Flutter

05 Mar 2021


Welcome to Discreet Log! A fortnightly technical development blog to provide an in-depth look into the research, projects and tools that we work on at Open Privacy. For our second post Erinn Atwater documents the development of a new Flutter-based UI for Cwtch.


The Cwtch library provides a backend written in Go intended to provide support to a number of other apps we have planned that use Cwtch connections as a (metadata-minimizing, authenticated) transport layer. Sarah has been working on Tapir so the backend has an open path forward in Rust, but today we’re here to chat about the other side!

Currently written in Qt 5 and compiled for desktop operating systems and Android, the Cwtch alpha is our flagship project, showing off basic peer and group messaging, as well as some simple form-factor demos called Lists and Bulletins. We use Qt bindings for Go to accomplish this pairing.

Screenshot of the Cwtch alpha, Qt version. It depicts the newly designer user interface in a partially implemented earlier state, and the developer has been testing integration with the Mutant Standard emoji set in the message area.

While the team was initially impressed with how quickly we could turn out a proof of concept application using this stack, small frictions occurred frequently as time went on and the space developed.

In 2020 we had planned to move from alpha to beta with a special “stable” cut of the app for putting on app stores, but in the weeks before launch we discovered numerous crash bugs on arm64 builds for android that we could not replicate on other platforms. On further investigation, we learned that many of the bugs appeared to originate deep within the QML stack and common components like DropShadow and it became apparent that it would be impossible to realize a Qt based arm64 android app based on our current UI, without significant rework.

Additionally, given the rumbling of changes to the Qt Foundation and their open source offerings that were already sitting poorly with our developers, we decided to take a week in January and try to answer the question: how much work would it be to replace our Qt-based user interface with a similar one in another framework?

For our 1 week experiment we chose to de-risk a Flutter implementation.

Why Flutter?

Some readers will be familiar with the Open Privacy team’s general aversion to applications written with embedded browser frameworks, e.g. Electron, due to the difficulty of preventing privacy leaks introduced by their keenness to load remote resources. This ruled out many modern UI frameworks.

The Cwtch alpha was written using custom QML to achieve a responsive cross-platform UI with a single code base. Not wanting to give this property up, we found the Flutter dev channel (soon to be Flutter 2) had support not just for Android and iOS applications but desktop operating systems as well.

Flutter also featured a number of other little luxuries we’d come to appreciate , such as property binding and declarative widget syntax. Everything combined, Flutter stood out among the current cohort as a viable option for a second implementation of the Cwtch UI.

We set about de-risking several core features: theming, integration with our existing data model, accessibility, localization and porting over our existing translations, in addition to the overall debugging and building experience:

Themes and Opaque

While creating Cwtch, we separated our reusable widgets and our theming system into a separate library we named Opaque: Open Privacy’s Awesome Qt-based User Experience library. This was extraordinarily helpful when we suddenly needed to create Lockbox last year, as we were able to quickly turn out a standalone, cross-platform application in the Open Privacy aesthetic our staff designer Marcia has been so lovingly crafting for us, hooked into our existing translation system and even with little niceties like light and dark themes.

Animation depicting a pillbox for choosing between three different tabs in the Cwtch Qt alpha's dark theme.

We’ll certainly be creating a similar library in order to maintain this capability with Flutter-based apps in the future.

However, the need has been less pressing. Standard Flutter abstractions like Flexes, Expanded, ShrinkWrap,Scrolling viewports, and Navigators reduce a significant amount of the boilerplate required to make a visually identical responsive component in QML.

Data models and lazy-loading lists

Flutter admits a number of choices for how to store/access/manage your application’s state, depending on your needs and preferences. We went with the Provider package, which allows us to create “ChangeNotified” state objects which are delivered to the Consumers of that state via Providers.

Flutter’s ListView also natively supports the concept of “lazy loading”, that is, only accessing the data required to inflate a list entry when the user scrolls that entry into on-screen view, and discarding it once they scroll away. This can be done in Qt just as well, but the time to understand and implement a robust abstract model compatible with our Go bindings was not insignificant.

Localization

With huge, massive, absolute thanks to our volunteers, Open Privacy translates our software using Lokalise. In QML, strings are referenced like this:

String translations in Qt using qstr(string id)

The Qt tools lupdate and lrelease then comb our QML files looking for changes to the string identifiers, and updates .TS files which we then send to Lokalise. For Flutter, the flutter_lokalise package is able to download the translations that are already there, and convert them to .arb files which are then parsed for us by an auto-generated LocalizationDelegate class.

String translations in Flutter using AppLocalizations.of(context).stringid

One nice thing about this is that mistyped string IDs are now caught by the compiler! Yay! We now manage our strings directly on Lokalise, simplifying our pipeline from having to parse and upload strings before obtaining translations, to simply downloading translations. It also allows us to create modules like Opaque that provide their own LocalizationDelegates, instead of the awkward .TS file management we had to do with Qt.

Accessibility

Another annoyance we found in Qt was having to manually implement scaling and font size for all our custom widgets, using a high-density screen resolution API that we found to be buggy and inconsistent across platforms. This was represented by the “Zoom” control in our settings pane, which became one of our most frequent sources of reported bugs.

In Flutter, we do not have this headache or this control because the Material widgets respect these system settings already. :)

Flwtch's profile manager scaled up by accessibility settings

Debugging tools comparison

Our use of Go/Qt bindings also precluded us from using some of the nicer Qt development tools, such as Qt Creator, its debugger, inspector, and qmlscene.

Using FFI/Gomobile with Flutter doesn’t break these tools in the same way.

We are able to use Android Studio to develop “Flwtch,” and the Flutter and Dart development tools work beautifully out of the box:

Screenshot of Flutter Inspector's outline mode

On the Horizon

Now that we’ve ironed out most of the anticipated difficulties, we are pushing hard to finish this new frontend so we can release it for a new round of Alpha testing. Since the new stack rectifies our app store issues, we should also be able to create Play Store builds soon, and potentially Windows App Store builds not far behind (fingers crossed/knock on wood/send us your best dehexes).

The Flutter app feels significantly more performant, both due to our determination to stick to “Flutterisms” and the significant wisdom we gained doing this once the hard way. It looks and feels more platform-native and complies with Material design guidelines on Android, which people have asked us for in the past. We’re excited to get it into your hands, for Beta, Stable and beyond!

What is Discreet Log?

Discreet Log is a fortnightly technical development blog to give a more in-depth look at the research, projects and tools that we work on at Open Privacy.

More Discreet Log

Donate to Open Privacy



Open Privacy is an incorporated non-profit society in British Columbia, Canada. Donations are not tax deductible. You can Donate Once via Bitcoin, Monero, Zcash, and Paypal, or you can Donate Monthly via Patreon or Paypal. Please contact us to arrange a donation by other methods.