Julian Steenbakker

imp: improve error handling, add scanner_error_widget.dart to lib and show error…

… messages when kdebugmode is true
@@ -8,19 +8,6 @@ class ScannerErrorWidget extends StatelessWidget { @@ -8,19 +8,6 @@ class ScannerErrorWidget extends StatelessWidget {
8 8
9 @override 9 @override
10 Widget build(BuildContext context) { 10 Widget build(BuildContext context) {
11 - String errorMessage;  
12 -  
13 - switch (error.errorCode) {  
14 - case MobileScannerErrorCode.controllerUninitialized:  
15 - errorMessage = 'Controller not ready.';  
16 - case MobileScannerErrorCode.permissionDenied:  
17 - errorMessage = 'Permission denied';  
18 - case MobileScannerErrorCode.unsupported:  
19 - errorMessage = 'Scanning is unsupported on this device';  
20 - default:  
21 - errorMessage = 'Generic Error';  
22 - }  
23 -  
24 return ColoredBox( 11 return ColoredBox(
25 color: Colors.black, 12 color: Colors.black,
26 child: Center( 13 child: Center(
@@ -32,11 +19,11 @@ class ScannerErrorWidget extends StatelessWidget { @@ -32,11 +19,11 @@ class ScannerErrorWidget extends StatelessWidget {
32 child: Icon(Icons.error, color: Colors.white), 19 child: Icon(Icons.error, color: Colors.white),
33 ), 20 ),
34 Text( 21 Text(
35 - errorMessage, 22 + error.errorCode.message,
36 style: const TextStyle(color: Colors.white), 23 style: const TextStyle(color: Colors.white),
37 ), 24 ),
38 - Text(  
39 - error.errorDetails?.message ?? '', 25 + if (error.errorDetails != null) Text(
  26 + error.errorDetails!.message ?? '',
40 style: const TextStyle(color: Colors.white), 27 style: const TextStyle(color: Colors.white),
41 ), 28 ),
42 ], 29 ],
@@ -27,6 +27,23 @@ enum MobileScannerErrorCode { @@ -27,6 +27,23 @@ enum MobileScannerErrorCode {
27 /// Scanning is unsupported on the current device. 27 /// Scanning is unsupported on the current device.
28 unsupported; 28 unsupported;
29 29
  30 + String get message {
  31 + switch (this) {
  32 + case MobileScannerErrorCode.controllerUninitialized:
  33 + return 'The MobileScannerController has not been initialized. Call start() before using it.';
  34 + case MobileScannerErrorCode.permissionDenied:
  35 + return 'Camera permission denied.';
  36 + case MobileScannerErrorCode.unsupported:
  37 + return 'Scanning is not supported on this device.';
  38 + case MobileScannerErrorCode.controllerAlreadyInitialized:
  39 + return 'The MobileScannerController is already running. Stop it before starting again.';
  40 + case MobileScannerErrorCode.controllerDisposed:
  41 + return 'The MobileScannerController was used after it was disposed.';
  42 + case MobileScannerErrorCode.genericError:
  43 + return 'An unexpected error occurred.';
  44 + }
  45 + }
  46 +
30 /// Convert the given [PlatformException.code] to a [MobileScannerErrorCode]. 47 /// Convert the given [PlatformException.code] to a [MobileScannerErrorCode].
31 factory MobileScannerErrorCode.fromPlatformException( 48 factory MobileScannerErrorCode.fromPlatformException(
32 PlatformException exception, 49 PlatformException exception,
@@ -80,10 +80,10 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @@ -80,10 +80,10 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
80 ); 80 );
81 } 81 }
82 82
83 - throw const MobileScannerException(  
84 - errorCode: MobileScannerErrorCode.genericError, 83 + throw MobileScannerException(
  84 + errorCode: MobileScannerErrorCode.unsupported,
85 errorDetails: MobileScannerErrorDetails( 85 errorDetails: MobileScannerErrorDetails(
86 - message: 'Only Android, iOS and macOS are supported.', 86 + message:MobileScannerErrorCode.unsupported.message,
87 ), 87 ),
88 ); 88 );
89 } 89 }
@@ -218,10 +218,10 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @@ -218,10 +218,10 @@ class MethodChannelMobileScanner extends MobileScannerPlatform {
218 @override 218 @override
219 Future<MobileScannerViewAttributes> start(StartOptions startOptions) async { 219 Future<MobileScannerViewAttributes> start(StartOptions startOptions) async {
220 if (!_pausing && _textureId != null) { 220 if (!_pausing && _textureId != null) {
221 - throw const MobileScannerException( 221 + throw MobileScannerException(
222 errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, 222 errorCode: MobileScannerErrorCode.controllerAlreadyInitialized,
223 errorDetails: MobileScannerErrorDetails( 223 errorDetails: MobileScannerErrorDetails(
224 - message: 'The scanner was already started.', 224 + message: MobileScannerErrorCode.controllerAlreadyInitialized.message,
225 ), 225 ),
226 ); 226 );
227 } 227 }
@@ -6,6 +6,7 @@ import 'package:mobile_scanner/src/mobile_scanner_exception.dart'; @@ -6,6 +6,7 @@ import 'package:mobile_scanner/src/mobile_scanner_exception.dart';
6 import 'package:mobile_scanner/src/mobile_scanner_platform_interface.dart'; 6 import 'package:mobile_scanner/src/mobile_scanner_platform_interface.dart';
7 import 'package:mobile_scanner/src/objects/barcode_capture.dart'; 7 import 'package:mobile_scanner/src/objects/barcode_capture.dart';
8 import 'package:mobile_scanner/src/objects/mobile_scanner_state.dart'; 8 import 'package:mobile_scanner/src/objects/mobile_scanner_state.dart';
  9 +import 'package:mobile_scanner/src/objects/scanner_error_widget.dart';
9 import 'package:mobile_scanner/src/scan_window_calculation.dart'; 10 import 'package:mobile_scanner/src/scan_window_calculation.dart';
10 11
11 /// The function signature for the error builder. 12 /// The function signature for the error builder.
@@ -199,6 +200,8 @@ class _MobileScannerState extends State<MobileScanner> @@ -199,6 +200,8 @@ class _MobileScannerState extends State<MobileScanner>
199 return ValueListenableBuilder<MobileScannerState>( 200 return ValueListenableBuilder<MobileScannerState>(
200 valueListenable: controller, 201 valueListenable: controller,
201 builder: (BuildContext context, MobileScannerState value, Widget? child) { 202 builder: (BuildContext context, MobileScannerState value, Widget? child) {
  203 +
  204 + // If the controller is still initializing, show a black screen, or user provided placeholder
202 if (!value.isInitialized) { 205 if (!value.isInitialized) {
203 const Widget defaultPlaceholder = ColoredBox(color: Colors.black); 206 const Widget defaultPlaceholder = ColoredBox(color: Colors.black);
204 207
@@ -207,12 +210,9 @@ class _MobileScannerState extends State<MobileScanner> @@ -207,12 +210,9 @@ class _MobileScannerState extends State<MobileScanner>
207 } 210 }
208 211
209 final MobileScannerException? error = value.error; 212 final MobileScannerException? error = value.error;
210 - 213 + // If the controller encountered, show an error screen, or user provided placeholder
211 if (error != null) { 214 if (error != null) {
212 - const Widget defaultError = ColoredBox(  
213 - color: Colors.black,  
214 - child: Center(child: Icon(Icons.error, color: Colors.white)),  
215 - ); 215 + final Widget defaultError = ScannerErrorWidget(error: error);
216 216
217 return widget.errorBuilder?.call(context, error, child) ?? 217 return widget.errorBuilder?.call(context, error, child) ??
218 defaultError; 218 defaultError;
@@ -164,20 +164,20 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> { @@ -164,20 +164,20 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
164 164
165 void _throwIfNotInitialized() { 165 void _throwIfNotInitialized() {
166 if (!value.isInitialized) { 166 if (!value.isInitialized) {
167 - throw const MobileScannerException( 167 + throw MobileScannerException(
168 errorCode: MobileScannerErrorCode.controllerUninitialized, 168 errorCode: MobileScannerErrorCode.controllerUninitialized,
169 errorDetails: MobileScannerErrorDetails( 169 errorDetails: MobileScannerErrorDetails(
170 - message: 'The MobileScannerController has not been initialized.', 170 + message: MobileScannerErrorCode.controllerUninitialized.message,
171 ), 171 ),
172 ); 172 );
173 } 173 }
174 174
175 if (_isDisposed) { 175 if (_isDisposed) {
176 - throw const MobileScannerException( 176 + throw MobileScannerException(
177 errorCode: MobileScannerErrorCode.controllerDisposed, 177 errorCode: MobileScannerErrorCode.controllerDisposed,
178 errorDetails: MobileScannerErrorDetails( 178 errorDetails: MobileScannerErrorDetails(
179 message: 179 message:
180 - 'The MobileScannerController was used after it has been disposed.', 180 + MobileScannerErrorCode.controllerDisposed.message,
181 ), 181 ),
182 ); 182 );
183 } 183 }
@@ -284,11 +284,10 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> { @@ -284,11 +284,10 @@ class MobileScannerController extends ValueNotifier<MobileScannerState> {
284 /// If the permission is denied on iOS, MacOS or Web, there is no way to request it again. 284 /// If the permission is denied on iOS, MacOS or Web, there is no way to request it again.
285 Future<void> start({CameraFacing? cameraDirection}) async { 285 Future<void> start({CameraFacing? cameraDirection}) async {
286 if (_isDisposed) { 286 if (_isDisposed) {
287 - throw const MobileScannerException( 287 + throw MobileScannerException(
288 errorCode: MobileScannerErrorCode.controllerDisposed, 288 errorCode: MobileScannerErrorCode.controllerDisposed,
289 errorDetails: MobileScannerErrorDetails( 289 errorDetails: MobileScannerErrorDetails(
290 - message:  
291 - 'The MobileScannerController was used after it has been disposed.', 290 + message: MobileScannerErrorCode.controllerDisposed.message,
292 ), 291 ),
293 ); 292 );
294 } 293 }
  1 +import 'package:flutter/foundation.dart';
  2 +import 'package:flutter/material.dart';
  3 +import 'package:mobile_scanner/mobile_scanner.dart';
  4 +
  5 +class ScannerErrorWidget extends StatelessWidget {
  6 + const ScannerErrorWidget({super.key, required this.error});
  7 +
  8 + final MobileScannerException error;
  9 +
  10 + @override
  11 + Widget build(BuildContext context) {
  12 + return ColoredBox(
  13 + color: Colors.black,
  14 + child: Center(
  15 + child: Column(
  16 + mainAxisSize: MainAxisSize.min,
  17 + children: [
  18 + const Padding(
  19 + padding: EdgeInsets.only(bottom: 16),
  20 + child: Icon(Icons.error, color: Colors.white),
  21 + ),
  22 + if (kDebugMode) ...[
  23 + Text(
  24 + error.errorCode.message,
  25 + style: const TextStyle(color: Colors.white),
  26 + ),
  27 + if (error.errorDetails != null) Text(
  28 + error.errorDetails!.message ?? '',
  29 + style: const TextStyle(color: Colors.white),
  30 + ),
  31 + ] else Text(
  32 + MobileScannerErrorCode.genericError.message,
  33 + style: const TextStyle(color: Colors.white),
  34 + ),
  35 + ],
  36 + ),
  37 + ),
  38 + );
  39 + }
  40 +}
@@ -273,10 +273,10 @@ class MobileScannerWeb extends MobileScannerPlatform { @@ -273,10 +273,10 @@ class MobileScannerWeb extends MobileScannerPlatform {
273 ); 273 );
274 } 274 }
275 275
276 - throw const MobileScannerException( 276 + throw MobileScannerException(
277 errorCode: MobileScannerErrorCode.controllerAlreadyInitialized, 277 errorCode: MobileScannerErrorCode.controllerAlreadyInitialized,
278 errorDetails: MobileScannerErrorDetails( 278 errorDetails: MobileScannerErrorDetails(
279 - message: 'The scanner was already started.', 279 + message: MobileScannerErrorCode.controllerAlreadyInitialized.message,
280 ), 280 ),
281 ); 281 );
282 } 282 }