If you are into mobile app development then you’ve probably heard about Flutter, Google’s open-source framework for building cross-platform applications. Released in 2017, the interest about Flutter kept growing since its launch, as you can see in Google Search Trends or Stack Overflow Trends.
As we like to do with emerging technologies, we wanted to see how it worked and how that experience was, for what we asked ourselves: what’s it like to get started with Flutter coming from a native developer background? What about moving from native languages to Dart, the one used by Flutter?
Our Android and iOS dev team spent a while with it and we’ll try to answer these questions below. Let’s dive into how was our first impression and steps with Flutter.
First look to Flutter & Dart
Starting with the documentation, Google seems to be investing as much in this as they are investing in coding the actual framework. There are many channels where documentation is available for the community to join and get a lot of valuable information, from how to start with this technology to more advanced features and techniques.
For instance, there’s the Flutter Developer page itself with all the basic setup and class documentation, their official YouTube channel, and many other popular platforms (StackOverflow, Medium, even Slack) we as developers use every day to discuss technologies and find solutions to specific problems. You can browse Flutter huge community here.
Setting up a Project
Following Flutter’s Get Started page everything worked right away. As native developers, we went with Android Studio as IDE that has full support for Flutter + Dart development by just applying a couple of plugins.
You can also work with other IDEs like IntelliJ and Visual Studio Code through a Flutter extension:
Once you download the SDK and choose your IDE of preference, you’re pretty much ready to go. Note that for running your app on iOS simulators you’ll need to have XCode installed on your computer.
Yes, this means that if you are working on a Windows PC then testing your app on Apple devices could be a challenge.
In the case of Android Studio, the Flutter extension provides a feature called Flutter Inspector that allows hot-swap rendering to iOS by using Android emulators, which is going to be fast for UI development. Note that Flutter Inspector’s hot-swap only works with Android emulators but not with iOS simulators:
The Project: Dart and dependencies
As native mobile devs we are used to dealing with multiple languages on a single project (Swift & Obj-C for iOS, or Java & Kotlin for Android), having said that, we were not thrilled at the prospect of adding yet another language, Dart to that mix.
To our surprise, starting with Dart felt somehow familiar with languages we already knew (of course we’re leaving Obj-C out). Besides naming conventions, like using underscore for private methods and variables or using “snake_case” for naming files, it somehow combines a Java syntax with more modern language features like lambdas and closure support.
As expected from using Dart, you can use a surprisingly large set of libraries available for Flutter through Pub (Dart’s package manager) allowing you not to reinvent the wheel and use packages created by other developers for both Flutter & Dart ecosystems.
Third-party libraries you sync and other project configurations are defined in a ‘yaml’ file called pubspec, which is something you can relate to using Gradle on Android or CocoaPods (in some ways) in iOS.
Hands-on with Flutter
In order to explore Flutter, we decided to re-create some screens from IdeaSource, which is our open-source platform for open innovation we made as a sandbox to research React Native development early this year.
The journey begins: Getting server data
First of all, we needed to get some data from the IdeaSource server, and since our existing project is based on GraphQL, we followed that in Flutter.
This led us to find our first dependency by searching for a GraphQL library to link on ‘pub.dev’. We found not many results (actually only one for Flutter) but it turned out to be pretty popular and well maintained.
After linking it to our project and syncing the packages, we followed the documentation to start getting data from our server. Right there we were able to try the asynchronous capabilities in Dart.
Here’s how our data provider class end up looking:
Besides some hidden work, like data classes creation and parsing, we were able to get the data pretty fast and the code is clean and readable. Although the code is almost self-explanatory, here is what each method is doing:
- Method to be called from our Widgets (more on that soon) to invoke the async call.
- Method that returns the options needed for the query. This is a strictly GraphQL related code where ‘Queries.getChallenges’ is the actual query to be performed by the request.
- Method that handles the result of our async call.
Even though the latter is somehow obvious, it is important to mention that the ‘ _handleResult’ method is being called in your main thread, which means that the data parsing is being done on our UI thread.
Of course this is not recommended, since data parsing may be consuming resources that will end up lagging your UI. To avoid these specific situations, Dart has a way to isolate this call and being asynchronous as well by using the ‘compute()’ function, which in my opinion is pretty neat.
Here’re the changes to send that parsing to the background:
Changes made to the previous implementation:
- Result type from
- Return line from
As you may notice, the ‘getChallenges’ function uses the promises pattern in order to handle the asynchronous call. Although it is not super common to use the promises pattern on native iOS and Android code, we’ve seen and used implementations of this kind of patterns by using Promises (iOS library from Google), PromiseKit or even Rx implementations. But this is definitely a change of paradigm on Flutter in comparison to iOS/Android development.
Now, it’s time to show something on the screen.
This is a big topic in the Flutter world from many perspectives. Starting from its essence, Flutter “draws” the interface instead of rendering platform-specific UI components in hierarchy as other mobile native technologies do (including React Native).
Here’s how a hierarchy view looks like:
Flutter uses Skia as the graphic engine to draw the app’s interface. Skia is an open-source graphic library sponsored and managed by Google that allows the technology to end up targeting the same interface implementation to many platforms. At the moment this platform includes iOS & Android only but supporting web and desktop apps is in the route map for Flutter.
What does this mean for our end users? Well, some of the concerns about this “drawing” approach are:
- UI platform parity: It is up to the Flutter framework to render the UI elements as target platforms need in order “to be native”. This means not only how a button looks but also how scrolling feels, transitions are made, and gestures perform.
- Backward compatibility: It is up to the Flutter devs or even third-party libraries to support different looks for different OS versions. This means that you need to update your app in the stores when these changes happen, otherwise your app may look old-fashioned in these scenarios.
- Performance: Even though Skia seems to be very efficient, you have to be very careful about how you build your interface and avoid rendering parts of the UI you don’t need to, otherwise you’ll get a laggy UI.
An almost impossible-to-avoid when writing Flutter apps are Widgets. A widget is basically a concept used to wrap a certain piece of code as a component you can reuse. They are not limited to UI related code, but most of the UI elements have a widget representation.
The Flutter SDK comes with a lot of predefined widgets you can use, and regarding UI there are probably too many. From basic elements that display a simple string called Text to more complex controls like the ListView (like a UITableView on iOS or RecyclerView on Android), there are many other widgets out there, such as for sizing and distributing elements, actions, styles, and animations. In fact, there’s a YouTube playlist called Flutter Widget of the Week which is a really good reference to discover new Widgets you can use.
So basically, in order to build your UI, you’ll be swimming on Widgets, stacking, nesting, and grouping them together. For instance, below are the widgets we needed to create to display the challenges screen the same way we did on our live app.
Let’s begin first with the main widget that will represent our main screen:
What’s being done is:
0) Widget Definition: You create your widgets by creating a class that inherits from a base Widget depending on what you are trying to achieve. Widgets can be stateless or stateful depending on what you’re going to be handling within. Even though by definitions it seems easy to decide, there’s a whole discussion about this which is tied up to the State Management topic (more about that below).
1) Override ‘build’: To use ‘stateless’ widgets you need to override the ‘build’ method and return whatever widget structure you need to display.
2) ‘FutureBuilder‘: This a specific widget that will allow running the asynchronous code we built before and handle different states of that call. This is the link between our data provider and the interface.
3) ‘future‘ Parameter: Here we provide the ‘Future’ instance returned by our method in the provided that will retrieve the data from our server.
4) ‘builder‘ Parameter: A function that has buildcontext and snapshot as parameters, and expects from you to return a widget (built with the provided context) for specific states of the snapshot.
5) Handle Errors: We check for errors and return the corresponding widget for that scenario.
6) Success State: If the data was retrieved successfully, we return a corresponding widget that lists the challenges.
7) Otherwise, we provide a loading view which is an activity indicator centered on the screen.
Let’s drill down on how the list is created:
The method shown returns a widget that will list our challenges on the screen. We use the ‘ListView’ widget, where the ‘itemBuilder’ parameter will dictate how every item on the list is going to look.
This is close to what a ‘UITableViewCell’ is for iOS or whatever ‘ViewHolder’ you return on your RecyclerView adapter for Android. The main difference is that it doesn’t need to inherit from a specific class to be used as a list item, bringing, in consequence, a homogeneous and reusable process for creating the interface.
Until now everything is very declarative, clear and compact.
Ok, forget about those last two. Here’s the snippet of how a piece of our row looks in code:
As seen above, you can get messy very quickly when stacking/nesting widgets in order to create a simple piece of UI.
There are a couple of tools and concepts you can use to avoid this, like theming objects and widget encapsulation, but here we wanted to bring another hot topic to the table, which is…
One interesting and useful feature of Flutter is Hot Reload. It enables live preview of the changes you make on UI code without rebuilding your project as you would normally do on Android Studio or Xcode.
Based on our experience, Hot Reload seems to work great and save you a lot of time during UI development. If Flutter didn’t have this feature it would take ages to create your UI and the lack of a designer would make crashing into UI development on Flutter a very frustrating process.
Coming from the native world with interface designers (like the iOS’ IB) and constraint-based layouts, the process of creating UI on Flutter had a steep learning curve. There were little to none concepts I would use from our experience with native development, plus there’s a lot of widgets that will probably solve something you want to solve manually you don’t know about.
Thanks to hot reload, vast documentation and increasing community, the good old trial-and-error speeds the process and smoothes the learning curve.
Last, but not least: State Management
This is the topic that had been on discussion with Flutter since the beginning. It is known that Flutter relates to a unidirectional data flow pattern, mostly found on reactive programming.
On native development, we are probably more used to use either one or a combination of MVC, MVP, or MVVM design patterns depending on your taste and needs. Even though there are examples of parts of the community trying to find a way to use these patterns in Flutter, Google encourages devs to stick with some flavor of reactive programming implemented by different approaches like BLoC / Rx, MobX, Redux or Provider.
The Provider approach is the one that Google is pushing as the most suitable solution if you are not sure about what to use. There’s also a Google I/O specific talk about this approach and how this existing Flutter library could help you to move your current approach to Provider.
Choosing the right state management pattern will depend on the application you are trying to build and how experienced or comfortable are you as a developer with the approach, but coming from native development we found that all of the approaches seem to be (highly) coupled to the UI code, envisioning a challenge if you want to migrate from one to the other once your app is on a mature state.
You can start playing with state management using this tutorial from the Flutter documentation and explore different approaches described in the List of state Management approaches also provided by the official documentation.
What about Flutter’s Performance?
When we read about Skia and the rendering approach, we were hesitant about how the UI would perform. After playing with Flutter a while, UI performance went beyond our expectations and proved to be a solid solution. Having said that, you should be careful about how you build your UI, the less you re-draw on state changes, the better; and note apps will perform much faster after you build your solution for release.
We also ran a few tests in order to see how a Flutter app would perform in terms of device resources. For a small app, we noticed a slightly increment on computation load and in the number of threads, but in terms of memory, the Flutter implementation was using more than 100% additional RAM memory compared to the native app.
Last, but not least, packages’ size it’s also bigger for Flutter applications than in natives’.
The End of our journey?
As an emerging technology, Flutter promises a good contribution to the multiplatform development world and we’re looking forward to seeing how far it will go.
What are your thoughts about Flutter? Are you looking to start a new mobile app development project based on it? Let’s talk! We’d love to help!