Flutterでページ遷移するTODOアプリを作ってみよう

![Simulator Screen Shot - iPhone SE (2nd generation) - 2020-08-23 at 14 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/8eb6a53d-a31f-f473-2244-00b1ec29d27e.jpeg)


# 🧑‍💻 まえがき

## ■やること
- dart&flutterにて、必要最低限の実装内容で TODO アプリに画面遷移を追加
- modelクラスを追加
- あと、ちょこちょこリファクタ


## ■やらないこと
- ~~画面遷移、ルーティング変更~~
- TODO の編集、削除、並び替え
- ライブラリの追加
- 永続化
- etc

## ■作業概要
- STEP1：前回のおさらい
- STEP2：実装
- STEP3：動作確認

## ■下記の症状に効能があります
- とりあえず最低限の実装で動く TODO アプリを作ったので、画面遷移を学びたい
- modelを準備したい


## ■環境
- editer：VScode
- flutter：1.17.5
- Dart：2.8.4

## ■広告
codmonで働いてます。デザイナーさん&エンジニアさん積極採用中。
子育て環境をよりよくしたいかたwanted!
最近、モバイルアプリの採用もはじめました！
qiita見ました！っと言ってくれるとお祝い金がでるかも。
https://www.wantedly.com/projects/468828


## ■完成コード
https://github.com/TD3P/flutter_mytodo/tree/prot_2pagesView/prod

## ■事前準備はこちらを参照

- Flutter開発環境構築
    - https://flutter.dev/docs/get-started/install
- Flutterの新規プロジェクトの作成
    - https://flutter.dev/docs/get-started/test-drive?tab=terminal
- Flutterで基本のTODOアプリを作ってみよう
    - https://qiita.com/pe-ta/items/b3b7458059c1fd7efcf0

# 👀STEP1：前回のおさらい

## ■前回はなにやったっけ？
- 1ファイルで必要最低限のTODOアプリを作成した
- 画面遷移、ルーティング、モデルクラス作成はしていなかった

## ■前回の課題感
- 1ファイルではなく、フォルダを分けたい
- 画面遷移+ルーティング設定したい
- スタイルのちょっと微妙なところを修正

# 🛠STEP2：実装

## ■新規ファイル作成
- こんな感じでフォルダとファイルを切る

![スクリーンショット 2020-08-23 12.30.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63060/be2ec993-04ab-15cd-848a-bd1b2daf4419.png)

## ■モデル作成
- 下記の内容で追記
- @requiredをつけると、引数を必須扱いします

```dart:todoModel.dart
import 'package:flutter/material.dart';

class TodoModel {
  String title;

  TodoModel({
    @required this.title,
  });
}
```

## ■ルーティング設定
- MyAppクラスの変更
- initialRoute：最初にどこの画面を表示するかの設定
- route：各種クラスに名前をつける
- importに先ほど追加したファイルを追加

```dart:main:dart
import 'package:flutter/material.dart';
+ import 'package:mytodo/View/todoList.dart';
+ import 'package:mytodo/View/addTodo.dart';

// アプリの起動
void main() {
  runApp(MyApp());
}

// TOPページ起動
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MY TODO',
      theme: ThemeData(
        primarySwatch: Colors.teal,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
+      initialRoute: '/',
+      routes: {
+        '/': (_) => TodoList(),
+        '/add': (_) => AddTodo(),
      },
    );
  }
}
```

## ■todoList.dartを実装
- todoListにmain.dartのMyHomePageクラス以下を移植
- MyHomePageの部分をtodoListに置き換え
- 先ほど追加したmodelクラスをimportに追加
- 不要パーツの削除とwidgetの整理
- listcardを切り離し


```dart:todoList.dart
import 'package:flutter/material.dart';
import 'package:mytodo/Model/todoModel.dart';

// TOPページ
class TodoList extends StatefulWidget {
  TodoList({Key key}) : super(key: key);

  @override
  _TodoListState createState() => _TodoListState();
}

// TOPページのステータス管理
class _TodoListState extends State<TodoList> {
  // todoの一覧リスト変数を用意
  List<TodoModel> todoList = [];

  @override
  Widget build(BuildContext context) {
    // scaffoldは画面構成の基本widget
    return Scaffold(
      // 背景色
      backgroundColor: Colors.teal[100],

      // アプリ上部のコンテンツ設定
      appBar: AppBar(
        title: Text("TODO一覧"),
      ),

      // アプリのコンテンツ部分の設定
      body: ListView.builder(
        // リストの長さを計算
        itemCount: todoList.length,
        itemBuilder: (BuildContext context, int index) {
          return _listCard(index);
        },
      ),

      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await Navigator.of(context).pushNamed('/add');
          if (result != null) {
            setState(() {
              todoList.add(result);
            });
          }
        },
        child: const Icon(
          Icons.add,
        ),
      ),
    );
  }

  Widget _listCard(index) {
    return Card(
      margin: EdgeInsets.only(top: 6.0, right: 8.0, bottom: 0.0, left: 8.0),
      color: Colors.cyan[600],
      child: ListTile(
        leading: Icon(Icons.star),
        title: Text(
          // リストに表示する文字列を設定
          ("$index : ${todoList[index].title}"),
          style: TextStyle(
            fontSize: 24,
            color: Colors.white,
          ),
        ),
      ),
    );
  }
}
```
## POINT解説：戻ってきたときに、値を受け取る
- 下記のコードで遷移先から戻ってきたときに、値を受け取ることができます
- async(非同期処理)で、囲み、await以下の結果が終了した際の結果（渡された値）をresultに代入
- todoListの配列に値を追加
- さらに、setStateで囲むことによって画面のリストを再描画します
- もちろん、遷移先のaddTodo.dartで戻る際に値を渡す処理を追加しましょう

