💻

[Flutter] onPressedの処理中は押せなくなるようなボタンを作る

2020/08/13に公開

やりたいこと

以下のように、ボタンを押すと何らかの処理が行われるという実装を考えてみます。

このサンプルのコードは以下のようになっています。

import 'package:flutter/material.dart';

class Sample extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Builder(
        builder: (BuildContext context) => Center(
          child: RaisedButton(
            child: Text('送信'),
            onPressed: () async {
              await _someAsyncProcess();
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('送信しました'),
              ));
            },
          ),
        ),
      ),
    );
  }
}

Builder() で囲っている理由については

[Flutter] SnackBarを使おうとすると Scaffold.of() called with(略)と言われるときの対応

こちらをご参照ください。

このままだと、ボタンが押されたときの処理を実行している間もボタンが押せる状態になっているため、 ボタンを連打すると処理が並行して複数回実行されてしまいます。

これは色々と困ることがありそうなので、処理中はボタンを押せなくするような実装をしたくなります。

やり方

まず、ボタンを押せなくするには、 onPressednull を渡せばよいです。

- onPressed: () async {
-   await _someAsyncProcess();
-   Scaffold.of(context).showSnackBar(SnackBar(
-     content: Text('送信しました'),
-   ));
- },
+ onPressed: null,

onPressednull だと、以下のようにボタンがグレーアウトして押せなくなります。

参考:dart - How do I disable a Button in Flutter? - Stack Overflow

なので、今回の例であれば RaisedButton をラップした StatefulWidget を作って、処理を実行している間のみ onPressednull に切り替えるという実装をしてあげればよさそうです。

というわけで、そのような実装を WaitableRaisedButton として作ってみましょう。

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

class WaitableRaisedButton extends StatefulWidget {
  
  createState() => _State();

  WaitableRaisedButton({
     this.onPressed,
    this.child,
  });

  final VoidCallback onPressed;
  final Widget child;
}

class _State extends State<WaitableRaisedButton> {
  bool _waiting = false;

  
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: widget.onPressed == null || _waiting
          ? null
          : () async {
              setState(() => _waiting = true);
              await widget.onPressed();
              setState(() => _waiting = false);
            },
      child: widget.child,
    );
  }
}

こんな感じになりました。

RaisedButton の引数のうち、とりあえず毎回使う onPressedchild だけをラップしています。必要に応じて他の引数も渡せるようにしてみてください。

_waiting という状態を持たせて、 onPressed の処理を実行している間だけ _waitingtrue になるようにし、 _waitingtrue の場合は onPressednull が渡るようにしています。

あとは RaisedButton の代わりにこの WaitableRaisedButton を使うだけです👍

- child: RaisedButton(
+ child: WaitableRaisedButton(

これで、連打できないボタンが作れました🙌

本格的に使う場合はライブラリを活用しましょう

ちなみに、 pub.dev を検索してみると素晴らしいライブラリが色々配布されています。

今回は簡単なものを自前で用意する場合の実装例を示しましたが、本格的に使う場合はこの辺りのライブラリを活用するのがよいと思います✋

GitHubで編集を提案

Discussion