Flutter: Lifecycle of a Widget and Navigation

10,302

I ended up stopping the controller before I navigate to the other page and restart it, when pop() is called.

//navigate to new page
void didDetectBarcode(String barcode) {
   controller.stop();
   Navigator.of(context)
       .push(...)
       .then(() => controller.start()); //future completes when pop() returns to this page

}

Another solution would be to set the maintainState property of the route that opens ScanPage to false.

Share:
10,302
hendra
Author by

hendra

Updated on December 04, 2022

Comments

  • hendra
    hendra over 1 year

    I have written a flutter plugin, that displays a camera preview and scans for barcodes. I have a Widget called ScanPage that displays the CameraPreview and navigates to a new Route when a barcode is detected.

    Problem: When I push a new Route (SearchProductPage) to the navigation stack, the CameraController continues to detect barcodes. I need to call stop() on my CameraController when the ScanPage is removed from the screen. I need to call start() again, when the user returns to the ScanPage.

    What I tried: The CameraController implements WidgetsBindingObserver and reacts to didChangeAppLifecycleState(). This works perfectly when I press the home button, but not when I push a new Route to the navigation stack.

    Question: Is there an equivalent for viewDidAppear() and viewWillDisappear() on iOS or onPause() and onResume() on Android for Widgets in Flutter? If not, how can I start and stop my CameraController so that it stops scanning for barcodes when another Widget is on top of the navigation stack?

    class ScanPage extends StatefulWidget {
    
      ScanPage({ Key key} ) : super(key: key);
    
      @override
      _ScanPageState createState() => new _ScanPageState();
    
    }
    
    class _ScanPageState extends State<ScanPage> {
    
      //implements WidgetsBindingObserver
      CameraController controller;
    
      @override
      void initState() {
    
        controller = new CameraController(this.didDetectBarcode);
        WidgetsBinding.instance.addObserver(controller);
    
        controller.initialize().then((_) {
          if (!mounted) {
            return;
          }
          setState(() {});
        });
      }
    
      //navigate to new page
      void didDetectBarcode(String barcode) {
          Navigator.of(context).push(
              new MaterialPageRoute(
                builder: (BuildContext buildContext) {
                  return new SearchProductPage(barcode);
                },
              )
          );    
      }
    
      @override
      void dispose() {
        WidgetsBinding.instance.removeObserver(controller);
        controller?.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
    
        if (!controller.value.initialized) {
          return new Center(
            child: new Text("Lade Barcodescanner..."),
          );
        }
    
        return new CameraPreview(controller);
      }
    }
    

    Edit:

    /// Controls a device camera.
    ///
    ///
    /// Before using a [CameraController] a call to [initialize] must complete.
    ///
    /// To show the camera preview on the screen use a [CameraPreview] widget.
    class CameraController extends ValueNotifier<CameraValue> with WidgetsBindingObserver {
    
      int _textureId;
      bool _disposed = false;
    
      Completer<Null> _creatingCompleter;
      BarcodeHandler handler;
    
      CameraController(this.handler) : super(const CameraValue.uninitialized());
    
      @override
      void didChangeAppLifecycleState(AppLifecycleState state) {
    
        switch(state){
          case AppLifecycleState.inactive:
            print("--inactive--");
            break;
          case AppLifecycleState.paused:
            print("--paused--");
            stop();
            break;
          case AppLifecycleState.resumed:
            print("--resumed--");
            start();
            break;
          case AppLifecycleState.suspending:
            print("--suspending--");
            dispose();
            break;
        }
      }
    
      /// Initializes the camera on the device.
      Future<Null> initialize() async {
    
        if (_disposed) {
          return;
        }
        try {
          _creatingCompleter = new Completer<Null>();
          _textureId = await BarcodeScanner.initCamera();
    
          print("TextureId: $_textureId");
    
          value = value.copyWith(
            initialized: true,
          );
          _applyStartStop();
        } on PlatformException catch (e) {
          value = value.copyWith(errorDescription: e.message);
          throw new CameraException(e.code, e.message);
        }
    
        BarcodeScanner._channel.setMethodCallHandler((MethodCall call){
          if(call.method == "barcodeDetected"){
            String barcode = call.arguments;
            debounce(2500, this.handler, [barcode]);
          }
        });
    
        _creatingCompleter.complete(null);
      }
    
      void _applyStartStop() {
        if (value.initialized && !_disposed) {
          if (value.isStarted) {
            BarcodeScanner.startCamera();
          } else {
            BarcodeScanner.stopCamera();
          }
        }
      }
    
      /// Starts the preview.
      ///
      /// If called before [initialize] it will take effect just after
      /// initialization is done.
      void start() {
        value = value.copyWith(isStarted: true);
        _applyStartStop();
      }
    
      /// Stops the preview.
      ///
      /// If called before [initialize] it will take effect just after
      /// initialization is done.
      void stop() {
        value = value.copyWith(isStarted: false);
        _applyStartStop();
      }
    
      /// Releases the resources of this camera.
      @override
      Future<Null> dispose() {
        if (_disposed) {
          return new Future<Null>.value(null);
        }
        _disposed = true;
        super.dispose();
        if (_creatingCompleter == null) {
          return new Future<Null>.value(null);
        } else {
          return _creatingCompleter.future.then((_) async {
            BarcodeScanner._channel.setMethodCallHandler(null);
            await BarcodeScanner.disposeCamera();
          });
        }
      }
    }