Flutter: What's the fuss about?

JC

September 13, 2018

Published by Joshua Colley

Bright HR SVG

Flutter - what's the fuss about?

So, if you’re reading this post, you’ve probably already heard some of the hype surrounding Google’s Flutter SDK. If not, let me shed some light on the subject. Flutter is a new, open-source SDK created by Google for the purpose of simplifying cross-platform development of mobile apps. Initially released in May 2017, Flutter made the headlines when the Hamilton Broadway musical developed and released their app using the platform. Taking only 3 months to develop, the app, which features on both the App Store and Play Store, has passed 1 million installations across platforms.

In this post, I would like to give a little introduction to the platform and the language it uses, in addition to sharing my experience learning and prototyping with it.

"What is Dart?"

If you’ve never heard of Dart before, I can assure you, you’re not alone. Dart is a C-style object-orientated programming language developed by Google in 2011. As the brain child of Lars Bak & Kasper Lund, the Dart language was originally intended to bring structured programming to the web. As a result, Dart can be compiled into JavaScript, allowing any web browser to run it. But where does Flutter come in? Dart is also capable of ‘Ahead of Time’ compilation into native machine code. Flutter allows developers to deploy AOT-compiled Dart code to the app stores.

Declaring a variable:

String _hello = "Hello";
String world = "World";

Declaring a method:

void _saySomething(String text) {
         print(text);
}

As you can see from the example, Dart shares similarities with both Java and Objective-C. (NOTE: the underscore prefixing the variable & method names is used to declare the property as private.)

For further information on Dart, please check out the Docs - https://www.dartlang.org

"How does Flutter work?"

If you’ve ever used React Native, then you’re probably familiar with how cross-platform tools usually work. React Native for example uses a JavaScript bridge to interact with both native widgets and devices services like the Camera and Location services. However, as a result of the reactive nature, widgets can be accessed up to 60 times per second (during animations & transitions). As you can imagine, this is taxing and can cause performance issues. This is where Flutter is designed to shine. Although Flutter uses the same reactive-style views as React Native, it takes advantage of Dart’s AoT compilation to provide native code at runtime. This means there is no need for a JavaScript bridge.

At this stage, you may be asking, "how do I access native widgets & services? "

The answer, as it turns out, is fairly interesting. Flutter does not access native widgets. Instead, it builds custom widgets within a native canvas (i.e. UIViewController & Activity/Fragment). This method allows re-rendering of the subtree of widgets to occur much quicker, thus providing the user with an overall smoother experience.

In order to access services in Flutter, we need to create a ‘Method Channel’. A method channel allows us to call native methods and pass data to and from our Dart code.

Flutter Interactions

Flutter Interactions

React Native Interactions

React Native Interactions

Method Channels

To setup a method channel, all we need to do is declare a MethodChannel property with the name of our method channel. The name here is "versionChannel" and is provided as a string within the MethodChannel constructor. This name will be used within out native code to reference the desired channel.

final versionChannel = const MethodChannel("versionChannel");

Once the channel is setup, we can use it to call a method in our native code. To do so, we can create an asynchronous method that uses the versionChannel property to invoke the desired method. Again, the string provided here will be used to call the native method.

Future<dynamic> getVersion() async {
       return await versionChannel.invokeMethod("getVersion");
}

With our Dart code setup, let’s take a look at the native setup. Here I’m going to show you the iOS implementation using Swift 4.0, however, the same basic principles are applicable in Objective-C and in both Kotlin & Java for Android.

Within our iOS AppDelegate’s didfinishLaunchingWithOptions method, we need to setup a FlutterViewController and our FlutterMethodChannel.

let controller: FlutterViewController = window?.rootViewController as! FlutterViewController

let versionChannel = FlutterMethodChannel.init(name: "versionChannel", binaryMessenger: controller)

(NOTE: the name parameter is exactly the same as that in out Dart code)

The next step is to set out method call handler.

versionChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
      if call.method == "getVersion" {
             self.getVersion(result: result)
      }
}

Here, you can see that we are checking which method wants to be called. This is because we can call multiple methods using the same method channel. The FlutterResult object passed as a parameter to our getVersion method is a completion handler defined by Flutter that contains the value returned to our Dart code. The last step is to create our native getVersion method. All we are going to do in this method is return the apps version number from the main bundle.

