Navaron Bracke

remove autoresume field; make controller required; fix memory leak; fix bug with _restartScanner()

  1 +import 'dart:async';
  2 +
1 import 'package:flutter/foundation.dart'; 3 import 'package:flutter/foundation.dart';
2 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
3 import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; 5 import 'package:mobile_scanner/src/mobile_scanner_controller.dart';
@@ -11,8 +13,8 @@ typedef MobileScannerArgumentsCallback = void Function( @@ -11,8 +13,8 @@ typedef MobileScannerArgumentsCallback = void Function(
11 13
12 /// A widget showing a live camera preview. 14 /// A widget showing a live camera preview.
13 class MobileScanner extends StatefulWidget { 15 class MobileScanner extends StatefulWidget {
14 - /// The controller of the camera.  
15 - final MobileScannerController? controller; 16 + /// The controller that manages the barcode scanner.
  17 + final MobileScannerController controller;
16 18
17 /// Function that gets called when a Barcode is detected. 19 /// Function that gets called when a Barcode is detected.
18 /// 20 ///
@@ -35,11 +37,7 @@ class MobileScanner extends StatefulWidget { @@ -35,11 +37,7 @@ class MobileScanner extends StatefulWidget {
35 /// Create a [MobileScanner] with a [controller]. 37 /// Create a [MobileScanner] with a [controller].
36 /// The [controller] must have been initialized, using [MobileScannerController.start]. 38 /// The [controller] must have been initialized, using [MobileScannerController.start].
37 const MobileScanner({ 39 const MobileScanner({
38 - super.key,  
39 - required this.onDetect,  
40 - this.onStart,  
41 - this.controller,  
42 - this.autoResume = true, 40 + required this.controller,
43 this.fit = BoxFit.cover, 41 this.fit = BoxFit.cover,
44 }); 42 });
45 43
@@ -49,44 +47,53 @@ class MobileScanner extends StatefulWidget { @@ -49,44 +47,53 @@ class MobileScanner extends StatefulWidget {
49 47
50 class _MobileScannerState extends State<MobileScanner> 48 class _MobileScannerState extends State<MobileScanner>
51 with WidgetsBindingObserver { 49 with WidgetsBindingObserver {
52 - late MobileScannerController controller; 50 + /// The subscription that listens to barcode detection.
  51 + StreamSubscription<BarcodeCapture>? _barcodesSubscription;
  52 +
  53 + /// Whether the controller should resume
  54 + /// when the application comes back to the foreground.
  55 + bool _resumeFromBackground = false;
  56 +
  57 + /// Restart a previously paused scanner.
  58 + void _restartScanner() {
  59 + widget.controller.start().then((arguments) {
  60 + widget.onScannerRestarted?.call(arguments);
  61 + }).catchError((error) {
  62 + // The scanner somehow failed to restart.
  63 + // There is no way to recover from this, so do nothing.
  64 + });
  65 + }
53 66
54 @override 67 @override
55 void initState() { 68 void initState() {
56 super.initState(); 69 super.initState();
57 WidgetsBinding.instance.addObserver(this); 70 WidgetsBinding.instance.addObserver(this);
58 - controller = widget.controller ?? MobileScannerController();  
59 - if (!controller.isStarting) {  
60 - _startScanner();  
61 - }  
62 - }  
63 -  
64 - Future<void> _startScanner() async {  
65 - final arguments = await controller.start();  
66 - widget.onStart?.call(arguments); 71 + _barcodesSubscription = widget.controller.barcodes.listen(
  72 + widget.onBarcodeDetected,
  73 + );
67 } 74 }
68 75
69 - bool resumeFromBackground = false;  
70 -  
71 @override 76 @override
72 void didChangeAppLifecycleState(AppLifecycleState state) { 77 void didChangeAppLifecycleState(AppLifecycleState state) {
73 - // App state changed before it is initialized.  
74 - if (controller.isStarting) { 78 + // App state changed before the controller was initialized.
  79 + if (widget.controller.isStarting) {
75 return; 80 return;
76 } 81 }
77 82
78 switch (state) { 83 switch (state) {
79 case AppLifecycleState.resumed: 84 case AppLifecycleState.resumed:
80 - resumeFromBackground = false;  
81 - _startScanner(); 85 + _resumeFromBackground = false;
  86 + _restartScanner();
82 break; 87 break;
83 case AppLifecycleState.paused: 88 case AppLifecycleState.paused:
84 - resumeFromBackground = true; 89 + _resumeFromBackground = true;
85 break; 90 break;
86 case AppLifecycleState.inactive: 91 case AppLifecycleState.inactive:
87 - if (!resumeFromBackground) controller.stop(); 92 + if (!_resumeFromBackground) {
  93 + widget.controller.stop();
  94 + }
88 break; 95 break;
89 - default: 96 + case AppLifecycleState.detached:
90 break; 97 break;
91 } 98 }
92 } 99 }
@@ -125,26 +132,9 @@ class _MobileScannerState extends State<MobileScanner> @@ -125,26 +132,9 @@ class _MobileScannerState extends State<MobileScanner>
125 } 132 }
126 133
127 @override 134 @override
128 - void didUpdateWidget(covariant MobileScanner oldWidget) {  
129 - super.didUpdateWidget(oldWidget);  
130 - if (oldWidget.controller == null) {  
131 - if (widget.controller != null) {  
132 - controller.dispose();  
133 - controller = widget.controller!;  
134 - }  
135 - } else {  
136 - if (widget.controller == null) {  
137 - controller = MobileScannerController();  
138 - } else if (oldWidget.controller != widget.controller) {  
139 - controller = widget.controller!;  
140 - }  
141 - }  
142 - }  
143 -  
144 - @override  
145 void dispose() { 135 void dispose() {
146 - controller.dispose();  
147 WidgetsBinding.instance.removeObserver(this); 136 WidgetsBinding.instance.removeObserver(this);
  137 + _barcodesSubscription?.cancel();
148 super.dispose(); 138 super.dispose();
149 } 139 }
150 } 140 }