[Flutter入門(3)] Write your first Flutter app, part 2をやってみる
Flutterの公式サイトに、チュートリアルよりも前にやってみる用のコンテンツとして Write your first Flutter app というものが用意されています。
前回の記事 ではこれのPart 1を解説したので、今回は続きである Part 2 の解説をしていきたいと思います。
Step 1:イントロダクション
Part 2では以下の内容を学ぶらしいです。
- iOS、Android、Webでよくあるような見た目のアプリをFlutterで作る方法
- より素早い開発サイクルのためにホットリロードを使う方法
- Statefulウィジェットをインタラクティブにする方法
- 別の画面へのナビゲートを作成する方法
- アプリの見た目を「テーマ」を使って変更する方法
と原文に書いてあるんですが、ホットリロードについてはこのパートで特別詳しく書かれている様子はなかったです😅
出来上がるアプリはこのようなものになります。

Step 2:Flutter自体のセットアップ
Write your first Flutter app, part 1 の内容を参考にFlutter自体のセットアップを済ませておきます。
Step 3:アプリのベースを準備する
Write your first Flutter app, part 1 を終えている状態を前提として、続きで開発していくので、先にPart 1を終えておきましょう。
いつもどおり、iOSシミュレータを立ち上げて、アプリを起動したら、準備完了です。
$ open -a Simulator
$ cd startup_namer
$ flutter run
Step 4:リストにアイコンを追加する
まず、リストの各行にハートのアイコンを追加します。(次のステップで、アイコンをタップするとお気に入りとして保存されるような実装をしていきます)
_RandomWordsState クラスに _saved という Set 型のクラス変数を追加します。この Set にユーザーがお気に入りに追加した単語ペアを保存する想定です。
Set 型は List 型と違って同じものを重複して登録できないので、ここでのニーズにはより適しています。
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
+ final _saved = Set<WordPair>();
final _biggerFont = TextStyle(fontSize: 18.0);
続いて、 _buildRow() メソッドにおいて各行が保存済みかどうかを判別できるよう、 alreadySaved という変数を用意しておきます。
また、各行にハートアイコンを追加しておきます。次のステップで alreadySaved が機能するようになると、お気に入りに追加されているかどうかによってアイコンの見た目が変化します。
Widget _buildRow(WordPair pair) {
+ final alreadySaved = _saved.contains(pair);
+
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
+ trailing: Icon(
+ alreadySaved ? Icons.favorite : Icons.favorite_border,
+ color: alreadySaved ? Colors.red : null,
+ ),
);
}
この時点で見た目は以下のようになっています。

Step 5:インタラクティブにする
リストの各行をタップしたときに _saved 変数の中身を更新してお気に入りの追加・削除をできるようにします。
_buildRow() メソッドで ListTile を作る際に、 onTap 引数にクロージャーを渡すことでタップ時に何らかの処理を実行させることができます。
ここではウィジェットのステートを変更したいので、ただ _saved 変数を更新するだけでなく、 setState() を呼び出すことでフレームワークに状態の変更を通知する必要があります。
setState()を呼び出すと、ウィジェットのbuild()メソッドが内部的に呼び出され、結果としてウィジェットのUIが再描画されます。
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
+ onTap: () {
+ setState(() {
+ if (alreadySaved) {
+ _saved.remove(pair);
+ } else {
+ _saved.add(pair);
+ }
+ });
+ },
);
}
これで、行をタップすることでアイコンの色がトグルするようになりました。

Step 6:画面遷移を実装する
今度は画面遷移を実装してみましょう。
Flutterでは、 Navigator というクラスが画面遷移のスタックを管理してくれます。 Navigator のスタックにルートをpushすることでそのルートへの画面遷移が発生し、逆にスタックからルートをpopすることで1つ前の画面に戻ることができます。
まずは、 _RandomWordsState クラスの build() メソッドを修正して、 AppBar にリストアイコンを追加しましょう。
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Startup Name Generator'),
+ actions: [
+ IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
+ ],
),
body: _buildSuggestions(),
);
}
リストアイコンをタップすると _pushSaved() というクラスメソッドが実行されるように指定しています。ではこの _pushSaved() メソッドを実装していきましょう。
_RandomWordsState クラスに以下のように _pushSaved() メソッドを追加します。
void _pushSaved() {
}
とりあえずこれで AppBar の右端にリストアイコンが表示されますが、この時点ではタップしても特に何も起こりません。これから _pushSaved() メソッドの中身を実装して、タップしたときに次の画面へ遷移して、お気に入り一覧画面を表示するようにしていきます。
まずは以下の内容まで書きましょう。
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
// 遷移先の画面を表すウィジェットを作ってreturnする
},
),
);
}
-
Navigatorのpush()メソッドを実行して、新しいルートをスタックに追加する - 追加するルートは
MaterialPageRouteクラスのインスタンスで、そのコンストラクタ引数builderにクロージャーを渡して、そこで次の画面のウィジェットを作る
という流れになります。
では、次の画面を表すウィジェットを実際に構築するコードを書き足してみましょう。
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
+ final tiles = _saved.map(
+ (WordPair pair) {
+ return ListTile(
+ title: Text(
+ pair.asPascalCase,
+ style: _biggerFont,
+ ),
+ );
+ },
+ ).toList();
+
+ return Scaffold(
+ appBar: AppBar(
+ title: Text('Saved Suggestions'),
+ ),
+ body: ListView(children: tiles),
+ );
},
),
);
}
-
_savedリストをmapで回して、保存済みの単語ペアを元にListTileのリストを作る - 上記で作った
ListTileのリストをbodyとするようなScaffoldウィジェットを作ってreturnする
ということをしています。
これで、リストアイコンをタップすると画面遷移が発生して以下のようなお気に入りリストが表示されるようになります。

この時点で機能としては十分なのですが、さらにもう一手間加えてこのリストに仕切り線を入れるようにします。
Part 1では「リストの偶数番目の要素なら仕切り線を入れる」という処理を自力で書きましたが、ここではもっと簡単な方法を用います。
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
- ).toList();
+ );
+ final divided = ListTile.divideTiles(
+ context: context,
+ tiles: tiles,
+ ).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved Suggestions'),
),
- body: ListView(children: tiles),
+ body: ListView(children: divided),
);
},
),
);
}
ListTile.divideTiles() という静的メソッドを利用して、 ListTile のリストを元に 仕切り線を入れた状態の ListTile のリスト を作成しました。
これで、下図のように仕切り線が入った状態でリストが表示されて見やすくなりました👍

Step 7:テーマを使ってUIを変更してみる
最後に、FlutterのMaterialデザイン実装における テーマ という機能を使って、UIの見た目を切り替えるということをやってみましょう。
Flutterでは、何も指定しなければデフォルトのテーマが適用されますが、ThemeData クラス を設定することで自由にカラースキームを変更することができます。
例えば以下のように MyApp の build() メソッドを修正してみてください。
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
+ theme: ThemeData(
+ primaryColor: Colors.white,
+ ),
home: RandomWords(),
);
}
}
AppBarなどの色が白に変わったはずです。

primaryColor に Colors.white を指定したことによって、Flutterで用意されている各種ウィジェットにおいて「ここは primaryColor の色を付ける」と定義されているところがまとめて白に変わったわけです。
このように、ThemeData クラス で定義されている様々な変数を上書きしてあげることで、UIの見た目を統一感を保ったままカスタマイズすることができます。
Discussion