Flutter扫码弹窗Navigator.pop多次触发致多退一页求助
问题描述
我在Flutter中实现了一个以AlertDialog形式展示的条码扫码功能,扫码能正常返回预期值,但遇到一个问题:扫码完成后,Navigator.pop()不仅关闭了弹窗,还莫名退回了一个路由。我怀疑是监听器触发过快,导致Navigator.pop()被多次执行,但不知道如何阻止这种情况。
我的Alert Dialog类代码
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; import 'package:qr_scanner_overlay/qr_scanner_overlay.dart'; Future<String?> showQrScannerDialog({required BuildContext context}) async { return showDialog<String>( context: context, barrierDismissible: false, builder: (BuildContext context) { return QrScannerDialog(); }, ); } class QrScannerDialog extends StatefulWidget{ const QrScannerDialog({super.key}); @override _QrScannerDialogState createState() => _QrScannerDialogState(); } class _QrScannerDialogState extends State<QrScannerDialog> with WidgetsBindingObserver { bool found = false; MobileScannerController controller = MobileScannerController(); StreamSubscription<Object?>? _subscription; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _subscription = controller.barcodes.listen(_handleBarcode); unawaited(controller.start()); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (!controller.value.isInitialized) { return; } switch (state) { case AppLifecycleState.detached: case AppLifecycleState.hidden: case AppLifecycleState.paused: return; case AppLifecycleState.resumed: _subscription = controller.barcodes.listen(_handleBarcode); unawaited(controller.start()); case AppLifecycleState.inactive: unawaited(_subscription?.cancel()); _subscription = null; unawaited(controller.stop()); } } @override Widget build(BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), side: BorderSide( color: Theme.of(context).colorScheme.primary, width: 2), ), title: Text("QR Scanner"), content: AspectRatio( aspectRatio: 1, child: Container( decoration: BoxDecoration( border: Border.all( color: Theme.of(context).colorScheme.primary, width: 2, ), borderRadius: BorderRadius.circular(10), ), child: Center( child: Stack( children: [ MobileScanner( controller: controller, ), QRScannerOverlay( borderColor: Colors.red, borderRadius: 16, ), ], ), ), ), ), actions: <Widget>[ TextButton( onPressed: () { Navigator.of(context).pop(); }, child: Text('Abbrechen'), ), ]); } @override Future<void> dispose() async { WidgetsBinding.instance.removeObserver(this); unawaited(_subscription?.cancel()); _subscription = null; super.dispose(); await controller.dispose(); } void _handleBarcode(Object? barcode) { if (barcode == null && found) return; print((barcode as BarcodeCapture).barcodes.first.rawValue); if (barcode is BarcodeCapture) { found = true; BarcodeCapture capture = barcode; if(capture.barcodes.isEmpty) return; Navigator.of(context).pop(capture.barcodes.first.rawValue); } } }
调用弹窗的代码
Future<void> addObjectByBarcode(BuildContext context) async { String? barcode = await showQrScannerDialog(context: context); print(barcode); if (barcode != null) { try { OpenFoodFactsApi api = OpenFoodFactsApi(); Product? product = await api.searchByBarcode(barcode); if (product != null) { print(product.productName); } } catch (e, s) { print(e); print(s); print('Fehler beim Abrufen des Produkts: $e'); CustomSnackBar.show(context, 'Fehler beim Abrufen des Produkts: $e'); } } else { CustomSnackBar.show( context, 'Barcode konnte nicht richtig gescannt werden.'); } }
解决方案
核心问题确实是扫码监听器会连续多次触发_handleBarcode方法,加上生命周期方法的逻辑错误,导致Navigator.pop()被多次执行。可以通过以下几点修复:
1. 扫码成功后立即停止扫描并取消订阅
在_handleBarcode方法中,一旦成功获取条码,就停止扫描控制器并取消流订阅,避免后续触发:
void _handleBarcode(Object? barcode) { if (found || barcode == null) return; if (barcode is BarcodeCapture) { final capture = barcode; if (capture.barcodes.isEmpty) return; // 标记已找到,停止扫描并取消订阅 setState(() => found = true); unawaited(_subscription?.cancel()); unawaited(controller.stop()); // 仅执行一次pop操作 Navigator.of(context).pop(capture.barcodes.first.rawValue); } }
2. 修复生命周期方法的逻辑错误
原didChangeAppLifecycleState的switch分支缺少break,会导致状态切换时执行多余代码,比如从resumed切换到inactive时,会先执行resumed的代码再执行inactive的,造成重复订阅和取消:
@override void didChangeAppLifecycleState(AppLifecycleState state) { if (!controller.value.isInitialized) { return; } switch (state) { case AppLifecycleState.detached: case AppLifecycleState.hidden: case AppLifecycleState.paused: return; case AppLifecycleState.resumed: // 仅在无订阅时重新订阅 if (_subscription == null) { _subscription = controller.barcodes.listen(_handleBarcode); unawaited(controller.start()); } break; case AppLifecycleState.inactive: unawaited(_subscription?.cancel()); _subscription = null; unawaited(controller.stop()); break; } }
3. 调整dispose方法的执行顺序
确保先取消订阅、释放控制器,再执行父类的dispose:
@override Future<void> dispose() async { WidgetsBinding.instance.removeObserver(this); unawaited(_subscription?.cancel()); _subscription = null; await controller.dispose(); super.dispose(); }
这样修改后,扫码成功后会立即停止扫描并切断事件流,确保Navigator.pop()只执行一次;同时修复了生命周期的逻辑漏洞,避免重复订阅和执行多余操作,就能解决多退路由的问题。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

