Skip to content

BuildContextの非同期ギャップを安全に扱う方法

要点

Flutterでは非同期処理中にBuildContextを使用すると、ウィジェットが既に破棄されている可能性があるため、「Do not use BuildContexts across async gaps」という警告が表示されます。この記事では、この問題の解決方法について解説します。

問題の理解

Flutterアプリケーションで非同期処理を行った後に画面遷移(Navigator.pop()など)を行う場合、非同期処理が完了するまでに元のウィジェットが破棄されている可能性があります。破棄されたBuildContextを使用するとアプリがクラッシュするため、Flutterのlintルールが警告を表示します。

dart
// 問題のあるコード例
MyCustomClass{
  final BuildContext context;
  const MyCustomClass({required this.context});

  myAsyncMethod() async {
    await someFuture();
    Navigator.of(context).pop(); // 警告が発生
  }
}

解決方法

方法1: mountedプロパティでチェックする(推奨)

Flutter 3.7以降では、BuildContextmountedプロパティが追加され、StatefulWidgetだけでなくStatelessWidgetでも使用できるようになりました。

dart
// 安全なコード例
Future<void> myAsyncMethod(BuildContext context) async {
  await someFuture();
  
  if (context.mounted) {
    Navigator.of(context).pop();
  }
}

方法2: StatefulWidget内でのmountedチェック

StatefulWidgetのState内では、mountedプロパティを直接使用できます。

dart
class _MyWidgetState extends State<MyWidget> {
  Future<void> _navigateAfterDelay() async {
    await Future.delayed(const Duration(seconds: 3));
    
    if (!mounted) return;
    Navigator.of(context).push(
      MaterialPageRoute(builder: (context) => const MainScreen())
    );
  }
  
  @override
  Widget build(BuildContext context) {
    // ウィジェットのビルド処理
  }
}

方法3: コールバック関数を使用する

カスタムクラスでBuildContextを直接保存するのではなく、コールバック関数を渡す方法もあります。

dart
class MyCustomClass {
  const MyCustomClass();

  Future<void> myAsyncMethod(
    BuildContext context, 
    VoidCallback onSuccess
  ) async {
    await Future.delayed(const Duration(seconds: 2));
    onSuccess.call();
  }
}

// 使用例
const MyCustomClass().myAsyncMethod(context, () {
  if (!mounted) return;
  Navigator.of(context).pop();
});

方法4: ナビゲーターを事前に取得する

非同期処理の前にナビゲーターインスタンスを取得しておく方法もあります。

dart
myAsyncMethod() async {
  final navigator = Navigator.of(context);
  await someFuture();
  navigator.pop();
}

注意点

context.mountedmountedの違いに注意してください:

  • mounted: StatefulWidgetのStateクラス内でのみ使用可能
  • context.mounted: 任意のBuildContextで使用可能(Flutter 3.7以降)

よくある間違い

以下のような解決策は推奨されません:

dart
// 非推奨: lintルールを無効化する
linter:
  rules:
    use_build_context_synchronously: false

// 非推奨: 安全でないcontextの保存
void onButtonTapped(BuildContext context) async {
  await Future.delayed(const Duration(seconds: 1));
  Navigator.of(context).pop(); // クラッシュの可能性
}

ベストプラクティス

  1. BuildContextの保存を避ける: カスタムクラスにBuildContextを保存するのは避け、必要な場合はメソッドの引数として渡す
  2. 非同期処理後のチェック: 非同期処理後に必ずmountedまたはcontext.mountedをチェックする
  3. 最新のアプローチを採用: Flutterのバージョンに応じた最新の解決方法を使用する

まとめ

BuildContextを非同期ギャップを跨いで使用する場合は、常に安全性を確保する必要があります。Flutter 3.7以降ではcontext.mountedを使用し、それ以前のバージョンでは適切なコールバック手法やmountedチェックを実施しましょう。これにより、アプリのクラッシュを防ぎ、ユーザー体験を向上させることができます。