rmtmckenzie / flutter_qr_mobile_vision

QR reader plugin using mobile vision API for Flutter.
MIT License
214 stars 185 forks source link

How to completely dispose the widget? #110

Open steve28100 opened 4 years ago

steve28100 commented 4 years ago

Hello, the camera is works fine but doesn't work when come back to the same screen again. Example: Home menu -> Barcode menu (works fine) -> Some Screens -> Home menu -> Barcode menu (doesn't work).

However if using Navigator pushReplacementNamed the camera is loaded again. But I can't do that because I still need previous routes.

Maybe the problem is because the camera is not fully disposed in dispose method so the camera is not loaded when using pushNamed method. Is there any solution for this?

rio45ka commented 4 years ago

Any update for this issue?

rmtmckenzie commented 4 years ago

There needs to be some more refactoring to the widget but in this case I don't think it'd help anyways - when you push a new route on top of the old one, it doesn't automatically dispose the routes under it. By using replacementNamed, you're actually disposing the old routes. Also, if you're just pushing a new route on top every time that is a bit of an issue with your code - you should eventually be letting the old routes go away. Instead of pushReplacementNamed, look into Navigator.pushAndRemoveUntil( for when you return to the home screen.

If that doesn't help, at some point I'm going to try to separate the UI from the plugin a bit more which will allow this to be fixed more easily, but for now you could simply use a stateful widget with a bool "showCamera" or something and only show the QrCamera if showCamera is true. As soon as the QrCamera isn't in the widget tree, it should dispose itself fully.

Xtase commented 4 years ago

Hello,

Behind, what i'm using to Restart Camera, when return to screen, on build method of my Statefull widget :

Create key for QrCamera Widget (Ex: GlobalKey camKey = GlobalKey())

class _CamAppBarState extends State<CamAppBar> {
  final bool showCam = true;
  GlobalKey camKey = GlobalKey();

@override
  Widget build(BuildContext context) {
    if (showCam && qrCam == null) {
      qrCam = QrCamera(
        key: camKey,
        onError: (context, error) => Text(
          error.toString(),
          style: TextStyle(color: Colors.red),
        ),
        qrCodeCallback: (code) => camCallBack(code),
        child: Container(
          decoration: BoxDecoration(
            color: Colors.transparent,
            border: Border.all(
                color: Colors.black, width: 1.0, style: BorderStyle.solid),
          ),
        ),
      );
    } else if (qrCam != null && // Restart de la camera
        (camKey.currentState as QrCameraState).onScreen &&
        QrMobileVision.channelReader.qrCodeHandler == null) {
      Future.delayed(Duration(milliseconds: 200),
          () => (camKey.currentState as QrCameraState).restart());
    }
}

It seem to be OK after testing.

But new to Flutter and i don't know if it is the right way...

FMorschel commented 2 years ago

Here is a minimal reproducible code with the Flutter's intended way to detect and manage route.

Click to expand! ```dart import 'package:flutter/material.dart'; import 'package:qr_mobile_vision/qr_camera.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { /// Create this over your main MaterialApp final routeObserver = RouteObserver>(); return MaterialApp( title: 'Disposing Camera Example', theme: ThemeData( primarySwatch: Colors.blue, ), navigatorObservers: [routeObserver], /// Give it to the main materialApp home: MyHomePage(routeObserver: routeObserver), /// Pass it to the widget ); } } class MyHomePage extends StatefulWidget { const MyHomePage({Key? key, required this.routeObserver}) : super(key: key); final RouteObserver routeObserver; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State with RouteAware { bool showCamera = true; @override void didChangeDependencies() { super.didChangeDependencies(); /// Subscribe to this route inside a State with RouteAware widget.routeObserver.subscribe(this, ModalRoute.of(context)!); } /// Called when the top route has been popped off, and the current route /// shows up. @override void didPopNext() { setState(() { showCamera = true; }); } /// Called when the current route has been pushed. @override void didPush() {} /// Called when the current route has been popped off. @override void didPop() { setState(() { showCamera = false; }); } /// Called when a new route has been pushed, and the current route is no /// longer visible. @override void didPushNext() { setState(() { showCamera = false; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Disposing Camera Example'), centerTitle: true, ), body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ if (showCamera) SizedBox.fromSize( size: MediaQuery.of(context).size * 0.3, child: QrCamera( qrCodeCallback: debugPrint, ), ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (context) => const NewPage()), ); }, child: const Text('New Page'), ), ), ], ), ), ); } } class NewPage extends StatelessWidget { const NewPage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('New Page'), centerTitle: true, ), body: Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () { Navigator.of(context).pop(); }, child: const Text('Back'), ), ), ); } } ```