As explained in my previous post, Dart can be used to write mobile apps. I want to write a flashcard app for Android.

Setting up the environment

I am using a MacBook. I installed Flutter 0.9.4 beta.
Then as per the docs, I installed Android Studio Preview, installed the Flutter plugin and tried to create an empty Flutter project, but the "Create new Flutter project" menu item is missing. I'm guessing it's a bug since it works in Android Studio 3.2 Stable. Fine.
I also set it up on Visual Studio Code (doc) and was able to create a new project in it.

Codelab

The codelab (link) teaches a few useful things:

  • Create an empty project with one view
  • Use an external package from https://pub.dartlang.org/flutter/
  • Run the app in a virtual machine
  • Stateful vs non stateful widget
  • Set up an infinite scrolling list

Concepts

Everything I'm writing is based on the codelab. I am rephrasing it to force myself to understand it.

Stateless vs Stateful widget

class StatelessWidget {
  build(BuildContext context) → Widget
  createElement() → StatelessElement
}

class StatefulWidget {
  createElement() → StatefulElement
  createState() → State<StatefulWidget>
}

class State<T extends StatefulWidget> {
  build(BuildContext context) → Widget: Describes the part of the user interface represented by this widget. [...]
  deactivate() → void: Called when this object is removed from the tree. [...]
  didUpdateWidget(T oldWidget) → void: Called whenever the widget configuration changes. [...]
  dispose() → void: Called when this object is removed from the tree permanently. [...]
  initState() → void: Called when this object is inserted into the tree. [...]
  setState(void fn) → void
}

The surprising thing is that StatelessWidget (doc) and State (doc) have the build method but StatefulWidget (doc) does not.
In the original example, the state is never persisted because the app only renders once. If the user leaves and comes back, it renders a new word.

Storing state

In the infinite scroll example, we will re-render the words depending on the scroll position, so we need to persist states. I know, this is mostly a copy/paste from the tutorial.

class RandomWords extends StatefulWidget {
  @override
  RandomWordsState createState() => new RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  @override
  Widget build(BuildContext context) {
    return Scaffold (
      appBar: AppBar(
        title: Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions() {
    return ListView.builder(
      ...
      itemBuilder: (context, i) {
        if (i.isOdd) return Divider();
        final index = i ~/ 2;

        // If you've reached the end of the available word pairings...
        if (index >= _suggestions.length) {
          // ...then generate 10 more and add them to the suggestions list.
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      }
    );
  }

  Widget _buildRow(WordPair pair) {
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

The final app Widget structure is like so:

MaterialApp(
  title: 'Startup Name Generator',
  home: Scaffold (
    appBar: AppBar(
      title: Text('Startup Name Generator'),
    ),
    body: ListView.builder(
      ...
      itemBuilder: (context, i) {
        return a Divider() or a ListTile(
          title: Text(
            _suggestions[index].asPascalCase,
            style: _biggerFont,
          ),
        );
      }
    ),
  )
);