You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter扫码弹窗Navigator.pop多次触发致多退一页求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:34:53