Julian Steenbakker

bug: fix disposing controller

  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +
  4 +class BarcodeScannerWithController extends StatefulWidget {
  5 + const BarcodeScannerWithController({Key? key}) : super(key: key);
  6 +
  7 + @override
  8 + _BarcodeScannerWithControllerState createState() =>
  9 + _BarcodeScannerWithControllerState();
  10 +}
  11 +
  12 +class _BarcodeScannerWithControllerState
  13 + extends State<BarcodeScannerWithController>
  14 + with SingleTickerProviderStateMixin {
  15 + String? barcode;
  16 +
  17 + MobileScannerController controller = MobileScannerController(
  18 + torchEnabled: true,
  19 + // facing: CameraFacing.front,
  20 + );
  21 +
  22 + @override
  23 + Widget build(BuildContext context) {
  24 + return MaterialApp(
  25 + home: Scaffold(
  26 + backgroundColor: Colors.black,
  27 + body: Builder(builder: (context) {
  28 + return Stack(
  29 + children: [
  30 + MobileScanner(
  31 + controller: controller,
  32 + fit: BoxFit.contain,
  33 + // controller: MobileScannerController(
  34 + // torchEnabled: true,
  35 + // facing: CameraFacing.front,
  36 + // ),
  37 + onDetect: (barcode, args) {
  38 + if (this.barcode != barcode.rawValue) {
  39 + setState(() {
  40 + this.barcode = barcode.rawValue;
  41 + });
  42 + }
  43 + }),
  44 + Align(
  45 + alignment: Alignment.bottomCenter,
  46 + child: Container(
  47 + alignment: Alignment.bottomCenter,
  48 + height: 100,
  49 + color: Colors.black.withOpacity(0.4),
  50 + child: Row(
  51 + crossAxisAlignment: CrossAxisAlignment.center,
  52 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  53 + children: [
  54 + IconButton(
  55 + color: Colors.white,
  56 + icon: ValueListenableBuilder(
  57 + valueListenable: controller.torchState,
  58 + builder: (context, state, child) {
  59 + switch (state as TorchState) {
  60 + case TorchState.off:
  61 + return const Icon(Icons.flash_off,
  62 + color: Colors.grey);
  63 + case TorchState.on:
  64 + return const Icon(Icons.flash_on,
  65 + color: Colors.yellow);
  66 + }
  67 + },
  68 + ),
  69 + iconSize: 32.0,
  70 + onPressed: () => controller.toggleTorch(),
  71 + ),
  72 + Center(
  73 + child: SizedBox(
  74 + width: MediaQuery.of(context).size.width - 120,
  75 + height: 50,
  76 + child: FittedBox(
  77 + child: Text(
  78 + barcode ?? 'Scan something!',
  79 + overflow: TextOverflow.fade,
  80 + style: Theme.of(context)
  81 + .textTheme
  82 + .headline4!
  83 + .copyWith(color: Colors.white),
  84 + ),
  85 + ),
  86 + ),
  87 + ),
  88 + IconButton(
  89 + color: Colors.white,
  90 + icon: ValueListenableBuilder(
  91 + valueListenable: controller.cameraFacingState,
  92 + builder: (context, state, child) {
  93 + switch (state as CameraFacing) {
  94 + case CameraFacing.front:
  95 + return const Icon(Icons.camera_front);
  96 + case CameraFacing.back:
  97 + return const Icon(Icons.camera_rear);
  98 + }
  99 + },
  100 + ),
  101 + iconSize: 32.0,
  102 + onPressed: () => controller.switchCamera(),
  103 + ),
  104 + ],
  105 + ),
  106 + ),
  107 + ),
  108 + ],
  109 + );
  110 + }),
  111 + ),
  112 + );
  113 + }
  114 +}
  1 +import 'package:flutter/material.dart';
  2 +import 'package:mobile_scanner/mobile_scanner.dart';
  3 +
  4 +class BarcodeScannerWithoutController extends StatefulWidget {
  5 + const BarcodeScannerWithoutController({Key? key}) : super(key: key);
  6 +
  7 + @override
  8 + _BarcodeScannerWithoutControllerState createState() =>
  9 + _BarcodeScannerWithoutControllerState();
  10 +}
  11 +
  12 +class _BarcodeScannerWithoutControllerState
  13 + extends State<BarcodeScannerWithoutController>
  14 + with SingleTickerProviderStateMixin {
  15 + String? barcode;
  16 +
  17 + @override
  18 + Widget build(BuildContext context) {
  19 + return MaterialApp(
  20 + home: Scaffold(
  21 + backgroundColor: Colors.black,
  22 + body: Builder(builder: (context) {
  23 + return Stack(
  24 + children: [
  25 + MobileScanner(
  26 + fit: BoxFit.contain,
  27 + onDetect: (barcode, args) {
  28 + if (this.barcode != barcode.rawValue) {
  29 + setState(() {
  30 + this.barcode = barcode.rawValue;
  31 + });
  32 + }
  33 + }),
  34 + Align(
  35 + alignment: Alignment.bottomCenter,
  36 + child: Container(
  37 + alignment: Alignment.bottomCenter,
  38 + height: 100,
  39 + color: Colors.black.withOpacity(0.4),
  40 + child: Row(
  41 + crossAxisAlignment: CrossAxisAlignment.center,
  42 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  43 + children: [
  44 + Center(
  45 + child: SizedBox(
  46 + width: MediaQuery.of(context).size.width - 120,
  47 + height: 50,
  48 + child: FittedBox(
  49 + child: Text(
  50 + barcode ?? 'Scan something!',
  51 + overflow: TextOverflow.fade,
  52 + style: Theme.of(context)
  53 + .textTheme
  54 + .headline4!
  55 + .copyWith(color: Colors.white),
  56 + ),
  57 + ),
  58 + ),
  59 + ),
  60 + ],
  61 + ),
  62 + ),
  63 + ),
  64 + ],
  65 + );
  66 + }),
  67 + ),
  68 + );
  69 + }
  70 +}
