[Flutter] Write Your First Flutter App, part 2 (の翻訳)


[write Your First Flutter App, part2](https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2/#0) の日本語訳です。間違えているところなどありましたらご指摘いただければ幸いです。

[Part1の日本語訳](https://qiita.com/kainos/items/1700bf245c72b6b2487d) もあります。

所要時間は43分だそうです。自分は日本語に起こしながらやってたのもあってもっとかかりました。。。:sweat_smile:

## 1. Introduction
Flutter は iOS/Android において高品質な native interface を記録的な時間で作成するためのGoogle製のSDkです。Flutter は既存のコードと共に動作し、世界中の開発者や組織に利用されており、無料でオープンソースです。

この codelab では、基本的な Flutter app にインタラクティブ性を追加します。また、ユーザが遷移できる2つめのページ( route と呼ばれる)も作成します。最後に、 app のテーマ(色)を変更します。この codelab は [part1 の「遅延読み込みList」](https://codelabs.developers.google.com/codelabs/first-flutter-app-pt1/)を拡張するものですが、もし part2 から始めたい場合は part1 のコードがあるのでそこから始めることもできます。


### part2 で学ぶこと

* iOS と Android の両方で自然な見た目の Flutter app の書き方
* より高速な開発サイクルのための hot reload の使い方
* stateful widget にインタラクティブ性を追加する方法
* 2つ目の画面の作り方と遷移の仕方
* theme (テーマ) を使った app の見た目の変更方法

### part2 で作るもの
startup会社のための候補名を、無限リスト上で生成するシンプルな app から始めます。この codelab の最後には、ユーザは、候補名の select/unselect、気に入ったものの保存、ができるようになります。App bar の右上にあるリストアイコンをタップすると、新しいページ ( route と呼ばれる) に遷移します。そのページではお気に入りの名前だけが表示されます。

このGIFアニメが最終的なアプリの挙動です。

![part2_finished.png](https://qiita-image-store.s3.amazonaws.com/0/104872/294f7cf4-b001-490d-24d6-9601e190c0c1.png)


## 2. Flutter 環境の構築
もしまだ Part1 を完了していないのであれば、環境構築のために[Flutter 環境構築](https://codelabs.developers.google.com/codelabs/first-flutter-app-pt1/#1)を見てください。

## 3. 最初の app の入手
もしこの codelab の part1 をすでに終えたなら、すでに 最初の app を持っています。次のステップに進むとよいでしょう。

もしまだ startup_namer を持っていない場合、大丈夫です、次の手順で入手できます。

* シンプルなテンプレート Flutter app を [Getting Started with your first Flutter app](https://flutter.io/get-started/test-drive/#create-app)の手順で作成します。`myapp` の代わりに `startup_namer` に名前を変更してください。
* `lib/main.dart` の内容をすべて消してください。[このファイル](https://gist.githubusercontent.com/Sfshaza/d6f9460a04d3a429eb6ac0b0f07da564/raw/6c4c0b476fd5d584fe0704314703653eff8eca2d/main.dart)の内容で置き換えます。このコードは、候補名を遅延読み込みして表示するリストです。
* English Words パッケージを追加するために `pubspec.yaml` を以下のように編集します。

    ```yaml
    dependencies:
      flutter:
        sdk: flutter

      cupertino_icons: ^0.1.0
      english_words: ^3.1.0    // この行を追加します。
    ```

    English Words パッケージはランダムな単語のペアを生成します。これが startup会社名の候補になります。
* Android Studio の Editor View で `pubspec.yaml` を開きながら、右上に表示される**Package Get**をクリックします。これはプロジェクトにパッケージを取り込みます。コンソールに以下のような出力がされるでしょう。

    ```
    flutter packages get
    Running "flutter packages get" in startup_namer...
    Process finished with exit code 0
    ```

* app を実行します。好きなだけスクロールしてみてください、候補名がずっと提示されるのを確認します。

## 4. List にアイコンを追加
このステップでは、リストの各行にハートアイコンを追加します。次のステップでは、それらをタップ可能にし、お気に入りに保存できるようにします。

* `RandomWordsState` に `_saved` Set を追加します。この Set はユーザがお気に入りに追加した単語を保持します。 正しく実装された Set は要素の重複を許可しないため、 Set は List よりも適しています。

    ```dart
    class RandomWordsState extends State<RandomWords> {
      final List<WordPair> _suggestions = <WordPair>[];
      final Set<WordPair> _saved = new Set<WordPair>();   // この行を追加します。
      final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
    ```

* 単語がまだお気に入りに追加されていないことをチェックするために、`_buildRow()` 関数に `alreadySaved` を追加します。

    ```dart
    Widget _buildRow(WordPair pair) {
      final bool alreadySaved = _saved.contains(pair);  // Add this line.
      ...
    }
    ```

    また、 `_buildRow()` 関数にて、お気に入りのために、ListTile オブジェクトにハートアイコンを追加します。次のステップでこのハートアイコンとのインタラクションを追加します。
* 以下のようにアイコンを追加します。

    ```dart
    Widget _buildRow(WordPair pair) {
      final bool alreadySaved = _saved.contains(pair);
      return new ListTile(
        title: new Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
        trailing: new Icon(   // この行から...
          alreadySaved ? Icons.favorite : Icons.favorite_border,
          color: alreadySaved ? Colors.red : null,
        ),                    // ... この行までを追加します。
      );
    }
    ```

* app を hot reload します。白塗りのハートアイコンが見えるはずです。ただし、このアイコンはまだタップできません。

## 5. インタラクティブ性の追加
このステップでは、ハートアイコンをタップできるようにします。ユーザがタップすると「お気に入り」状態をトグル、つまりその単語をお気に入り保存用の Set に追加/削除します。

このために、 `_buildRow` 関数を編集します。もし単語がすでにお気に入りに追加されていたら、再びタップした際にその単語をお気に入りから削除します。タイルがタップされたとき、関数は Flutter framework に 状態が変わったことを通知するために `setState()` を呼び出します。

* 以下のように `onTap` を追加します。

    ```dart
    Widget _buildRow(WordPair pair) {
      final alreadySaved = _saved.contains(pair);
      return new ListTile(
        title: new Text(
          pair.asPascalCase,
          style: _biggerFont,
        ),
        trailing: new Icon(
          alreadySaved ? Icons.favorite : Icons.favorite_border,
          color: alreadySaved ? Colors.red : null,
        ),
        onTap: () {      // ここから9行を追加します。
          setState(() {
            if (alreadySaved) {
              _saved.remove(pair);
            } else {
              _saved.add(pair);
            }
          });
        },               // ここまで追加します。。
      );
    }
    ```

    > Tip: Flutter の reactive style framework では、 `setState()` の呼び出しが State オブジェクトの `build()` メソッド呼び出しのトリガーになり、その結果が UI に反映されます。

    app を hot reload します。すべての tile がタップでき、お気に入り追加/削除ができます。タイルをタップするとタップした地点から ink splash アニメーションが暗黙的に生成されます。

## 6. 新しい画面への遷移
このステップでは、お気に入りリストを表示する新しいページ( Flutter では route と呼びます)を追加します。どのように home route と new route の移動を行うのかを学びます。

Flutter では、 Navigator が app の route を保持したスタックを管理します。ある route を Navigator のスタックに push すると、表示がその route に更新されます。 Navigator の stack から route を pop すると、表示が 1つ前の route に戻ります。

次に、 `RandomWordsState` の build メソッドの AppBar にリストアイコンを追加します。ユーザがリストアイコンをクリックすると、保存したお気に入り用の新しい route が Navigator に push され、画面が表示されます。

* `build` メソッドにアイコンとそれに対応するアクションを追加します。

    ```dart
    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Startup Name Generator'),
            actions: <Widget>[      // ここから3行追加
              new IconButton(icon: const Icon(Icons.list), onPressed: _pushSaved),
            ],                      // ここまで
          ),
          body: _buildSuggestions(),
        );
      }
      ...
    }
    ```

    > Tip: いくつかの widget のプロパティは1つの widget (`child`) を取り、アクションなどの他のプロパティは widget の配列 (`children`) を取ります。配列は角括弧 `[]` で表現します。

* `RandomWordsState` class に `_pushSaved()` 関数を追加します。

    ```dart
    void _pushSaved() {
    }
    ```

* app を hot reload します。リストアイコンが app bar に表示されるでしょう。このアイコンをタップしてもまだ何も起きません。なぜなら `_pushSaved()` 関数はまだ空だからです。

  次に、 route を作成し Navigator のスタックに追加します。この action は新しい route に表示を変更します。新しいページのコンテンツは MaterialPageRoute の builder プロパティで構築されます。これは匿名関数として記述します。

* 以下のように `Navigator.push` を呼び出します。これは Navigator のスタックに route を push します。 IDE は間違ったコードだと指摘してきますが、次のセクションで修正します。

    ```dart
    void _pushSaved() {
      Navigator.of(context).push(
      );
    }
    ```

    次に、 MaterialPageroute とその builder を追加します。とりあえず、 ListTile を生成するコードを追加します。 ListTile の `divideTiles()` メソッドは 各 ListTile 間に横方向のスペースを追加します。 divided 変数は `toList()` 便利関数によって List に変換された最終的な rows を保持します。

* 次のコードを追加しましょう。

    ```dart
    void _pushSaved() {
      Navigator.of(context).push(
        new MaterialPageRoute<void>(   // ここから20行追加します。
          builder: (BuildContext context) {
            final Iterable<ListTile> tiles = _saved.map(
              (WordPair pair) {
                return new ListTile(
                  title: new Text(
                    pair.asPascalCase,
                    style: _biggerFont,
                  ),
                );
              },
            );
            final List<Widget> divided = ListTile
              .divideTiles(
                context: context,
                tiles: tiles,
              )
              .toList();
          },
        ),                           // ここまで追加します。
      );
    }
    ```

    builder プロパティは Scaffold を返します。これは 新しい route のための app bar を持っていて、名前は 「Saved Suggestions」です。新しい route の body は ListTile を含んだ ListView row で構成されます。この row は divider によって分けられています。

* 水平方向の divider を以下のように追加します。

    ```dart
    void _pushSaved() {
      Navigator.of(context).push(
        new MaterialPageRoute<void>(
          builder: (BuildContext context) {
            final Iterable<ListTile> tiles = _saved.map(
              (WordPair pair) {
                return new ListTile(
                  title: new Text(
                    pair.asPascalCase,
                    style: _biggerFont,
                  ),
                );
              },
            );
            final List<Widget> divided = ListTile
              .divideTiles(
                context: context,
                tiles: tiles,
              )
                  .toList();

            return new Scaffold(         // ここから6行追加
              appBar: new AppBar(
                title: const Text('Saved Suggestions'),
              ),
              body: new ListView(children: divided),
            );                           // ここまで追加
          },
        ),
      );
    }
    ```

* app を hot reload します。いくつかの行を選択してお気に入りに追加し、 app bar のリストアイコンをタップしてみます。新しい route が表示され、お気に入りストが確認できるでしょう。 Navigator が 「Back」ボタンをを app bar に追加していることに着目してください。このように、明示的に Navigator.pop を実装する必要はありません。「Back」ボタンをタップすると home route に戻ります。

## 7. Theme を使った UI の変更
このステップでは、 app の theme (テーマ) を変更します。 theme は app の見た目や雰囲気をコントロールします。実機やSimulatorに依存するデフォルトの theme を使うことも、あなたのブランドを反映させたカスタム theme を利用することもできます。

[ThemeData](https://docs.flutter.io/flutter/material/ThemeData-class.html) class を設定することで簡単に theme を変えることができます。この app は今は default theme を使っていますが、 app の primary color を white に変更してみましょう。

* MyApp class の color の変更

    ```dart
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Startup Name Generator',
          theme: new ThemeData(          // Add the 3 lines from here...
            primaryColor: Colors.white,
          ),                             // ... to here.
          home: new RandomWords(),
        );
      }
    }
    ```

* hot reload してみます。App bar も含めて、全体の背景が白くなりました。

    読者の練習のために、 ThemeData を使ってUIの他の部分も変更してみるとよいでしょう。 Material Library の Colors class が提供するたくさんの色定数を使って遊ぶことができます。そして hot reload により、すばやく簡単に UI を試すことができます。

## 8. よくできました！
Flutter を使った iOS と Android の両方で動作するインタラクティブな app を書きました。この codelab では

* Dart言語でコードを書きました
* より高速な開発のために hot reload を使いました
* stateful widget を実装し、インタラクティブ性を追加しました
* route を作成し、home route と new route の間の遷移ロジックを追加しました
* theme を使った UI の見た目の変更方法を学びました


## 9. 次のステップ


Learn more about the Flutter SDK:

* [Building Layouts in Flutter](https://flutter.io/tutorials/layout/) tutorial
[Add Interactivity](https://flutter.io/tutorials/interactive/) tutorial
* [A Tour of the Flutter Widget Framework](https://flutter.io/widgets-intro/)
* [Flutter for Android Developers](https://flutter.io/flutter-for-android/)
* [Flutter for React Native Developers](https://flutter.io/flutter-for-react-native/)
* [Flutter for Web Developers](https://flutter.io/web-analogs/)

Other resources include:

* [Build Native Mobile Apps with Flutter](https://www.udacity.com/course/build-native-mobile-apps-with-flutter--ud905) (a free Udacity course)
* [Flutter Cookbook](https://flutter.io/cookbook/)
* [From Java to Dart](https://codelabs.developers.google.com/codelabs/from-java-to-dart/#0) codelab
* [Bootstrap into Dart: learn more about the language](https://flutter.io/bootstrap-into-dart/)