📝

Don't use 'BuildContext's across async gaps.

2024/04/27に公開2

赤じゃないからと無視し続けてきたけれど

地味に黄色の波線にじりじりと包囲されてきたので、
そろそろ真面目に考えようと思って、documentを読んだ。

ダメな例

void onButtonTapped(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  Navigator.of(context).pop();
}

非同期処理中にcontextって書くなって言われているのはわかるけれど、
じゃあどうするの。
ドキュメントには三つの回避方法が書いてある。
1つ目は、非同期処理から独立させる。

void onButtonTapped(BuildContext context) {
  Navigator.of(context).pop();
}

・・・といったって、そうできないからそうなってるわけで・・・

残りの二つがそっくりなのに微妙に違うので、初級者は困る。

void onButtonTapped(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));

  if (!context.mounted) return;
  Navigator.of(context).pop();
}
abstract class MyState extends State<MyWidget> {
  void foo() async {
    await Future.delayed(const Duration(seconds: 1));
    if (!mounted) return; // Checks `this.mounted`, not `context.mounted`.
    Navigator.of(context).pop();
  }
}

なにが違うんだ!

まずやってみる

とりあえず、両方やってみて、今回は2つ目が効いた。
こんな感じ。

  Future<void> fetchPrincipalByLocation(String keywords) async {
    try {
      List<String> location = keywords.split(',').map((e) => e.trim()).toList();
      listPrincipal = await client.principal.getPrincipal(keywords: location);
      principalIds = listPrincipal.map((item) => item.id as int).toList();

      if (!mounted) return; <= ここをプラス

      if (listPrincipal.length < 5) {
        // データが5件に満たない場合、アラートを表示
        showDialog(
          context: context, <= ここに波線が出ていた
          builder: (BuildContext context) {
            return AlertDialog(  以下略

もう少し理解したい

で、爺様に聞いてみた。その答えが以下だけれど、もしかすると爺様の学習期間以後の変化なのかもしれない。ちょっとためらってる感じがある。

違い
if (!mounted) return; は State クラス内部で使われ、その State オブジェクトがまだウィジェットツリーに存在するかどうかをチェックします。
if (!context.mounted) return; は、ウィジェットの BuildContext がまだ有効かどうかをチェックします。ただし、このメソッドはFlutterの標準APIには存在しないため、使用する前に特定のフレームワークがこの機能を提供しているかを確認する必要があります。

突き詰めると先に進めないので、とりあえず今回はここまで

Flutter大学

Discussion

osakiosaki

if (!context.mounted) return; は、ウィジェットの BuildContext がまだ有効かどうかをチェックします。ただし、このメソッドはFlutterの標準APIには存在しないため、

爺様へ教えてあげてください。存在しますよ、と。
https://api.flutter.dev/flutter/widgets/BuildContext/mounted.html

BuildContextのmountedプロパティは、Flutter 3.7.0で追加されました。3.7なので、1年以上前だと思います。
https://docs.flutter.dev/release/release-notes/release-notes-3.7.0

Add BuildContext.mounted by @goderbauer in https://github.com/flutter/flutter/pull/111619

それまではこのプロパティがなかったため、StatelessWidget内でmountedを確認する術が無かったのですね。

どんぐりどんぐり

ありがとうございます!Flutterは成長が早いので、ときどき爺様の歳を感じます笑