Browse Source

Severals fix

Hi,

Made several improvement on the reader_widget.dart :
1- Fix WidgetsBindingObserver not working on app after injecting reader_widget du to SystemChannels.lifecycle.setMessageHandler
2- Avoid memory leak by removing removeListener
3- Replace method _flashIcon by widget _FlashIcon
4- pass a widget argument for the loader to give more customisation
5- pass a duration argument to customise duration after successful scan
6- don't instantiate new camera if the old one not disposed
 
Hope you like it. Don't hesitate contact me if needed.

Regards,
pull/35/head
Jérémy Favier 2 years ago committed by GitHub
parent
commit
81988dcbf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 154
      lib/src/ui/reader_widget.dart

154
lib/src/ui/reader_widget.dart

@ -4,13 +4,12 @@ import 'dart:math';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'package:flutter_beep/flutter_beep.dart'; // import 'package:flutter_beep/flutter_beep.dart';
import '../../generated_bindings.dart'; import '../../generated_bindings.dart';
import '../logic/zxing.dart'; import '../logic/zxing.dart';
import '../utils/extentions.dart'; import '../utils/extentions.dart';
import '../utils/isolate_utils.dart';
import 'scanner_overlay.dart'; import 'scanner_overlay.dart';
class ReaderWidget extends StatefulWidget { class ReaderWidget extends StatefulWidget {
@ -24,8 +23,10 @@ class ReaderWidget extends StatefulWidget {
this.showFlashlight = true, this.showFlashlight = true,
this.allowPinchZoom = true, this.allowPinchZoom = true,
this.scanDelay = const Duration(milliseconds: 1000), // 1000ms delay this.scanDelay = const Duration(milliseconds: 1000), // 1000ms delay
this.scanDelaySuccess = const Duration(milliseconds: 1000), // 1000ms delay
this.cropPercent = 0.5, // 50% of the screen this.cropPercent = 0.5, // 50% of the screen
this.resolution = ResolutionPreset.high, this.resolution = ResolutionPreset.high,
this.loading = const DecoratedBox(decoration: BoxDecoration(color: Colors.black)),
}); });
final Function(CodeResult) onScan; final Function(CodeResult) onScan;
@ -38,14 +39,15 @@ class ReaderWidget extends StatefulWidget {
final Duration scanDelay; final Duration scanDelay;
final double cropPercent; final double cropPercent;
final ResolutionPreset resolution; final ResolutionPreset resolution;
final Duration scanDelaySuccess;
final Widget loading;
@override @override
State<ReaderWidget> createState() => _ReaderWidgetState(); State<ReaderWidget> createState() => _ReaderWidgetState();
} }
class _ReaderWidgetState extends State<ReaderWidget> class _ReaderWidgetState extends State<ReaderWidget> with TickerProviderStateMixin, WidgetsBindingObserver {
with TickerProviderStateMixin { List<CameraDescription> cameras = <CameraDescription>[];
List<CameraDescription>? cameras;
CameraController? controller; CameraController? controller;
bool _cameraOn = false; bool _cameraOn = false;
@ -59,13 +61,10 @@ class _ReaderWidgetState extends State<ReaderWidget>
// true when code detecting is ongoing // true when code detecting is ongoing
bool _isProcessing = false; bool _isProcessing = false;
/// Instance of [IsolateUtils]
IsolateUtils? isolateUtils;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
initStateAsync(); initStateAsync();
} }
@ -81,38 +80,50 @@ class _ReaderWidgetState extends State<ReaderWidget>
} }
}); });
}); });
}
SystemChannels.lifecycle.setMessageHandler((String? message) async { @override
debugPrint(message); void didChangeAppLifecycleState(AppLifecycleState state) {
final CameraController? cameraController = controller; switch (state) {
if (cameraController == null || !cameraController.value.isInitialized) { case AppLifecycleState.resumed:
return; if (cameras.isNotEmpty && !_cameraOn) {
onNewCameraSelected(cameras.first);
} }
if (mounted) {
if (message == AppLifecycleState.paused.toString()) { break;
await cameraController.stopImageStream(); case AppLifecycleState.inactive:
await cameraController.dispose(); break;
case AppLifecycleState.paused:
controller?.stopImageStream().then((_) => controller?.dispose());
setState(() {
_cameraOn = false; _cameraOn = false;
setState(() {});
}
if (message == AppLifecycleState.resumed.toString()) {
_cameraOn = true;
onNewCameraSelected(cameraController.description);
}
}
return null;
}); });
break;
case AppLifecycleState.detached:
break;
}
} }
@override @override
void dispose() { void dispose() {
stopCameraProcessing(); stopCameraProcessing();
controller?.removeListener(rebuildOnMount);
controller?.dispose(); controller?.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
void rebuildOnMount() {
if (mounted) {
setState(() {
_cameraOn = true;
});
}
}
Future<void> onNewCameraSelected(CameraDescription cameraDescription) async { Future<void> onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) { if (controller != null) {
controller?.removeListener(rebuildOnMount);
await controller!.dispose(); await controller!.dispose();
} }
@ -120,35 +131,24 @@ class _ReaderWidgetState extends State<ReaderWidget>
cameraDescription, cameraDescription,
widget.resolution, widget.resolution,
enableAudio: false, enableAudio: false,
imageFormatGroup: imageFormatGroup: isAndroid() ? ImageFormatGroup.yuv420 : ImageFormatGroup.bgra8888,
isAndroid() ? ImageFormatGroup.yuv420 : ImageFormatGroup.bgra8888,
); );
final CameraController? cameraController = controller; if (controller == null) {
if (cameraController == null) {
return; return;
} }
try { try {
await cameraController.initialize(); await controller!.initialize();
await cameraController.setFlashMode(FlashMode.off); await controller!.setFlashMode(FlashMode.off);
_maxZoomLevel = await cameraController.getMaxZoomLevel(); _maxZoomLevel = await controller!.getMaxZoomLevel();
_minZoomLevel = await cameraController.getMinZoomLevel(); _minZoomLevel = await controller!.getMinZoomLevel();
cameraController.startImageStream(processImageStream); controller!.startImageStream(processImageStream);
} on CameraException catch (e) { } on CameraException catch (e) {
debugPrint('${e.code}: ${e.description}'); debugPrint('${e.code}: ${e.description}');
} }
cameraController.addListener(() { controller!.addListener(rebuildOnMount);
if (mounted) { rebuildOnMount();
setState(() {}); widget.onControllerCreated?.call(controller);
}
});
if (mounted) {
_cameraOn = true;
setState(() {});
}
widget.onControllerCreated?.call(cameraController);
} }
Future<void> processImageStream(CameraImage image) async { Future<void> processImageStream(CameraImage image) async {
@ -163,7 +163,7 @@ class _ReaderWidgetState extends State<ReaderWidget>
if (result.isValidBool) { if (result.isValidBool) {
widget.onScan(result); widget.onScan(result);
setState(() {}); setState(() {});
await Future<void>.delayed(const Duration(seconds: 1)); await Future<void>.delayed(widget.scanDelaySuccess);
} }
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
debugPrint(e.message); debugPrint(e.message);
@ -179,30 +179,13 @@ class _ReaderWidgetState extends State<ReaderWidget>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size; final bool isCameraReady = cameras.isNotEmpty && _cameraOn && controller != null && controller!.value.isInitialized;
final double cropSize = min(size.width, size.height) * widget.cropPercent;
return Stack(
children: <Widget>[
// Camera preview
Center(
child: _cameraPreviewWidget(cropSize),
),
],
);
}
// Display the preview from the camera.
Widget _cameraPreviewWidget(double cropSize) {
final CameraController? cameraController = controller;
final bool isCameraReady = cameras != null &&
(cameras?.isNotEmpty ?? false) &&
_cameraOn &&
!(cameraController == null || !cameraController.value.isInitialized);
final Size size = MediaQuery.of(context).size; final Size size = MediaQuery.of(context).size;
final double cameraMaxSize = max(size.width, size.height); final double cameraMaxSize = max(size.width, size.height);
final double cropSize = min(size.width, size.height) * widget.cropPercent;
return Stack( return Stack(
children: <Widget>[ children: <Widget>[
if (!isCameraReady) Container(color: Colors.black), if (!isCameraReady) widget.loading,
if (isCameraReady) if (isCameraReady)
SizedBox( SizedBox(
width: cameraMaxSize, width: cameraMaxSize,
@ -214,7 +197,7 @@ class _ReaderWidgetState extends State<ReaderWidget>
child: SizedBox( child: SizedBox(
width: cameraMaxSize, width: cameraMaxSize,
child: CameraPreview( child: CameraPreview(
cameraController, controller!,
), ),
), ),
), ),
@ -241,9 +224,8 @@ class _ReaderWidgetState extends State<ReaderWidget>
_zoom = _scaleFactor; _zoom = _scaleFactor;
}, },
onScaleUpdate: (ScaleUpdateDetails details) { onScaleUpdate: (ScaleUpdateDetails details) {
_scaleFactor = _scaleFactor = (_zoom * details.scale).clamp(_minZoomLevel, _maxZoomLevel);
(_zoom * details.scale).clamp(_minZoomLevel, _maxZoomLevel); controller?.setZoomLevel(_scaleFactor);
cameraController?.setZoomLevel(_scaleFactor);
}, },
), ),
if (widget.showFlashlight) if (widget.showFlashlight)
@ -252,36 +234,40 @@ class _ReaderWidgetState extends State<ReaderWidget>
left: 20, left: 20,
child: FloatingActionButton( child: FloatingActionButton(
onPressed: () { onPressed: () {
if (cameraController != null) { if (controller != null) {
FlashMode mode = cameraController.value.flashMode; FlashMode mode = controller!.value.flashMode;
if (mode == FlashMode.torch) { if (mode == FlashMode.torch) {
mode = FlashMode.off; mode = FlashMode.off;
} else { } else {
mode = FlashMode.torch; mode = FlashMode.torch;
} }
cameraController.setFlashMode(mode); controller!.setFlashMode(mode);
setState(() {}); setState(() {});
} }
}, },
backgroundColor: Colors.black26, backgroundColor: Colors.black26,
child: Icon(_flashIcon(cameraController)), child: _FlashIcon(flashMode: controller!.value.flashMode)),
), ),
)
], ],
); );
} }
}
IconData _flashIcon(CameraController? cameraController) { class _FlashIcon extends StatelessWidget {
final FlashMode mode = cameraController?.value.flashMode ?? FlashMode.torch; const _FlashIcon({required this.flashMode});
switch (mode) { final FlashMode flashMode;
@override
Widget build(BuildContext context) {
switch (flashMode) {
case FlashMode.torch: case FlashMode.torch:
return Icons.flash_on; return const Icon(Icons.flash_on);
case FlashMode.off: case FlashMode.off:
return Icons.flash_off; return const Icon(Icons.flash_off);
case FlashMode.always: case FlashMode.always:
return Icons.flash_on; return const Icon(Icons.flash_on);
case FlashMode.auto: case FlashMode.auto:
return Icons.flash_auto; return const Icon(Icons.flash_auto);
} }
} }
} }

Loading…
Cancel
Save