private func getVersion(result: @escaping FlutterResult) {
      result(Bundle.main.releaseVersion)
}

And that’s all there is to it. We can now use the result of our native method directly in our Dart code. We can also pass data to our native methods by providing a [String: Any] dictionary as a parameter in the invokeMethod() call. This dictionary is the accessed in Swift by using call.arguments (which is optional) within the setMethodCallHandler method.

Set State

As I mentioned earlier, Flutter utilises a Reactive architecture. This means that the widget DOM is static. In order to change the ‘state’ of the DOM we need to re-render it with our new requirements. To achieve this, we need to create a StatefulWidget. In Flutter, pretty much everything is a Widget, including our screens.

class HomePage extends StatefulWidget {
      final String title;

      HomePage(this.title);

      @override
      _HomePageState createState() => _HomePageState();
}

This is a very basic setup for our screen widget. As you can see, our title property is final (convention for properties within a Widget), the value of which is assigned in our constructor. We also have an override method that provides our widget with a new state. Within this state will be our widget tree.

class _HomePageState extends State<HomePage> {
      int _value = 0;

      void _incrementCounter() {
               setState(() {
                       _value++;
               });
      }

      // Our Widget Tree
      @override
       Widget build(BuildContext context) {
              return Scaffold(
                    appBar: AppBar(
                          title: Text(widget.title),
                     ),
                     body: Center(
                           child: Text('$_counter')
                     ),
                     floatingActionButton: FloatingActionButton(
                             onPressed: _incrementCounter,
                              child: Icon(Icons.add),
                     ),
                );
         }
}

Within our _HomePageState class, we have a private property _value that gets updated whenever the user taps our button. The value out the _value property is displayed in the middle of our screen. If we look closer at the _incrementCounter() method that is called when the user taps the FAB, we can see that is calls setState(). In essence, all the setState() method does is execute the code block then rebuild the widget tree using the newly assigned properties. Of course this is a very simple example and your app will have a lot more complexity within it. You may even want to use ‘global’ properties that are shared throughout your app. To avoid passing updated values around our navigation stack, we can use wrap our Main App Widget in an InheretedWidget, this allows us to access and update properties throughout out app.

"What else does Flutter provide?"

Hot Reload

As a mobile developer, many hours a week are wasted by the need to constantly rebuild the app, even for simple one-line string changes. Google have provided a brilliant way of allowing developers to test code changes without having to wait ages for a rebuild. Their ‘Hot Reload’ mechanism only rebuild the areas of code that you have changed meaning it’s a lot faster to view changes and also allows your build to keep its current state.

UI Styling

As I’ve mentioned, Flutter comes with its own set of widgets to remove the need of a bridge. Google have provided two complete sets of widgets which allow you to create native looking apps. The Cupertino widget set provides you with everything you would expect if you were building an iOS app natively. The look and feel of the components from the navigation bars to the segment controllers is indistinguishable to the user from the ‘real thing’. You are also provided with Material widgets, these mimic that of a native Android application but have the potential to be extended and customised to suit your personal needs. This allows you to create a modern, unique style that fits your business.

My experience with Flutter so far

After a few months of experimenting with Flutter, I can confidently say it’s a great platform for prototyping & building PoCs. That being said however, as a native mobile developer I found that there’s a steep learning curve when it comes to the reactive architecture Flutter adheres to. I tried experimented with some common place structures like MVVM & VIP to make my code a little less entangled, but they caused more problems than they solved. Although I believe there are many great things about Flutter, some of which I’ve mentioned above, I continuously get the feeling that it’s a work in progress. Although I have the ability to access native code, I would like to be able to access device services directly within Dart and not have to jump between (at least) 3 different languages.

To summarise, Flutter has the potential to be a great alternative to native development in the future. Although challenging at times, the basics are relatively straight forward and the hot reload is a massive time saver, especially when tweaking the UI. I would strongly recommend giving Flutter a try.

Registered Office: Bright HR Limited, The Peninsula, Victoria Place, Manchester, M4 4FB. Registered in England and Wales No: 9283467. Tel: 0844 892 3928. I Copyright © 2024 BrightHR