juliansteenbakker / mobile_scanner

A universal scanner for Flutter based on MLKit. Uses CameraX on Android and AVFoundation on iOS.
BSD 3-Clause "New" or "Revised" License
807 stars 470 forks source link

Camera does not work after navigation from one screen to another (Both use MobileScanner) #589

Open oleksii-tatarintsev opened 1 year ago

oleksii-tatarintsev commented 1 year ago

Every time when i am trying to navigate from one screen to another (both screens use MobileScanner), on the second screen i receive white screen if i do not use autostart and starting/stopping controller by myself. If i use autostart, i receive black screen

Package: mobile_scanner: 3.2.0 iOS Device: iPad Pro 12.9 inch (5th generation) iPadOS: 16.4 Android Device: Samsung Galaxy Tab S7 Android version: Android 11

Code:

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

  @override
  State<ScannerOne> createState() => _ScannerOneState();
}

class _ScannerOneState extends State<ScannerOne> {

  final MobileScannerController controller = MobileScannerController(autoStart: false);

  @override
  void initState() {
    controller.start();
    super.initState();
  }

  @override
  void dispose() {
    controller.stop();
    controller.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'scanner 1',
        ),
        leading: IconButton(
            onPressed: () => Modal().show(context),
            icon: Container(
              width: 50,
              height: 50,
              color: Colors.red,
            )),
      ),
      body: MobileScanner(
        controller: controller,
        onDetect: (_) {},
      ),
    );
  }
}

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

  @override
  State<ScannerTwo> createState() => _ScannerTwoState();
}

class _ScannerTwoState extends State<ScannerTwo> {

  final MobileScannerController controller = MobileScannerController(autoStart: false);

  @override
  void initState() {
    controller.start();
    super.initState();
  }

  @override
  void dispose() {
    controller.stop();
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'scanner 2',
        ),
        leading: IconButton(
          onPressed: () => Modal().show(context),
          icon: Container(
            width: 50,
            height: 50,
            color: Colors.red,
          ),
        ),
      ),
      body: MobileScanner(
        controller: controller,
        onDetect: (_) {},
      ),
    );
  }
}

class Modal {
  show(BuildContext context) {
    return showDialog(
      context: context,
      builder: (context) {
        return Dialog(
          child: Column(
            children: [
              CupertinoButton(
                child: const Text('Scanner 1'),
                onPressed: () {
                  Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const ScannerOne(),
                    ),
                  );
                },
              ),
              CupertinoButton(
                child: const Text('Scanner 2'),
                onPressed: () {
                  Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(
                      builder: (context) => const ScannerTwo(),
                    ),
                  );
                },
              )
            ],
          ),
        );
      },
    );
  }
}

Console: output:

#1      MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:315:18)
<asynchronous suspension>
#2      EventChannel.receiveBroadcastStream.<anonymous closure> (package:flutter/src/services/platform_channel.dart:674:9)
<asynchronous suspension>

while de-activating platform stream on channel dev.steenbakker.mobile_scanner/scanner/event

flutter: mobile_scanner: not starting automatically because autoStart is set to false in the controller.
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: MobileScannerException: code genericError, message: Called start() while already started!
#0      MobileScannerController.start (package:mobile_scanner/src/mobile_scanner_controller.dart:210:7)
<asynchronous suspension>

Actual result when autostart false: IMG_0614

Actual result when autoStart true: IMG_0613

Flutter doctor:

[✓] Flutter (Channel stable, 3.7.7, on macOS 13.2.1 22D68 darwin-arm64, locale en-GB)
    • Flutter version 3.7.7 on channel stable at /Users/aleksey/Development/SDK/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 2ad6cd72c0 (5 weeks ago), 2023-03-08 09:41:59 -0800
    • Engine revision 1837b5be5f
    • Dart version 2.19.4
    • DevTools version 2.20.1

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    • Android SDK at /Users/aleksey/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.3)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14E222b
    • CocoaPods version 1.12.0

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2022.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.15+0-b2043.56-8887301)

