Skip to content

FlutterでWillPopScopeからPopScopeへの移行方法

問題の背景

WillPopScopeウィジェットは、Flutterアプリで物理的な戻るボタン(Android)やスワイプジェスチャによるナビゲーションをカスタマイズするために長く使用されてきました。しかし、Flutterバージョン3.12.0-1.0.pre以降、このウィジェットは**非推奨(deprecated)**となりました。代わりにPopScopeウィジェットの使用が推奨されています。

非推奨の警告が表示される典型的なコード例:

dart
WillPopScope(
  onWillPop: () async {
    // カスタムロジック
    return false; // 戻るナビゲーションをブロック
  },
  child: Scaffold(...),
)

この変更の主な理由は:

  1. 新しいAndroidの予測可能な戻るナビゲーションとの互換性向上
  2. API設計の簡素化とエラーの削減
  3. ナビゲーション結果の扱いをより直感的に

基本移行方法

WillPopScopeの代わりにPopScopeを使用する基本的な移行パターン:

dart
PopScope(
  canPop: false, // 戻る操作を制御するフラグ
  onPopInvokedWithResult: (didPop, result) {
    if (!didPop) {
      // カスタムロジックを実行
      // 戻る操作を許可する場合はNavigator.pop()を呼び出す
    }
  },
  child: Scaffold(...),
)

主要プロパティの説明

  • canPop:
    • falseに設定すると、戻る操作がデフォルトでブロックされる
    • trueの場合は標準の戻る動作が実行される
  • onPopInvokedWithResult:
    • didPop: 実際にポップ操作が発生したかどうかを示す
    • result: ナビゲーターから返される結果データ(Navigator.pop(result)で設定)

実践的な移行例

ケース1: 戻る操作を完全にブロック

戻るボタンを無効化するシンプルな例:

dart
PopScope(
  canPop: false,
  onPopInvokedWithResult: (didPop, result) {},
  child: Scaffold(...),
)

ケース2: 確認ダイアログを表示

戻る前にユーザー確認を求める実装:

dart
Future<bool> _showConfirmDialog(BuildContext context) async {
  return await showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('確認'),
      content: Text('本当に終了しますか?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context, false),
          child: Text('キャンセル'),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, true),
          child: Text('終了'),
        ),
      ],
    ),
  ) ?? false;
}

@override
Widget build(BuildContext context) {
  return PopScope(
    canPop: false,
    onPopInvokedWithResult: (didPop, result) async {
      if (didPop) return;
      
      final shouldPop = await _showConfirmDialog(context);
      if (shouldPop && context.mounted) {
        Navigator.pop(context); // ページを閉じる
      }
    },
    child: Scaffold(...),
  );
}

重要

onPopInvokedWithResult内でcontextを使用する場合、非同期処理中の画面破棄を考慮してcontext.mountedチェックを必ず行いましょう

ケース3: WebViewでの戻る操作制御

WebView内のナビゲーション履歴を扱う例:

dart
final InAppWebViewController webViewController;

PopScope(
  canPop: false,
  onPopInvokedWithResult: (didPop, result) async {
    if (await webViewController.canGoBack()) {
      webViewController.goBack(); // WebView内で戻る
    } else if (context.mounted) {
      Navigator.pop(context); // WebViewを閉じる
    }
  },
  child: WebViewWidget(...),
)

高度な使用法

ダブルタップでアプリ終了

戻るボタン2回タップでアプリ終了するパターン:

dart
class DoubleTapExit extends StatefulWidget {
  const DoubleTapExit({super.key});

  @override
  State<DoubleTapExit> createState() => _DoubleTapExitState();
}

class _DoubleTapExitState extends State<DoubleTapExit> {
  DateTime? lastBackPressedTime;

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, result) {
        if (didPop) return;

        final now = DateTime.now();
        final gap = lastBackPressedTime == null
            ? const Duration(hours: 1)
            : now.difference(lastBackPressedTime!);

        if (gap < const Duration(seconds: 2)) {
          SystemNavigator.pop(); // アプリ終了
        } else {
          lastBackPressedTime = now;
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('もう一度タップで終了'),
              duration: Duration(seconds: 2),
            ),
          );
        }
      },
      child: Scaffold(...),
    );
  }
}

注意点とベストプラクティス

バージョン互換性

  • PopScopeはFlutter 3.7以降で利用可能
  • Flutter 3.16以降ではonPopInvokedが非推奨となり、onPopInvokedWithResultに完全移行

間違った実装

以下のような非推奨APIの使用は避けましょう:

dart
// 非推奨: 古いPopScopeの使用法
PopScope(
  onPopInvoked: (didPop) {} // Flutter 3.16以降では非推奨
)

// 非推奨: WillPopScopeは完全に廃止
WillPopScope(...)

公式ドキュメント

詳細は公式移行ガイドを参照: Migrating from WillPopScope to PopScope

まとめ

  • WillPopScopeからPopScopeへの移行は必須
  • canPoponPopInvokedWithResultの組み合わせで柔軟な制御が可能
  • ダイアログ表示やWebView制御など、高度なユースケースも対応可能
  • コンテキストの生存状態(mounted)チェックを忘れずに
  • 常に最新のFlutterバージョンと公式ドキュメントを参照

新しいPopScope APIは予測可能なナビゲーションを実現し、Androidの最新機能との互換性を向上させます。移行作業は現在のコードベース規模に関わらず、比較的シンプルに行えるでしょう。