Introduction

I’m definitely not a front-end developer. I usually find myself experimenting in the back end of things. So, when I created the LofiGirl, I initially developed a cli interface and that was it. Later on, I wanted to have a nice looking front-end which interacts with the backend and also shows the user what the heck is going on.

For the first front-end of lofigirl, I did not go too away the idea of playing around Rust and I went to an option which is somewhat okay in the community with seed-rs. With event driven system with message passsing (?), seed-rs can create some challenges but the system was working fine in the end by compiling into webassembly. However, it was lacking the nice looks of a native web app.

So, after dealing with my PhD viva / oral examination (completely not related), I wanted to give brain a break and do something about the lofigirl front-end. As I said, I’m not a front-end person that much. I know a little bit of javascript and used jQuery stuff back in my undergraduate times. So, I wanted something which can generate native code to web or other platforms directly. That limits the number of options quite a lot and in fact there are actually two candidates: React Native and Flutter. Having heard of both of them, React gives me more JS vibes so I decided to look into Flutter and the Dart language. I have seen this marketing line/ sellingpoint of sound null safety in Flutter web-site which sounds super familiar to a person who uses Rust or any language with optional values for null safety. So, I think that give the edge for me to learn Dart in a basic context and use Flutter to create a nice looking front-end.

How about bridging common types for Server/Client from Rust to the Front-end

Even before learning anything about Flutter and Dart, I decided to look into the idea of bridging the common API types into Dart automatically. This sounds a like great idea to increase maintainability automatically and make it easier to develop something new.

I found about flutter_rust_bridge. When I try to dwell into the code, I realise it’s difficult to understand without knowing any Flutter context but I managed to grab some ideas about bridging Rust types outside of the crate using mirror types in the flutter_rust_bridge project. This seems like a good idea to me. I was pretty impressed on the point that I can basically create Dart types from Rust types and then use them in the front-end. However, it wasn’t actually working like that. The flutter rust bridge system is actually using Dart’s FFI system to talk to any C-ABI libraries and Rust counterpart just targets that automatically. This creates one big bottleneck in the system: it reduces the number of platforms that can be supported, particularly for web.

I kind of felt disappointed with this limitation and decided to just bite the bullet and write the whole project in a language I have never coded before: Dart and using a platform I’ve never used before: Flutter.

Learning a new Language

While trying to digest/understand how Dart code works, I had some observations. The language, at least for the syntax point of view is not too far away Java like C-style languages. Object-orientation methods are easy to grab. I didn’t have so much difficulty to understand. I found (?) syntax on types as in String? to make it nullable/option sensible and I like the explicit unwrap with (!) as in var!. I also found it impressive that in some places if the null check is already made, the explicit unwrapping is not necessary. The system can automatically determine that, impressive!

A second observation I’ve made was on async runtime. Again, I found it very sensible and similar to Rust. I managed to grab the idea in no time. A couple of differences I’ve notice which I wouldn’t assume are: 1) Async body return types needs explicit Future wrapping, 2) await syntax being in front of the async call can reduce readability.

The Application

The logic

By going through the flutter cookbook, I got familiar with the language in a couple of days. A couple of concepts I needed to use in my system were already provided in the cookbook with examples such as basic value storing, networking etc. After understanding widget data/event mechanism of the platform, I started developing a simple prototype.

One key issue I have encountered is to the trigger new events, depending on a condition in a sub-widget. Investigating into this, I found out there are multiple ways to address this: 1) global keys to access where you want to access and send the event, 2) register function callbacks in sub-widgets and trigger callbacks when needed, 3) use third party libraries which can help you in two-way data hierarchy such as Redux. I went for the second option since it looked more like a good idea to me. Maybe in a bigger scale project, the other options might be cleaner since callbacks can clutter the code a bit.

Another bottleneck I had was to have periodic task running in the background. I don’t have ambitious goals such as running the background code while the app is not running but I wanted to have the scrobbling logic to run in the background with periodic checks depending on the playing status. From diving online, I found out, I can start a periodic timer in the initState of a Stateful Widget (my main widget). That solved the issue perfectly.

After around 2 days of coding I managed to finish my app. The app uses 2 distinct pages: 1) main page to choose the stream the scrobble and 2) settings page for all necessary settings. I even created a guide system to help the user to go to the settings page first to set things up.

I’ve mainly used one particular stateful widget as my main widget to store all vital information. Other widgets included inside the main state widget triggered whatever they wanted to trigger using the registered callbacks. The view of the app depending on the state of the program is done by conditionals in the main widget. This last but makes the code a bit spaghetti to be honest. I realised that I can use nice widgets like Visibility later on but I’ve already committed the app at it is and since it’s a small project I decided to keep it simple.

The looks

A couple of screenshots:

  • The guiding in the main page towards settings.

  • Initial settings page. When the user is finished with a certain field, it’s automatically grabbed by the system. LastFM passwords is never stored. For server url, the app automatically uses the health API back-end to test if the server is accessible before setting the value.

  • After filling the settings. LastFM session-key is grabbed from the server side and this is stored with the username value. The app also uses a connection button when the user filled enough information to grab a app session token.

  • Main page after being connected.

  • Scrobbling a song on the app and its lastfm/listenbrainz verifications.

Testing

Example flutter app automatically comes with simple widget testing. I haven’t dwell into widget testing myself much but the idea is impressive. It’s pretty powerful to emulate page changes or clicks/tabs for testing purposes. It reminds me of selenium testing platform but it’s included in the flutter toolchain directly and that is pretty powerful.

In the end, I decided to keep one single test to test out the initial page of the app. However, I kind surprised to see that my initial app was failing that test. Upon looking up the reason, I realise the testing system is complaining that the timer is still alive even with the widget is destroyed/disposed. My assumption was that, each timer background task would be terminated automatically by some runtime checks depending on the widget state but it wasn’t the case. So, I added a condition to cancel my periodic timer on the dispose method of the stateful widget.

Targetting platforms

Now it’s the magic part, at least for me. Following flutter doctor’s advices, I can build my application for android/windows/linux/web without any efforts! I’m not a Mac user so I don’t know about Mac/IOS targetting capabilities. Maybe I can try to do something about it on a VM in the future or can directly use Github Actions to blindly build for those targets as well.

For the platforms I have tested on, I’m very impressed that the same codebase can directly go towards different places without too much hassle. I guess th scope of the application I’m dealing with and keeping the app simple without any third party dependencies keeps the multi-platform part easy.

One single thing I haven’t looked into is to sign/play around android manifestation. I’m not super experienced with this stuff. However, I suspect since I did nothing about it, the android OS doesn’t trust the app even as a third party installed app directly from an apk. I can look into this later as well.

Demo

I’m embedding the flutter web demo to this website which can be accessible here.

Final words

I’m happy that I had some time to clear my brain from PhD stuff and also forced myself to leave my Rusty comfort zone to play around some other language. I’m surprised that I managed to pull this app off in a week-end without any prior Dart and Flutter experience. It’s not the prettiest or the cleanest code ever but it gets the job done while reasonably looking well and working fine. If I need to do some multi-platform client side app in the future, I’m happy to use Flutter again.