5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Flutterで遷移した先から前の画面に戻ったときのダイアログを消したい

Posted at

アプリ開発をしている時、リストのセルをタップして動作確認のダイアログでOKかキャンセルかを表示した後、処理を走らせて終わったら次の画面に遷移させるのってどうやるのかなって思って色々調べたのでそのまとめとして書きました。

完成動画

Dialogのボタンから処理をして、その後別画面に遷移させようと思った時、ローディング画面がダイアログの背面に移ってしまい、さらに遷移先から戻ってきた時にも、ダイアログが表示されたままになってしまっている。

ダイアログが戻ってきた時にも表示されたままの問題は遷移の方法をpushからpushReplaceに変えればいい。

けど、ローディング画面がダイアログの背面に回ってしまうのはどうにかしたい。
これの何が問題かと言うと、ローディング中に何度もダイアログのボタンを押せてしまい、バグの元になってしまう。

AnyConv.com__test1.gif

こっちがそれらの問題を解決した方。

ダイアログのOKを押した後にダイアログを閉じて、その後ローディング。
それが終わると次の画面に遷移する。

これなら、先ほどの問題を解決できる

AnyConv.com__test.gif

全容

まずこれがサンプルの全体のコード。
やっていることは、リストを表示させてそのセルをタップしたらダイアログを表示。そこで「OK」を押せば_refresh_処理が走り、終わったら次のページに遷移。もし、「キャンセル」が押されたら何もせずにダイアログを閉じると言う簡単なもの。

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {

  List<int> listItem = [1, 2, 3];
  bool isLoading = false;

  Future<void> _refresh() async {
    // ローディング
    setState(() {
      isLoading = true;
    });

    // 1秒待つ
    await Future.delayed(Duration(seconds: 1));

    // 新しいリストを代入
    setState(() {
      listItem = listItem.map((f) => f + 3).toList();
    });

    // 1秒待つ
    await Future.delayed(Duration(seconds: 1));

    // ロード終わり
    setState(() {
      isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Scaffold(
          appBar: AppBar(title: Text('Dialog'),),
          body: ListView.builder(
            itemBuilder: (context, index) {
              return GestureDetector(
                onTap: () async {
                  final _isRefresh = await showDialog(
                    context: context,
                    builder: (context) {
                      return AlertDialog(
                        title: Text('${listItem[index]}'),
                        actions: <Widget>[
                          FlatButton(
                            child: Text('ok'),
                            onPressed: () => Navigator.of(context).pop(true),
                          ),
                          FlatButton(
                            child: Text('キャンセル'),
                            onPressed: () => Navigator.of(context).pop(false),
                          ),
                        ],
                      );
                    }
                  );
                  if(_isRefresh) {
                    await _refresh();
                    Navigator.of(context).push(MaterialPageRoute(builder: (context) => MySecondScreen()));
                  }
                },
                child: Card(
                  child: Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: Center(child: Text('INDEX = ${listItem[index]}')),
                  ),
                ),
              );
            },
            itemCount: listItem.length,
          ),
        ),
        Visibility(
          visible: isLoading,
          child: DecoratedBox(
            decoration: BoxDecoration(
              color: Color(0x44000000),
            ),
            child: Center(child: const CircularProgressIndicator()),
          ),
        ),
      ],
    );
  }
}

class MySecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dialog'),),
      body: Center(
        child: Text('Second Screen')
      ),
    );
  }
}

解説

上から一つずつみていくと

  Future<void> _refresh() async {
    // ローディング
    setState(() {
      isLoading = true;
    });

    // 1秒待つ
    await Future.delayed(Duration(seconds: 1));

    // 新しいリストを代入
    setState(() {
      listItem = listItem.map((f) => f + 3).toList();
    });

    // 1秒待つ
    await Future.delayed(Duration(seconds: 1));

    // ロード終わり
    setState(() {
      isLoading = false;
    });
  }

これは1秒待った後にリストを更新して、さらに1秒待つという関数で、実際の通信とかを再現して作った関数。
ローディングの変数とリストの変数のsetState()以外特に何もやってない。

次は、GestureDetector内のonTapの処理部分

final _isRefresh = await showDialog(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: Text('${listItem[index]}'),
      actions: <Widget>[
        FlatButton(
          child: Text('ok'),
          onPressed: () => Navigator.of(context).pop(true),
        ),
        FlatButton(
          child: Text('キャンセル'),
          onPressed: () => Navigator.of(context).pop(false),
        ),
      ],
    );
  }
);
if(_isRefresh != null && _isRefresh) {
  await _refresh();
  Navigator.of(context).push(MaterialPageRoute(builder: (context) => MySecondScreen()));
}

まず最初にshowDialogの結果を_isRefreshという変数に代入しています。

showDialogNavigator.of(context).push(...)と同じようにawaitで待つことで遷移先から戻ってきた時の結果などを返してくれる。

この場合だと

actions: <Widget>[
  FlatButton(
    child: Text('ok'),
    onPressed: () => Navigator.of(context).pop(true),
  ),
  FlatButton(
    child: Text('キャンセル'),
    onPressed: () => Navigator.of(context).pop(false),
  ),
],

Navigator.of(context).pop(true)Navigator.of(context).pop(false)の部分でbool型の値が返され、その結果がawaitして待っている_isRefresh変数に代入されるという感じになっています。
※ ダイアログの背景を押して、閉じた場合には何も返さないので注意

こうすることでダイアログでOKが押されたのかキャンセルが押されたのかを判別できるようにして、_refresh関数を走らせるかの判定をできるようにしています。

if(_isRefresh != null && _isRefresh) {
  await _refresh();
  Navigator.of(context).push(MaterialPageRoute(builder: (context) => MySecondScreen()));
}

ここは前のshowDialogの部分でtrueが返ってきた(OKが押された)時だけ_refresh関数を走らせて、その後次の画面に遷移させるようにしています。

nullチェックをしているのは、ダイアログの背景を押されたら何も返ってこないため

最後に
これは直接は関係していないですが、ローディングする部分になっています

Visibility(
  visible: isLoading,
  child: DecoratedBox(
    decoration: BoxDecoration(
      color: Color(0x44000000),
    ),
    child: Center(child: const CircularProgressIndicator()),
  ),
),

Visibilityvisibleの部分で表示、非表示を切り替えています。
本来の通信ではローディングがいるだろうと思って追加しました。

終わりに

Flutterのダイアログの表示方法など最初は慣れるのに苦労したけど、今回の件を通してちょっと分かった気がする。
もっといい方法があるなどがあったらコメントで教えていただけると、すごく助かります

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?