Flutter from C++/Qt developer perspective

Wojciech-Kniec

Wojciech

Senior Software Engineer

Introduction

Giving new life to old, unused stuff always brings a lot of joy to the helpless nerd like me. Recently I connected Raspberry pi to my old stereo amplifier - WS-303. It was bought by my father in the mid-'80s so it still remembers the communist era in Poland! :). I wanted to steer it with a remote control. Here is my justification for the idea of writing a Mopidy client in Flutter.

In this article:

Why to use Flutter

Introduction

I found an old infra-red sensor, relay, and some resistors in my gizmo cabinet. It took me a few hours and a lot of swearing to finally be able to switch on my amp with a TV remote. Adding support for more keys was straightforward from this point (Vol+ and Vol- keys are changing the volume on both my amp and TV, but I'm ok with that).

Music! That's the thing that was missing in my setup. I decided to install a Mopidy server. It has support for various music formats, Spotify, and even YouTube. It provides basic communication via web sockets using json rpc 2 protocol. There are dozens of clients for this server, some of them are web-based, some work in the terminal, and finally there are also clients for mobile phones. None of them obviously supports turning on and off my own custom stereo set.

Here is my amp, beautiful, isn’t it?

Sorry about this long introduction. I just wanted to justify my idea to write a Mopidy client in Flutter. It was my first contact with this framework and I want to share some thoughts about it as a C++/Qt developer.

Reasons to love Flutter

Setting environment

Compared to Qt, setting up the whole working environment for Android was super easy. Many problems could simply be solved by a command-line tool called flutter doctor. Just run it in the terminal, and it will download missing stuff, ask you to confirm the license and show a readable summary. Much simpler than manually installing and setting paths in Qt Creator for JRE, NDK, SDK... Here is an example of flutter doctor summary:

Doctor summary (to see all details, run flutter doctor -v):[√] Flutter (Channel master, v1.15.4-pre.144, on Microsoft Windows [Version 10.0.17763.1039], locale pl-PL)
[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[√] Chrome - develop for the web
[!] Android Studio (version 3.5)
 X Flutter plugin not installed; this adds Flutter specific functionality.
 X Dart plugin not installed; this adds Dart specific functionality.
[√] Connected device (2 available)
! Doctor found issues in 1 category.

Dependencies and package manager

I hate the fact that C++ doesn't have a standardized package manager. Conan is growing in popularity and C++20 will have modules, but it's still nothing compared to npm from node.js or even pythons pip. Flutter has something similar to the Cargo from Rust, which is great. Using 3rd party libraries with it is very simple, all you have to do is to add an entry into pubspec.yaml file. Dependencies section looks like this:

dependencies:
     flutter:
         sdk: flutter
# additional libraries with minimal versions
     cupertino_icons: ^0.1.2
     loading: ^1.0.2

If I want to use e.g. json_rpc_2 library, version 2.1.0 or higher, all I have to do is to add this line in the dependency section and save the file.

json_rpc_2: ^2.1.0

Library will be downloaded and ready to use in a second!

Hot reload

This feature is awesome! Building & deploying apps can take a long time. It gets annoying when all we want to check is a simple, tiny fix. In Flutter, there is a 'Hot reload' function, which means super-fast upload on every single save (if code is compilable). Setting up widgets on a page is almost as convenient as writing a markdown document with preview. It really works!

Declarative approach

Flutter rebuilds view reflects the state of an application. It is a big paradigm shift, but it is easier to maintain states of UI. Forget about e.g. setting the font color to red when something happens and then removing buttons on another event. Here you define what UI should look like for a given state. If something changes, the UI will rebuild itself from scratch. Everything is done automatically, under the hood. It is easy and elegant but here comes my biggest concern - will it cause performance issues for complicated UI? Probably everything depends on how the UI is created and designed.

Responsiveness

Async code is quite natural in the Dart programming language. Using futures is as easy as using fetch in javascript. Here is an example: let's suppose that we have an asynchronous stream which produces some data from time to time. We can directly build a widget based on received information:

    body:  StreamBuilder<BrowsingDTO>(
       stream: widget._mopidy.browsingStream(),
       builder: (BuildContext context, AsyncSnapshot<BrowsingDTO> snapshot) {
         if (snapshot.hasError)
           return Text('Error: ${snapshot.error}');
         switch (snapshot.connectionState) {
           case ConnectionState.none: return Text("No connection");
           case ConnectionState.waiting: return Text("Waiting");
           case ConnectionState.active: return _createBrowsingList(snapshot.data);
           case ConnectionState.done: return _createBrowsingList(snapshot.data);
         }
         return null;
       },
 )

Widget _createBrowsingList(BrowsingDTO dto, mopidy) => ListView.builder(
   padding: const EdgeInsets.all(8),
   itemCount: dto.results.length,
   itemBuilder: (BuildContext context, int index) {
     return Container(
         height: 50,
         child: FlatButton(
           onPressed: () {
             switch (dto.results[index].type) {
               case "directory":
                 mopidy.browse(uri: dto.results[index].uri);
                 break;
               case "track":
                 mopidy.add(uris: [dto.results[index].uri]);
                 mopidy.play();
                 break;
               case "playlist":
                 mopidy.addPlaylist(dto.results[index].uri);
                 mopidy.play();
                 break;
             }
           },
           child: Text(dto.results[index].name),
         ));
   });

Little explanation - this small block of code attaches to a stream that delivers data based on user browsing data (current folder, etc.). Every time a new BrowsingDTO object is yielded, widgets are rebuilt and when the user clicks on the generated flat button, a new request is created and the cycle continues. Looks cool, doesn't it?

Bloc design pattern

Although I haven't used it in my Mopidy client (yet), I've made some experiments with it and it is very promising. IMO, it is more testable than a standard MVC approach and fits much better to the flutters declarative way. These are my first observations, I haven't found any drawbacks of this pattern (I believe there are some).

Visual Studio Code

I can bet that this point can be surprising and controversial for some people. I personally love VS Code for its robustness and extensibility. Not all environments come along with this editor. Thankfully, Flutter is not one of them. Configuring it to work with Flutter is seamless and I haven't experienced any problems working in it.

Fast development

This point is a conclusion. All of these little things above lead to it. Less time is needed to develop fully functional applications. I didn't have much trouble finding a solution when I got stuck. This was my concern because Flutter is quite new and the community is relatively small.

wewnatrz.jpg

Reasons to hate Flutter

Big paradigm shift

One has to change the way of thinking about building an app. The declarative approach is totally different from the standard, iterative one. It takes time to get used to it. Experience in game development is helpful in this case.

Debugging & hot reload problems

Sometimes the debugger was not triggered on the right spot. The call stack is sometimes messy and useless as a result. Hot reload is a great feature, but sometimes it is misleading. It took me a while to find a bug in my code. There was none - all I had to do is to restart the whole app.

Ugly code

Dart language is fine itself, but building view with it can lead to unreadable, messy code with cascading widgets. I still haven't gotten used to it.

Coding traps

Flutter and Dart give a lot of freedom. They are very flexible, but it is tempting to create big all-in-one classes. The second problem which comes to mind is that it is easy to mix models and view logic. It happened to me a few times.

3rd Party

Most important things are present, but it is nothing compared with mature languages with huge codebases. This is changing. Flutter is growing!

No native builds

Running apps on Windows or Linux is not possible yet, running in a browser is possible but still at an early stage. I know, I know - it is a mobile environment.

Summary table
Good Bad
Good documentation Bad documentation ;)
Easy to set up Big paradigm shift
VS Code Debugging hiccups
Hot reload Hot reload problems
Easy responsiveness Ugly UI code
Easy to create views Easy to create big classes
Fast development Easy to mix model & view logic
Bloc pattern No native builds
Easy UI tests Small codebase
Convenient dependencies

Conclusions

My overall thoughts are definitely positive. It is worth a try! This project was mostly about learning and experimenting. I had a lot of fun doing it, and I guess I didn't always stick to good practices. The best thing is that, as a result, I got a working and functional application in a relatively short amount of time.

Last thing, here are screenshots that I took in my phone:

flutter_2.png

Share on social media

Choose your way of implementation and let's start working together on your project

Get a quote
Back