FlutterのNavigatorで画面遷移

Flutter の画面遷移では [Navigator](https://docs.flutter.io/flutter/widgets/Navigator-class.html) を使用します。
[Navigator](https://docs.flutter.io/flutter/widgets/Navigator-class.html) は、ウィジェットをスタックで管理します。


Navigator　を使用して以下のような画面遷移を実現してみたいと思います。
*※ボックスが画面、矢印が画面遷移、(赤文字)が操作イベント、[青文字]がNavigatorのメソッドです。*

![Flutter Navigator.png](https://qiita-image-store.s3.amazonaws.com/0/12177/f3a39f68-0452-f510-042e-d6f2b76cf9bc.png)


- スプラッシュやログイン画面の様な一方通行の画面遷移の場合は、[pushReplacement](https://docs.flutter.io/flutter/widgets/NavigatorState/pushReplacement.html) を使用します。
この場合、ログイン後はホーム画面がスタックのルートの画面になります。

  ```dart
  Navigator.of(context).pushReplacementNamed("/home");
  ```

- ホーム画面からその他画面、またその他画面への遷移し戻ることが出来るようにする場合は、次の画面への遷移に [push](https://docs.flutter.io/flutter/widgets/Navigator/push.html)、画面を戻るときは [pop](https://docs.flutter.io/flutter/widgets/Navigator/pop.html)　を使用します。ダイアログもウィジェットであるため同じです。
[push](https://docs.flutter.io/flutter/widgets/Navigator/push.html)　された画面では [pop](https://docs.flutter.io/flutter/widgets/Navigator/pop.html) することが可能であれば、バックオペレーションが可能になります。※アクションバーの戻るボタン

  ```dart
  Navigator.of(context).pushNamed("/next");
  ```

- スタックされたその他画面から、すべての画面をクリアしてホーム画面へ戻りたい場合は、[popUntil](https://docs.flutter.io/flutter/widgets/NavigatorState/popUntil.html) を使用します。　[popUntil](https://docs.flutter.io/flutter/widgets/NavigatorState/popUntil.html) は条件に一致するまでスタックから画面をpopして行きます。第二引数の predicate にPopを止めたい条件を指定します。

  ```dart
  Navigator.popUntil(context, ModalRoute.withName("/home"));
  ```

- ダイアログを表示する場合は、[showDialog](https://docs.flutter.io/flutter/material/showDialog.html)を使用します。showDialog の内部コードでは、[push](https://docs.flutter.io/flutter/widgets/Navigator/push.html) が呼ばれています。ダイアログを閉じる場合は、[pop](https://docs.flutter.io/flutter/widgets/Navigator/pop.html) を使用します。

  ```dart
  showDialog<bool>(
    context: context,
    builder: (BuildContext context) {
      return new AlertDialog(
        content: const Text('Do you want logout?'),
        actions: <Widget>[
          new FlatButton(
            child: const Text('No'),
            onPressed: () {
              Navigator.of(context).pop(false);
            },
          ),
          new FlatButton(
            child: const Text('Yes'),
            onPressed: () {
              Navigator.of(context).pop(true);
            },
          ),
        ],
      );
    },
  );
  ```
 
- ログアウトで、全ての画面を破棄してスプラッシュを表示する場合は、
[pushAndRemoveUntil](https://docs.flutter.io/flutter/widgets/NavigatorState/pushAndRemoveUntil.html) を使用します。[pushAndRemoveUntil](https://docs.flutter.io/flutter/widgets/NavigatorState/pushAndRemoveUntil.html) は条件に一致するまでスタックから画面を除いて行きます。その後画面を pushします。第二引数の predicate を false にすることで全て消されることになります。

  ```dart
  Navigator.pushAndRemoveUntil(
    context,
    new MaterialPageRoute(
      builder: (context) => new Splash()),
      (_) => false);
  ```

以下は全体のコードになりますので、動かしてみて下さい。

## 全体コード

```dart
import 'dart:async';

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    title: 'Navigation with Routes',
    routes: <String, WidgetBuilder>{
      '/': (_) => new Splash(),
      '/login': (_) => new Login(),
      '/home': (_) => new Home(),
      '/next': (_) => new Next(),
    },
  ));
}

// ---------
// スプラッシュ
// ---------
class Splash extends StatefulWidget {
  @override
  _SplashState createState() => new _SplashState();
}

class _SplashState extends State<Splash> {
  @override
  void initState() {
    super.initState();

    new Future.delayed(const Duration(seconds: 3))
        .then((value) => handleTimeout());
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        // TODO: スプラッシュアニメーション
        child: const CircularProgressIndicator(),
      ),
    );
  }

  void handleTimeout() {
    // ログイン画面へ
    Navigator.of(context).pushReplacementNamed("/login");
  }
}

// ---------
// ログイン画面
// ---------
class Login extends StatefulWidget {
  @override
  _LoginState createState() => new _LoginState();
}

class _LoginState extends State<Login> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: const Text("Login"),
      ),
      body: new Center(
        child: new Form(
          child: new SingleChildScrollView(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: new Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                const SizedBox(height: 24.0),
                new TextFormField(
                  decoration: const InputDecoration(
                    border: const UnderlineInputBorder(),
                    labelText: 'User Id',
                  ),
                ),
                const SizedBox(height: 24.0),
                new TextFormField(
                  maxLength: 8,
                  decoration: new InputDecoration(
                    border: const UnderlineInputBorder(),
                    labelText: 'Password',
                  ),
                ),
                const SizedBox(height: 24.0),
                new Center(
                  child: new RaisedButton(
                    child: const Text('Login'),
                    onPressed: () {
                      // TODO: ログイン処理
                      // ホーム画面へ
                      Navigator.of(context).pushReplacementNamed("/home");
                    },
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

// ---------
// ホーム画面
// ---------
class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: const Text("Home"),
      ),
      body: new Center(
        child: new RaisedButton(
          child: const Text("Launch Next Screen"),
          onPressed: () {
            // その他の画面へ
            Navigator.of(context).pushNamed("/next");
          },
        ),
      ),
    );
  }
}

// ---------
// その他画面
// ---------
class Next extends StatefulWidget {
  @override
  _NextState createState() => new _NextState();
}

class _NextState extends State<Next> {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: const Text("Next"),
      ),
      body: new Center(
        child: new Column(
          children: <Widget>[
            const SizedBox(height: 24.0),
            new RaisedButton(
              child: const Text("Launch Next Screen"),
              onPressed: () {
                // その他の画面へ
                Navigator.of(context).pushNamed("/next");
              },
            ),
            const SizedBox(height: 24.0),
            new RaisedButton(
              child: const Text("Home"),
              onPressed: () {
                // ホーム画面へ戻る　
                Navigator.popUntil(context, ModalRoute.withName("/home"));
              },
            ),
            const SizedBox(height: 24.0),
            new RaisedButton(
              child: const Text("Logout"),
              onPressed: () {
                // 確認ダイアログ表示
                showDialog<bool>(
                  context: context,
                  builder: (BuildContext context) {
                    return new AlertDialog(
                      content: const Text('Do you want logout?'),
                      actions: <Widget>[
                        new FlatButton(
                          child: const Text('No'),
                          onPressed: () {
                            // 引数をfalseでダイアログ閉じる
                            Navigator.of(context).pop(false);
                          },
                        ),
                        new FlatButton(
                          child: const Text('Yes'),
                          onPressed: () {
                            // 引数をtrueでダイアログ閉じる
                            Navigator.of(context).pop(true);
                          },
                        ),
                      ],
                    );
                  },
                ).then<void>((aBool) {
                  // ダイアログがYESで閉じられたら...
                  if (aBool) {
                    // 画面をすべて除いてスプラッシュを表示
                    Navigator.pushAndRemoveUntil(
                        context,
                        new MaterialPageRoute(
                            builder: (context) => new Splash()),
                        (_) => false);
                  }
                });
              },
            ),
          ],
        ),
      ),
    );
  }
}
```

## 参考
- Navigate to a new screen and back - Flutter https://flutter.io/cookbook/navigation/navigation-basics/
- Navigator class - widgets library - Dart API https://docs.flutter.io/flutter/widgets/Navigator-class.html
- [Flutterの画面遷移 \- Qiita](https://qiita.com/tatsu/items/38cd85efd93005b95af9)
- [FlutterでIntent\.FLAG\_ACTIVITY\_CLEAR\_TOPてきなことをやる \- Qiita](https://qiita.com/YusukeIwaki/items/0f5f753c63f763f9a9c9)