skip to Main Content

I have tried to write a barcode scanner in flutter. This is displayed in an alert dialogue). This also works and returns the desired value. However, I have the problem that after the readout the code that executed the AlterDialog runs to the end, but after the navigator.pop() not only the dialogue is closed, but for some reason it goes back one route further.

I suspect that this is due to the fact that the listener is running too fast and the navigator.pop is therefore executed several times, but I don’t know how to prevent this.
Can anyone help me with this?

My Alert Dialog Class:

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();
    // Start listening to lifecycle changes.
    WidgetsBinding.instance.addObserver(this as WidgetsBindingObserver);

    // Start listening to the barcode events.
    _subscription = controller.barcodes.listen(_handleBarcode);

    // Finally, start the scanner itself.
    unawaited(controller.start());
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // If the controller is not ready, do not try to start or stop it.
    // Permission dialogs can trigger lifecycle changes before the controller is ready.
    if (!controller.value.isInitialized) {
      return;
    }

    switch (state) {
      case AppLifecycleState.detached:
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
        return;
      case AppLifecycleState.resumed:
      // Restart the scanner when the app is resumed.
      // Don't forget to resume listening to the barcode events.
        _subscription = controller.barcodes.listen(_handleBarcode);

        unawaited(controller.start());
      case AppLifecycleState.inactive:
      // Stop the scanner when the app is paused.
      // Also stop the barcode events subscription.
        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 {
    // Stop listening to lifecycle changes.
    WidgetsBinding.instance.removeObserver(this);
    // Stop listening to the barcode events.
    unawaited(_subscription?.cancel());
    _subscription = null;
    // Dispose the widget itself.
    super.dispose();
    // Finally, dispose of the controller.
    await controller.dispose();
  }

  void _handleBarcode(Object? barcode) {
    if (barcode == null && found) return;
    print((barcode as BarcodeCapture).barcodes.first.rawValue); //<---- gets shown 5 times bevore the route closes
    if (barcode is BarcodeCapture) {
      found = true;
      BarcodeCapture capture = barcode;
      if(capture.barcodes.isEmpty) return;
      Navigator.of(context).pop(capture.barcodes.first.rawValue); //<-- i think this gets triggered to often 
    }
  }
}

The code that calls the Dialog:

Future<void> addObjectByBarcode(BuildContext context) async {
    // String barcode = await _scan();
    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); //<---- works fine till here but still skippes the page
        }
      } 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.');
    }
  }

2

Answers


  1. First of all, understand that your page and AlertDialog do not have different contexts, because you have returned the AlertDialog directly, so try this instead.

      @override
      Widget build(BuildContext context) {
        Future.delayed(Duration.zero).then((value) => showDialog(
          context: context,
          builder: (context) => AlertDialog(),
        ));
        return Scaffold();
      }
    
    Login or Signup to reply.
  2. You should try to use a synchronised approach.
    The listener triggers the method more often, which means that the Navigator.of(context).pop() is triggered more often. Combined with the suggestion from @Aks, I was therefore able to create the following solution for you.
    The lock.synchronised ensures that the code inside can only be used by one thread at a time. This prevents multiple threads from executing the method at the same time and therefore bypassing the counter.

    int counter = 0;
    var lock = Lock();
    
    void _handleBarcode(Object? barcode) {
        lock.synchronized(() {
          if (barcode == null && found) return;
          if (barcode is BarcodeCapture) {
            counter++;
            found = true;
            BarcodeCapture capture = barcode;
            if (capture.barcodes.isEmpty) return;
            if(counter == 1) {
              Navigator.of(context).pop(capture.barcodes.first.rawValue);
            }
          }
        });
      }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search