```dart
onPressed: () async {
  final result = await Navigator.of(context).pushNamed('/add');
  if (result != null) {
    setState(() {
      todoList.add(result);
    });
  }
},
```

## ■addTodo.dartの実装
- コントローラー、textField、保存ボタンなどを引越し
- textFieldでエンターを押した時の動作と、保存ボタンを押した時の動作を共通化
- 上部にパーツが寄ってたので、上下中央に配置
- 画面が遷移する際に、textFieldの中身を遷移先に渡すように設定

```dart:addTodo.dart
import 'package:flutter/material.dart';
import 'package:mytodo/Model/todoModel.dart';

class AddTodo extends StatefulWidget {
  @override
  _AddTodoState createState() => _AddTodoState();
}

class _AddTodoState extends State<AddTodo> {
  // todoモデルを用意
  TodoModel todoItem;

  // テキストフィールドのコントローラー設定
  // コントローラーの宣言
  TextEditingController _todoInputController;

  // コントローラーの初期化
  void initState() {
    super.initState();
    _todoInputController = TextEditingController();
  }

  // statefulオブジェクトが削除されるときに、参照を削除してくれる
  void dispose() {
    super.dispose();
    _todoInputController.dispose();
  }

  // テキストの内容を渡しつつ画面遷移
  void _addTodo() {
    // 何かしらの入力があるときだけ実行
    if (_todoInputController.text.length > 0) {
      // 配列に入力値を追加
      todoItem = TodoModel(
        title: _todoInputController.text,
      );
      // テキストボックスを初期化
      _todoInputController.clear();
      Navigator.of(context).pop(todoItem);
    }
  }

  @override
  Widget build(BuildContext context) {
    // scaffoldは画面構成の基本widget
    return Scaffold(
      // 背景色
      backgroundColor: Colors.teal[100],

      // アプリ上部のコンテンツ設定
      appBar: AppBar(
        title: Text("TODO追加"),
      ),

      // アプリのコンテンツ部分の設定
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.all(20.0),
              child: TextField(
                controller: _todoInputController,
                maxLength: 8,
                decoration: InputDecoration(
                  labelText: 'TODO title',
                  hintText: '入力してください',
                  icon: Icon(Icons.done),
                ),
                autofocus: true,
                onEditingComplete: _addTodo,
              ),
            ),
            Padding(
              padding: EdgeInsets.only(
                  top: 0.0, right: 0.0, bottom: 30.0, left: 0.0),
              child: RaisedButton(
                color: Colors.teal[400],
                textColor: Colors.white,
                child: Text('保存'),
                onPressed: _addTodo,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
```

## POINT解説：画面遷移時に値を渡す
- 下記のコードで遷移時に値を渡しています
- pop()に任意の引数を渡すことで、戻り先に値を渡すことができます
- また、今回は、textFieldと保存ボタンで処理を共通化させるためにfunction化してあります

```dart
void _addTodo() {
  // 何かしらの入力があるときだけ実行
  if (_todoInputController.text.length > 0) {
    // 配列に入力値を追加
    todoItem = TodoModel(
      title: _todoInputController.text,
    );
    // テキストボックスを初期化
    _todoInputController.clear();
    Navigator.of(context).pop(todoItem);
  }
}
```

## POINT解説：textFieldをリッチにしよう
- textFieldに特定のプロパティを追加することで表示を簡単にリッチにすることができます
- decoration/labelText:タイトルを追加できます
- decoration/hintText:いわゆるプレースホルダーを追加できます
- decoration/icon:先頭にアイコンを追加できます
- maxLength:最大文字数制限を付けられます
- onEditingComplete：エンター時にアクションを設定できます

```dart
child: TextField(
  controller: _todoInputController,
  maxLength: 8,
  decoration: InputDecoration(
    labelText: 'TODO title',
    hintText: '入力してください',
    icon: Icon(Icons.done),
  ),
  autofocus: true,
  onEditingComplete: _addTodo,
),
```

# 🙌STEP3：動作確認

- これで完成！
- flutter run で動作確認をしましょう！
- いやぁ〜、flutterって本当に便利ですね🎬


# 🎊🤸‍🎉Congratulations!🎉🤸‍🎊
完成したら、いいね！&コメントに「ワッフルワッフル」と叫んでください😆✨

# 📗参考資料📗
- Flutter公式ドキュメント
    - https://api.flutter.dev/flutter/widgets/widgets-library.html
- マテリアルデザインドキュメント
    - https://material.io/

# 🌟ご指摘大歓迎🌟
ここちゃうで！！っていうとこあったら、教えてもらえると嬉しいです！！

# シリーズ情報
📱Flutterで基本のTODOアプリを作ってみよう
https://qiita.com/pe-ta/items/b3b7458059c1fd7efcf0

📱Flutterでページ遷移するTODOアプリを作ってみよう
https://qiita.com/pe-ta/items/e547c4cf460319f5093c