Flutter: Lifecycle of a Widget and Navigation
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
.
hendra
Updated on December 04, 2022Comments
-
hendra over 1 year
I have written a flutter plugin, that displays a camera preview and scans for barcodes. I have a
Widget
calledScanPage
that displays theCameraPreview
and navigates to a newRoute
when a barcode is detected.Problem: When I push a new Route (
SearchProductPage
) to the navigation stack, theCameraController
continues to detect barcodes. I need to callstop()
on myCameraController
when theScanPage
is removed from the screen. I need to callstart()
again, when the user returns to theScanPage
.What I tried: The
CameraController
implementsWidgetsBindingObserver
and reacts todidChangeAppLifecycleState()
. This works perfectly when I press the home button, but not when I push a newRoute
to the navigation stack.Question: Is there an equivalent for
viewDidAppear()
andviewWillDisappear()
on iOS oronPause()
andonResume()
on Android forWidgets
in Flutter? If not, how can I start and stop myCameraController
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(); }); } } }