1 import 'package:flutter/material.dart'; 1 import 'package:flutter/material.dart';
2 -import 'package:mobile_scanner/mobile_scanner.dart'; 2 +import 'package:mobile_scanner_example/barcode_scanner_controller.dart';
  3 +import 'package:mobile_scanner_example/barcode_scanner_without_controller.dart';
3 4
4 -void main() {  
5 - runApp(const AnalyzeView());  
6 -} 5 +void main() => runApp(const MaterialApp(home: MyHome()));
7 6
8 -class AnalyzeView extends StatefulWidget {  
9 - const AnalyzeView({Key? key}) : super(key: key);  
10 -  
11 - @override  
12 - _AnalyzeViewState createState() => _AnalyzeViewState();  
13 -}  
14 -  
15 -class _AnalyzeViewState extends State<AnalyzeView>  
16 - with SingleTickerProviderStateMixin {  
17 - String? barcode;  
18 -  
19 - MobileScannerController controller = MobileScannerController(  
20 - torchEnabled: true,  
21 - facing: CameraFacing.front,  
22 - ); 7 +class MyHome extends StatelessWidget {
  8 + const MyHome({Key? key}) : super(key: key);
23 9
24 @override 10 @override
25 Widget build(BuildContext context) { 11 Widget build(BuildContext context) {
26 - return MaterialApp(  
27 - home: Scaffold(  
28 - backgroundColor: Colors.black,  
29 - body: Builder(builder: (context) {  
30 - return Stack(  
31 - children: [  
32 - MobileScanner(  
33 - controller: controller,  
34 - fit: BoxFit.contain,  
35 - // controller: MobileScannerController(  
36 - // torchEnabled: true,  
37 - // facing: CameraFacing.front,  
38 - // ),  
39 - onDetect: (barcode, args) {  
40 - if (this.barcode != barcode.rawValue) {  
41 - setState(() {  
42 - this.barcode = barcode.rawValue;  
43 - });  
44 - }  
45 - }),  
46 - Align(  
47 - alignment: Alignment.bottomCenter,  
48 - child: Container(  
49 - alignment: Alignment.bottomCenter,  
50 - height: 100,  
51 - color: Colors.black.withOpacity(0.4),  
52 - child: Row(  
53 - crossAxisAlignment: CrossAxisAlignment.center,  
54 - mainAxisAlignment: MainAxisAlignment.spaceEvenly,  
55 - children: [  
56 - IconButton(  
57 - color: Colors.white,  
58 - icon: ValueListenableBuilder(  
59 - valueListenable: controller.torchState,  
60 - builder: (context, state, child) {  
61 - switch (state as TorchState) {  
62 - case TorchState.off:  
63 - return const Icon(Icons.flash_off,  
64 - color: Colors.grey);  
65 - case TorchState.on:  
66 - return const Icon(Icons.flash_on,  
67 - color: Colors.yellow);  
68 - }  
69 - },  
70 - ),  
71 - iconSize: 32.0,  
72 - onPressed: () => controller.toggleTorch(),  
73 - ),  
74 - Center(  
75 - child: SizedBox(  
76 - width: MediaQuery.of(context).size.width - 120,  
77 - height: 50,  
78 - child: FittedBox(  
79 - child: Text(  
80 - barcode ?? 'Scan something!',  
81 - overflow: TextOverflow.fade,  
82 - style: Theme.of(context)  
83 - .textTheme  
84 - .headline4!  
85 - .copyWith(color: Colors.white),  
86 - ),  
87 - ),  
88 - ),  
89 - ),  
90 - IconButton(  
91 - color: Colors.white,  
92 - icon: ValueListenableBuilder(  
93 - valueListenable: controller.cameraFacingState,  
94 - builder: (context, state, child) {  
95 - switch (state as CameraFacing) {  
96 - case CameraFacing.front:  
97 - return const Icon(Icons.camera_front);  
98 - case CameraFacing.back:  
99 - return const Icon(Icons.camera_rear);  
100 - }  
101 - },  
102 - ),  
103 - iconSize: 32.0,  
104 - onPressed: () => controller.switchCamera(),  
105 - ),  
106 - ],  
107 - ),  
108 - ),  
109 - ),  
110 -  
111 - // Container(  
112 - // alignment: Alignment.bottomCenter,  
113 - // margin: EdgeInsets.only(bottom: 80.0),  
114 - // child: IconButton(  
115 - // icon: ValueListenableBuilder(  
116 - // valueListenable: cameraController.torchState,  
117 - // builder: (context, state, child) {  
118 - // final color =  
119 - // state == TorchState.off ? Colors.grey : Colors.white;  
120 - // return Icon(Icons.bolt, color: color);  
121 - // },  
122 - // ),  
123 - // iconSize: 32.0,  
124 - // onPressed: () => cameraController.torch(),  
125 - // ),  
126 - // ),  
127 - ],  
128 - );  
129 - }), 12 + return Scaffold(
  13 + appBar: AppBar(title: const Text('Flutter Demo Home Page')),
  14 + body: SizedBox(
  15 + width: MediaQuery.of(context).size.width,
  16 + height: MediaQuery.of(context).size.height,
  17 + child: Column(
  18 + mainAxisAlignment: MainAxisAlignment.center,
  19 + crossAxisAlignment: CrossAxisAlignment.center,
  20 + children: [
  21 + ElevatedButton(
  22 + onPressed: () {
  23 + Navigator.of(context).push(MaterialPageRoute(
  24 + builder: (context) => const BarcodeScannerWithController(),
  25 + ));
  26 + },
  27 + child: const Text('MobileScanner with Controller'),
  28 + ),
  29 + ElevatedButton(
  30 + onPressed: () {
  31 + Navigator.of(context).push(MaterialPageRoute(
  32 + builder: (context) => const BarcodeScannerWithoutController(),
  33 + ));
  34 + },
  35 + child: const Text('MobileScanner without Controller'),
  36 + ),
  37 + ],
  38 + ),
130 ), 39 ),
131 ); 40 );
132 } 41 }
133 -  
134 - @override  
135 - void dispose() {  
136 - // cameraController.dispose();  
137 - super.dispose();  
138 - }  
139 -  
140 - void display(Barcode barcode) {  
141 - Navigator.of(context).popAndPushNamed('display', arguments: barcode);  
142 - }  
143 } 42 }
144 -  
145 -// import 'package:flutter/material.dart';  
146 -// import 'package:flutter/rendering.dart';  
147 -// import 'package:mobile_scanner/mobile_scanner.dart';  
148 -//  
149 -// void main() {  
150 -// debugPaintSizeEnabled = false;  
151 -// runApp(HomePage());  
152 -// }  
153 -//  
154 -// class HomePage extends StatefulWidget {  
155 -// @override  
156 -// HomeState createState() => HomeState();  
157 -// }  
158 -//  
159 -// class HomeState extends State<HomePage> {  
160 -// @override  
161 -// Widget build(BuildContext context) {  
162 -// return MaterialApp(home: MyApp());  
163 -// }  
164 -// }  
165 -//  
166 -// class MyApp extends StatefulWidget {  
167 -// @override  
168 -// _MyAppState createState() => _MyAppState();  
169 -// }  
170 -//  
171 -// class _MyAppState extends State<MyApp> {  
172 -// String? qr;  
173 -// bool camState = false;  
174 -//  
175 -// @override  
176 -// initState() {  
177 -// super.initState();  
178 -// }  
179 -//  
180 -// @override  
181 -// Widget build(BuildContext context) {  
182 -// return Scaffold(  
183 -// appBar: AppBar(  
184 -// title: Text('Plugin example app'),  
185 -// ),  
186 -// body: Center(  
187 -// child: Column(  
188 -// crossAxisAlignment: CrossAxisAlignment.center,  
189 -// mainAxisAlignment: MainAxisAlignment.center,  
190 -// children: <Widget>[  
191 -// Expanded(  
192 -// child: camState  
193 -// ? Center(  
194 -// child: SizedBox(  
195 -// width: 300.0,  
196 -// height: 600.0,  
197 -// child: MobileScanner(  
198 -// onError: (context, error) => Text(  
199 -// error.toString(),  
200 -// style: TextStyle(color: Colors.red),  
201 -// ),  
202 -// qrCodeCallback: (code) {  
203 -// setState(() {  
204 -// qr = code;  
205 -// });  
206 -// },  
207 -// child: Container(  
208 -// decoration: BoxDecoration(  
209 -// color: Colors.transparent,  
210 -// border: Border.all(  
211 -// color: Colors.orange,  
212 -// width: 10.0,  
213 -// style: BorderStyle.solid),  
214 -// ),  
215 -// ),  
216 -// ),  
217 -// ),  
218 -// )  
219 -// : Center(child: Text("Camera inactive"))),  
220 -// Text("QRCODE: $qr"),  
221 -// ],  
222 -// ),  
223 -// ),  
224 -// floatingActionButton: FloatingActionButton(  
225 -// child: Text(  
226 -// "press me",  
227 -// textAlign: TextAlign.center,  
228 -// ),  
229 -// onPressed: () {  
230 -// setState(() {  
231 -// camState = !camState;  
232 -// });  
233 -// }),  
234 -// );  
235 -// }  
236 -// }  
@@ -29,8 +29,7 @@ class MobileScanner extends StatefulWidget { @@ -29,8 +29,7 @@ class MobileScanner extends StatefulWidget {
29 this.onDetect, 29 this.onDetect,
30 this.controller, 30 this.controller,
31 this.fit = BoxFit.cover, 31 this.fit = BoxFit.cover,
32 - }) : assert((controller != null)),  
33 - super(key: key); 32 + }) : super(key: key);
34 33
35 @override 34 @override
36 State<MobileScanner> createState() => _MobileScannerState(); 35 State<MobileScanner> createState() => _MobileScannerState();
@@ -38,33 +37,32 @@ class MobileScanner extends StatefulWidget { @@ -38,33 +37,32 @@ class MobileScanner extends StatefulWidget {
38 37
39 class _MobileScannerState extends State<MobileScanner> 38 class _MobileScannerState extends State<MobileScanner>
40 with WidgetsBindingObserver { 39 with WidgetsBindingObserver {
41 - bool onScreen = true;  
42 late MobileScannerController controller; 40 late MobileScannerController controller;
43 41
44 @override 42 @override
45 void initState() { 43 void initState() {
46 super.initState(); 44 super.initState();
  45 + WidgetsBinding.instance?.addObserver(this);
47 controller = widget.controller ?? MobileScannerController(); 46 controller = widget.controller ?? MobileScannerController();
48 } 47 }
49 48
50 @override 49 @override
51 void didChangeAppLifecycleState(AppLifecycleState state) { 50 void didChangeAppLifecycleState(AppLifecycleState state) {
52 - if (state == AppLifecycleState.resumed) {  
53 - setState(() => onScreen = true);  
54 - } else {  
55 - if (onScreen) { 51 + switch (state) {
  52 + case AppLifecycleState.resumed:
  53 + controller.start();
  54 + break;
  55 + case AppLifecycleState.inactive:
  56 + case AppLifecycleState.paused:
  57 + case AppLifecycleState.detached:
56 controller.stop(); 58 controller.stop();
57 - }  
58 - setState(() {  
59 - onScreen = false;  
60 - }); 59 + break;
61 } 60 }
62 } 61 }
63 62
64 @override 63 @override
65 Widget build(BuildContext context) { 64 Widget build(BuildContext context) {
66 return LayoutBuilder(builder: (context, BoxConstraints constraints) { 65 return LayoutBuilder(builder: (context, BoxConstraints constraints) {
67 - if (!onScreen) return const Text("Camera Paused.");  
68 return ValueListenableBuilder( 66 return ValueListenableBuilder(
69 valueListenable: controller.args, 67 valueListenable: controller.args,
70 builder: (context, value, child) { 68 builder: (context, value, child) {
@@ -112,7 +110,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -112,7 +110,8 @@ class _MobileScannerState extends State<MobileScanner>
112 110
113 @override 111 @override
114 void dispose() { 112 void dispose() {
115 - if (widget.controller == null) controller.dispose(); 113 + controller.dispose();
  114 + WidgetsBinding.instance?.removeObserver(this);
116 super.dispose(); 115 super.dispose();
117 } 116 }
118 } 117 }