[✓] IntelliJ IDEA Community Edition (version 2022.3.3)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin version 73.0.3
    • Dart plugin version 223.8888

[✓] Connected device (3 available)
    • iPad (mobile) • 00008103-000248A82E53001E • ios            • iOS 16.4 20E5229e
    • macOS (desktop)         • macos                     • darwin-arm64   • macOS 13.2.1 22D68 darwin-arm64
    • Chrome (web)            • chrome                    • web-javascript • Google Chrome 112.0.5615.49

[✓] HTTP Host Availability
    • All required HTTP hosts are available

https://user-images.githubusercontent.com/101318767/231444263-4b8b314c-9320-478c-a85c-0a1848d6ac3c.mov

iulianxpopa commented 1 year ago

@oleksii-tatarintsev have you found any workaround? I'm in the same exact situation

iJack93 commented 1 year ago

Have you tried with what mentioned in this other issue? https://github.com/juliansteenbakker/mobile_scanner/issues/539#issuecomment-1483740222 It works for me calling controller.stop() in the initState method

  @override
  void initState() {
    controller.stop(); ---> call stop here
    super.initState();
  }
eggnogdev commented 1 year ago

I was just running into a very similar issue and have found a workaround that works for me.

instead of defining the MobileScannerController in the Scanner class definition, pass it through as a parameter upon instantiation.

this example code may be easier to understand.

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Column(
          children: [
            TextButton(
              child: Text("BARCODE SCANNER"),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => ScannerScreen(
                      controller: MobileScannerController(
                        autoStart: true,
                      ),
                    ),
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

class ScannerScreen extends StatelessWidget {
  const ScannerScreen({
    super.key,
    required this.controller,
  });

  final MobileScannerController controller;

  @override
  Widget build(BuildContext context) {
    return MobileScanner(
      onDetect: (data) {
        controller.stop();

        Navigator.pop(context);
      },
      controller: controller,
    );
  }
}

as you can see, I create a new controller each time the ScannerScreen is navigated to, and pass it through as a parameter of ScannerScreen.

ogdans3 commented 9 months ago

None of the workarounds seemed to work. What ended up working was adding a delay between stopping and starting the controller:

  @override
  void initState() {
    controller.stop();
    Future.delayed(const Duration(milliseconds: 1000), () => controller.start());
    super.initState();
  }

Not a great solution, but atleast it works. Im running this on a OnePlus Nord N100 (cheap, old phone) in debug mode. Im guessing that the timeout can be shorter on a newer phone running a production build.

ArturDias13 commented 8 months ago

None of the workarounds seemed to work. What ended up working was adding a delay between stopping and starting the controller:

  @override
  void initState() {
    controller.stop();
    Future.delayed(const Duration(milliseconds: 1000), () => controller.start());
    super.initState();
  }

Not a great solution, but atleast it works. Im running this on a OnePlus Nord N100 (cheap, old phone) in debug mode. Im guessing that the timeout can be shorter on a newer phone running a production build.

I think you could just await the controller to stop

await controller.stop(); await controller.start(); OR

await controller.stop().whenComplete(() => controller.start());

juliandambrosio commented 5 months ago

In my case, when scanning the QR I go to a new screen where I perform an action and make a pop to return to the QR scan. The following code worked for me, for some reason Future.delayed is a must; otherwise it doesn't work. Remember to set autoStart: false

final MobileScannerController mobileScannerController = MobileScannerController(autoStart: false);

@override
void initState() {
  super.initState();
  _startCamera();
}

@override
void dispose() {
  super.dispose();

  mobileScannerController.stop();
  mobileScannerController.dispose();
}

Future<void> _startCamera() async {
  Future.delayed(const Duration(seconds: 1), () async => await mobileScannerController.start());
}