At Star, we have the opportunity to use a number of applications and frameworks when developing mobile applications for our customers. One of the frameworks we have used for rapid application development is Flutter. In this article, we share some of our experiences in working with this tool and recommendations for how to use it effectively to design an engaging UI.
Flutter is a great cross-platform framework from Google. It’s a UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. Flutter provides developers with a number of advantages, such as:
- Fast Development — Flutter has a hot reload. It helps us to experiment with our UI quickly and without losing application state.
- Native Performance — Flutter allows you to build full native performance on iOS and Android with the help of fully compatible widgets. Apps compile down into native binaries that rely on graphics and rendering engines built in C/C++, resulting in a very fast, high-performing application.
- Flexible and comfortable UI approach — Flutter enables you to implement the most ambitious designs, with the help of Material Design and Cupertino widgets, smooth scrolling, rich motions API and so on.
Our team has had the opportunity to develop several applications using Flutter. Here is a brief overview of just some of the applications we’ve developed in our projects.
An application that helps users to control air flow inside a building. The app connects to the vents (ventilation grill) or the temperature sensors via Bluetooth LE. Using the app, users can carry out a range of tasks, such as:
Air flow control system status monitoring;
Configuration of air flow control system;
Particular zone creation and configuration;
Remote firmware updates on vents or sensors using distant firmware update;
System log monitoring, gathering, and sending to the remote database;
Application stability monitoring via Firebase.
What technologies we used:
BLoC architecture pattern
Provider library for Dependency injection
Flutter Blue library for Bluetooth LE communication
Floor library as database abstraction
Firebase Crashlytics as an analytics service
An application that helps demonstrate body composition analysis (scanning body weight, body fat, BMI, body water, metabolic rate, blood pressure, heart rate etc.). It scans QR codes from a specialized fitness device and visualizes data. It enables the user to:
View body composition reports with graphs and charts;
Show personal progress by comparing previous results;
Monitor application stability via Firebase.
What technologies we used:
BLoC architecture pattern
SQFlite plugin for database
Firebase Crashlytics as an analytics service
Defining of architecture approach
When it comes to choosing an architecture for mobile applications, there are a number of possibilities. In our quest to find the right approach, we tried Redux, MVVM and BLoC. Each approach has advantages and disadvantages and, unfortunately, there’s no such thing as the perfect architecture. That being said, we prefer to use BLoC. Why?
BLoC is simple and predictable. The main idea is separation of the “logical component” from the abstraction block, which has a clear input and output interfaces. This approach is scalable because logic is stored in separate components. You can multiply existing components or add new ones. Also blocks are easy to test because you have an independent part of logic with clear input/output.
You can use https://pub.dev/packages/bloc library, which allows you to avoid boilerplate code. It also simplifies error handling and provides additional features like transitions or delegates. We use it in production and it looks good and stable. An added benefit is that the BLoC approach is simple to implement independently, which removes any additional dependency in your application.
Unstable 3rd party libraries
Flutter has an awesome community. There are a lot of open-source projects which help thousands of engineers every day. Some of these projects are stable, popular and well-maintained. However, if you try to find a library which covers unusual cases like you need to work with Bluetooth low energy (BLE), you’re likely to face some challenges, as we did.
For example, the most stable library which allows work with BLE is flutter_blue. However, if you start typing “BLE” as a query in pub.dev, you won’t find this library at the top of the search results. Instead, you’ll find it hidden away at the bottom of the list, together with all of the poorly maintained analogues. One of them is just a copy of `flutter_blue` which is stored in the Gitlab instead of Github. This makes it quite easy to mistakenly choose the wrong library.
This isn’t a huge problem, but it does make the search process cumbersome. We hope this issue can be fixed by adding some improvements to pub.dev, such as packages rating, trusted publisher and Flutter favorites.
Given this, we minimize the use of third-party libraries, which gives us confidence in the stability of our applications.
Working with localization in Flutter is a challenge. Flutter allows you to detect the locale of the device without any problem, but the mechanism for managing locale dependent resources does not exist. For example, Android has good resource management. You have access to resources from code. Also resources are stored inside separate files, which can be translated by non-technical persons.
There are several solutions from the community, yet we weren’t really impressed with any of them. The most commonly used approach involves working with json files and referring to constants as a json-field. From one side it looks very easy when you need to export all string constants because they store in files with simple structure, but from another side – you will not be protected from the runtime exceptions in case of mistakes accessing the constant field by name. To avoid this, you can use Android Studio flutter-i18n plugin, but this is inconvenient because it will add its special files to all your projects if you do not have different IDE instances with different configs.
Based on this, localization looks like a potential challenge in our future projects, so we decided to work out the ways to make it easier.
For now, we simply store resources inside separate dart files as constants. And it looks very convenient at work. Therefore, we decided to develop our localization based on this approach. Our solution is very simple: just extract some interface (in our case this is abstract class), with all needed fields for the constants and create strings classes for required languages. Proceeding from the current device locale you can provide appropriate strings class and, as a result, correct localized value. Further, this approach can be improved using json to optimize access for non-technical people who were mentioned at the beginning.
There are a wide range of device sizes, pixel densities and orientations that mobile applications need to support. Overall, this task is not new, but it does require attention.
As Android developers, we are used to building application UI components operating with dp (Density-Independent Pixels) — an abstract unit that is based on the physical density of the screen. These units are relative to a 160 dpi screen, so one dp is one pixel on a 160 dpi screen. The ratio of dp-to-pixel will change with the screen density. But Flutter provides something brand new – logical pixels. What are they?
Google explains: “Flutter doesn’t have dps but there are logical pixels, which are basically the same as device-independent pixels.”
It’s not exactly the same, in our experience. You cannot just build a UI using sizes from your sketches and hope that it will look the same as your design on every screen.
“Device pixels are also referred to as physical pixels. Logical pixels are also referred to as device-independent or resolution-independent pixels.”
So, to build a pixel-perfect design, we need to map our design layout to the concrete screen resolution. And simple math will help us with this. Just take dp (or point) size of view that you need, multiply this by your device screen width and divide by the design width. To operate with pixels, this method should be modernized considering the designPixelRatio (divide everything with it).
So the solution is very easy, and we can say that it works great.
If you’re used to developing in Android, you might be familiar with the life-long struggle that is saving state in application. No matter what type of state saving it is, orientation state changing or saved state when an application is killed by OS, all of these situations are difficult to handle. Flutter takes a look closer at the first of these problems. It handles orientation without losing user data. But the second problem is still unresolved, and when the OS kills your application, you can’t handle it and restore your data from the previous session.
So, to sum up:
- Flutter offers developers a number of advantages, including fast development, native performance and flexibility. And it’s a fast, high-performing application.
- When working with Flutter, we prefer to use the BLoC architecture approach, as it’s simple, predictable and easy to test.
- Flutter has an awesome community, but we recommend limiting the use of third-party libraries, as some of these are unstable and poorly maintained.
- Working with localization can be a challenge. We devised a simple method for localization, which involves storing resources inside separate dart files as constants.
- Logical pixels work well, just remember to map your design layout to the concrete screen resolution.
- Flutter solves the orientation state changing issue, but the issue of saving state when your OS kills your application is still, unfortunately, unresolved.
Flutter is an excellent tool that has helped us to rapidly develop engaging, meaningful UI experiences for our customers. Perhaps you have an idea for a mobile application, but you need help bringing it to life. If so, get in touch. Our experienced developers are ready and waiting to help.