【Flutter】GestureDetectorを使用したカスタムボタンで async/await にならないとき
よく、ボタンでこういうことやりたいですよね。
MyCustomButton(
onTap: () async {
await doSomething();
},
)
でも待ってください、
このボタンがカスタムWidgetの場合、async/await にならないことがあります。
たとえば、次のようにGestureDetectorを使用していて、onTapが Function()? 型で定義されていると、
onTap の中で非同期処理を実行できないです。
class MyButton extends StatelessWidget {
final Function()? onTap;
const MyButton({this.onTap});
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: ...
);
}
}
これを async/await に対応させるには、
コールバックを Future を返す関数 に変更する必要があります。
class MyButton extends StatelessWidget {
final Future<void> Function()? onTap;
const MyButton({this.onTap});
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
// 二重タップ防止などの処理をここに入れられます
await onTap?.call();
},
child: ...
);
}
}
Future<void> Function()は、AsyncValueCallbackでも表せるので、
以下でもOKです。
class MyButton extends StatelessWidget {
final AsyncCallback? onTap;
const MyButton({this.onTap});
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
// 二重タップ防止などの処理をここに入れられます
await onTap?.call();
},
child: ...
);
}
}
まとめ
カスタムWidgetのコールバックを Function() にしてしまうと、
async/await が使えず非同期処理が正しく動かないことがあります。
そのため、非同期を考慮した汎用的な設計をするなら、
AsyncCallback?(または Future<void> Function()?)を使うのが良いと思います。
カスタムWidgetにFunctionのパラメータ作る時は、とりあえずFutureにしとくのが良いと思っています。
カスタムじゃなくてFlutter標準のWidgetだとFuture FunctionもただのFunctionもどちらも渡せるのでややこしいんですよね。
よくonTapがFutureなっていないカスタムボタン系Widgetを見かけるので、皆さんも気をつけてください!
追記
Future明示していないFunctionをカスタムボタンに渡しているのにasync/awaitできているっぽい挙動になっているコードがあったので見てみたら、中身がCupertinoButtonだった。
GestureDetectorで完全カスタムボタンにしている場合だけの現象のようですね。
Discussion
しずみやさん
先日の記事に引き続き、コメント失礼致します。
記事の本題とはずれますが、nullable 関数を呼び出す場合以下の書き方だとコードを減らせます。
onTapがnullだったら呼び出されない想定です。
ありがとうございます、修正しました!
しずみやさんの日々の開発の力になれば幸いです!