Flutter is cool! A serious contender to React Native!

May 2017 ยท 3 minute read

Last week I made a small post about React Native. This week I’ve had my head around Flutter. Flutter is a new mobile app SDK to help developers and designers build modern mobile apps for iOS and Android. So it’s the same idea as React Native. Both projects are exceedingly similar. Flutter is written using the Dart language.

A long story short; I like flutter A LOT already. Now I’ve tried React Native I can say that I like Flutter the best of these two. Granted Flutter is very alpha at this stage, but I really dig the API that’s available right now. No markup. Just pure UI work with a vast amount of classes. There’s a class (or widget) for almost every thing. It is simple yet very powerful. The thing I really like about Flutter is its UI API actually results in beatiful Material design interfaces right off the bat. No boilerplate or undocumented mumbo jumbo you get with the current official Android SDK. I get the feeling that Flutter is what the Android SDK should be like today. A modern SDK without all the boilerplate.

Another thing Flutter does right is debugging and hot-reloading. This is not the case with React Native; I’ve experienced a lot crashes due to errors while hot-reloading code. Resulting in the need to completely relaunch your app in debug mode.

Head over to Flutter to get yourself started. Yet again I rewrote some of my TV Lige Nu! app. Below are some screenshots of what I’ve accomplished in 5-8 hours time with Flutter (while completely new to Dart and the Flutter SDK);

The above interface is done in-code like this;

@override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(title: new Text(widget.title), actions: [
        new IconButton(
            icon: new Icon(Icons.alarm, color: Colors.white),
            onPressed: _onAlarmsPressed),
        new IconButton(
            icon: new Icon(Icons.star, color: Colors.white),
            onPressed: _onFavoritesPressed),
        new IconButton(
            icon: new Icon(Icons.settings, color: Colors.white),
            onPressed: _onSettingsPressed),
      ]),
      body: new RefreshIndicator(
          child: new ListView(
            padding: new EdgeInsets.symmetric(vertical: 8.0),
            children: _overviews.map((Overview overview) {
              return new ChannelItem(
                overview: overview,
                onPressed: (Overview overview) {
                  _onChannelPressed(context, overview);
                },
              );
            }).toList(),
          ),
          onRefresh: _fetchChannels),
      floatingActionButton: new FloatingActionButton(
        onPressed: _onSearchPressed,
        child: new Icon(Icons.search),
      ),
      bottomNavigationBar: new FilterNavigationBar(),
    );
  }
}

Please appreciate the boilerplate-free and concise code.

Each material card is represented using the ChannelItem-class;

@override
  Widget build(BuildContext context) {
    var length = overview.airTimeNext.millisecondsSinceEpoch -
        overview.airTime.millisecondsSinceEpoch;
    var progress = overview.airTimeNext.millisecondsSinceEpoch -
        new DateTime.now().millisecondsSinceEpoch;
    double percent = (((length - progress) * 100) / length) / 100;
    return new Container(
        padding: const EdgeInsets.fromLTRB(5.0, 5.0, 5.0, 2.0),
        child: new Hero(tag: overview, child: new Card(
            child: new InkWell(
                onTap: () {
                  onPressed(overview);
                },
                child: new Container(
                    padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 20.0),
                    child: new Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        new Text(overview.channelName,
                            style: new TextStyle(
                              color: Colors.black54,
                              fontSize: 20.0,
                              fontWeight: FontWeight.w400,
                            )),
                        new Padding(padding: const EdgeInsets.all(5.0)),
                        new Row(
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: [
                            new Text(DateHelper.hoursMinutes(overview.airTime),
                                style: new TextStyle(
                                    color: Colors.grey,
                                    fontSize: 15.0,
                                    fontWeight: FontWeight.w300)),
                            new Padding(padding: const EdgeInsets.all(5.0)),
                            new Text(overview.programNameNow,
                                style: new TextStyle(
                                    color: Colors.black54,
                                    fontSize: 19.0,
                                    fontWeight: FontWeight.w300)),
                          ],
                        ),
                        new Padding(padding: const EdgeInsets.all(2.5)),
                        new LinearProgressIndicator(value: percent),
                        new Padding(padding: const EdgeInsets.all(5.0)),
                        new Row(
                          mainAxisAlignment: MainAxisAlignment.end,
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: [
                            new Text(DateHelper.hoursMinutes(overview.airTimeNext),
                                style: new TextStyle(
                                    color: Colors.grey,
                                    fontSize: 15.0,
                                    fontWeight: FontWeight.w300
                                )),
                            new Padding(padding: const EdgeInsets.all(5.0)),
                            new Text(overview.programNameNext,
                                style: new TextStyle(
                                    color: Colors.black54,
                                    fontSize: 16.0,
                                    fontWeight: FontWeight.w300)),
                          ],
                        ),
                      ],
                    ))))));
  }

It’s clean and fairly easy to read. In my opinion. Please observe the Hero-class at the top. Wrapping a widget in this widget Flutter will magically animate from and to this widget with the same tag. This means that any widget with the same tag will be animated to its new position upon pushing a new widget into view.