Flutterチュートリアルを咀嚼する part2 -routeとnavigator-

part1ではFlutterの概要を掴んだ
part2ではpart1で作ったstartup_namerアプリにスマホアプリによくある機能を搭載する
・アイコンの利用
・いいね機能
・別ページへの遷移

#対象のチュートリアル
https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2/#0

#コピペしてできあがったもの
気に入った名前にいいねができるようになった
![スクリーンショット 2020-11-22 0.01.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283826/ca40b2c9-30b4-5c3c-2290-1537d3bd2166.png)

右上の（名前なんて言うのこれ）を押すと右にスライドしてもう一つのページが表示されるようになった
（いいねしたものだけが表示される）
![スクリーンショット 2020-11-22 0.02.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283826/dde784a7-516c-0d0f-7297-82e22d30b028.png)

#ソースコード

```main.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: ThemeData(
        primaryColor: Colors.white,
      ),
      home: RandomWords(),
    );
  }
}

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

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = Set<WordPair>();
  final _biggerFont = TextStyle(fontSize: 18.0);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

  Widget _buildSuggestions() {
    return ListView.builder(
        padding: EdgeInsets.all(16.0),
        itemBuilder: /*1*/ (context, i) {
          if (i.isOdd) return Divider(); /*2*/

          final index = i ~/ 2; /*3*/
          if (index >= _suggestions.length) {
            _suggestions.addAll(generateWordPairs().take(10)); /*4*/
          }
          return _buildRow(_suggestions[index]);
        });
  }

  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);
          }
        });
      },
    );
  }

  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,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
}
```

順々にみていく

##ハート形アイコンの追加
ハートアイコンと必要な変数の設定

```dart
class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = Set<WordPair>();     // NEW
  final _biggerFont = TextStyle(fontSize: 18.0);
  ...
}
```

```dart
Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair); //NEW
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(   // NEW from here... 
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),                // ... to here.
  );
}
```
###Set
https://api.flutter.dev/flutter/dart-core/Set-class.html
_savedはいいねをした文字をメモリ上に保管するための変数

###contains()
https://api.flutter.dev/flutter/package-collection_collection/PriorityQueue/contains.html
対象が_savedに格納されていたらtrue、そうでない場合はfalseを返す
true/falseはalreadySavedに格納される

###ListTile.trailing
https://api.flutter.dev/flutter/material/ListTile/trailing.html
ListTile（リストの１行分のデータ）のタイトルの後に設定するIcon用のプロパティ


###Icon
https://api.flutter.dev/flutter/widgets/Icon-class.html


####Icons.favorite
https://api.flutter.dev/flutter/material/Icons/favorite-constant.html
色付きのハートアイコン
色はcolor:~のところで赤色としている
alreadySavedがtrueならこのアイコンを赤色に設定している

####Icons.favorite_border
https://api.flutter.dev/flutter/material/Icons/favorite_border-constant.html
色なし枠だけのハートアイコン
alreadySavedがfalseならこちらを設定している

###Colors
https://api.flutter.dev/flutter/material/Colors-class.html
色を司るクラス

####Colors.red
https://api.flutter.dev/flutter/material/Colors/red-constant.html
赤色を引き出せる


##いいね
タップしたらその単語が（メモリ上に）保存され、ハートアイコンが赤色になる

```dart
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: () {      // NEW lines from here...
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else { 
          _saved.add(pair); 
        } 
      });
    },               // ... to here.
  );
}
```

###onTap
https://api.flutter.dev/flutter/cupertino/CupertinoTabBar/onTap.html
アイテムがタップされたときに呼び出されるコールバック。

###setState()
https://api.flutter.dev/flutter/material/ScaffoldFeatureController/setState.html
Flutterにstate(状態)が変わったことを知らせる
これによりStatefulWidgetの状態が変化する
今回はalreadySavedがtrueなら、_savedから対象を取り除き（いいねをやめる）、falseなら追加する

##右ページへの移動

```dart
class _RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}
```

###IconButton
https://api.flutter.dev/flutter/material/IconButton-class.html
押したら(onPressed)反応するアイコンボタン
今回は_pushSavedを呼び出している

####Icons.list
https://api.flutter.dev/flutter/material/Icons/list-constant.html
リスト形のアイコン（画面右上にでているの）


```dart
  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,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        },
      ),
    );
  }
}
```

###Navigator
https://api.flutter.dev/flutter/dart-html/Navigator-class.html
Flutterでは新しいページのことをrouteと呼ぶ。
根っこ(root)ではなく経路とかの意味のルートである。
Navigatorは別のrouteへユーザを導く。
※使い方

```dart
  Navigator.of(context).push(
  );
```

###MaterialPageRoute
https://api.flutter.dev/flutter/material/MaterialPageRoute-class.html
これが右に移動したら現れる新しいページの本体
中身はいいねした単語がリスト表示されている

##全体のテーマを変える
```dart
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: ThemeData(          // Add the 3 lines from here... 
        primaryColor: Colors.white,
      ),                         // ... to here.
      home: RandomWords(),
    );
  }
}
```

###ThemeData
https://api.flutter.dev/flutter/material/ThemeData-class.html
MaterialAppのthemeを変更することでアプリ全体のテーマを変更できる
今回は白くした



これでWidgetの使い方
アイコン、新しいページの生成方法が学べた
基礎を理解できる良いチュートリアルだと思う