Skip to content

Commit d0b7109

Browse files
[camera] Fix initialization error in camera example on iOS (flutter#3406)
* Fix error on first camera initialize in example app * Improved error handling a bit & updated tests. * Updated pubspec and changelog Co-authored-by: Maurits van Beusekom <[email protected]>
1 parent 6d18db8 commit d0b7109

File tree

6 files changed

+195
-164
lines changed

6 files changed

+195
-164
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.7.0+2
2+
3+
* Improved error feedback by differentiating between uninitialized and disposed camera controllers.
4+
15
## 0.7.0+1
26

37
* Fixes picture captures causing a crash on some Huawei devices.

packages/camera/camera/example/lib/main.dart

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -580,10 +580,16 @@ class _CameraExampleHomeState extends State<CameraExampleHome>
580580

581581
try {
582582
await controller.initialize();
583-
_minAvailableExposureOffset = await controller.getMinExposureOffset();
584-
_maxAvailableExposureOffset = await controller.getMaxExposureOffset();
585-
_maxAvailableZoom = await controller.getMaxZoomLevel();
586-
_minAvailableZoom = await controller.getMinZoomLevel();
583+
await Future.wait([
584+
controller
585+
.getMinExposureOffset()
586+
.then((value) => _minAvailableExposureOffset = value),
587+
controller
588+
.getMaxExposureOffset()
589+
.then((value) => _maxAvailableExposureOffset = value),
590+
controller.getMaxZoomLevel().then((value) => _maxAvailableZoom = value),
591+
controller.getMinZoomLevel().then((value) => _minAvailableZoom = value),
592+
]);
587593
} on CameraException catch (e) {
588594
_showCameraException(e);
589595
}

packages/camera/camera/lib/src/camera_controller.dart

Lines changed: 30 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,7 @@ class CameraController extends ValueNotifier<CameraValue> {
323323
///
324324
/// Throws a [CameraException] if the capture fails.
325325
Future<XFile> takePicture() async {
326-
if (!value.isInitialized || _isDisposed) {
327-
throw CameraException(
328-
'Uninitialized CameraController.',
329-
'takePicture was called on uninitialized CameraController',
330-
);
331-
}
326+
_throwIfNotInitialized("takePicture");
332327
if (value.isTakingPicture) {
333328
throw CameraException(
334329
'Previous capture has not returned yet.',
@@ -366,13 +361,7 @@ class CameraController extends ValueNotifier<CameraValue> {
366361
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {
367362
assert(defaultTargetPlatform == TargetPlatform.android ||
368363
defaultTargetPlatform == TargetPlatform.iOS);
369-
370-
if (!value.isInitialized || _isDisposed) {
371-
throw CameraException(
372-
'Uninitialized CameraController',
373-
'startImageStream was called on uninitialized CameraController.',
374-
);
375-
}
364+
_throwIfNotInitialized("startImageStream");
376365
if (value.isRecordingVideo) {
377366
throw CameraException(
378367
'A video recording is already started.',
@@ -412,13 +401,7 @@ class CameraController extends ValueNotifier<CameraValue> {
412401
Future<void> stopImageStream() async {
413402
assert(defaultTargetPlatform == TargetPlatform.android ||
414403
defaultTargetPlatform == TargetPlatform.iOS);
415-
416-
if (!value.isInitialized || _isDisposed) {
417-
throw CameraException(
418-
'Uninitialized CameraController',
419-
'stopImageStream was called on uninitialized CameraController.',
420-
);
421-
}
404+
_throwIfNotInitialized("stopImageStream");
422405
if (value.isRecordingVideo) {
423406
throw CameraException(
424407
'A video recording is already started.',
@@ -448,12 +431,7 @@ class CameraController extends ValueNotifier<CameraValue> {
448431
/// The video is returned as a [XFile] after calling [stopVideoRecording].
449432
/// Throws a [CameraException] if the capture fails.
450433
Future<void> startVideoRecording() async {
451-
if (!value.isInitialized || _isDisposed) {
452-
throw CameraException(
453-
'Uninitialized CameraController',
454-
'startVideoRecording was called on uninitialized CameraController',
455-
);
456-
}
434+
_throwIfNotInitialized("startVideoRecording");
457435
if (value.isRecordingVideo) {
458436
throw CameraException(
459437
'A video recording is already started.',
@@ -483,12 +461,7 @@ class CameraController extends ValueNotifier<CameraValue> {
483461
///
484462
/// Throws a [CameraException] if the capture failed.
485463
Future<XFile> stopVideoRecording() async {
486-
if (!value.isInitialized || _isDisposed) {
487-
throw CameraException(
488-
'Uninitialized CameraController',
489-
'stopVideoRecording was called on uninitialized CameraController',
490-
);
491-
}
464+
_throwIfNotInitialized("stopVideoRecording");
492465
if (!value.isRecordingVideo) {
493466
throw CameraException(
494467
'No video is recording',
@@ -511,12 +484,7 @@ class CameraController extends ValueNotifier<CameraValue> {
511484
///
512485
/// This feature is only available on iOS and Android sdk 24+.
513486
Future<void> pauseVideoRecording() async {
514-
if (!value.isInitialized || _isDisposed) {
515-
throw CameraException(
516-
'Uninitialized CameraController',
517-
'pauseVideoRecording was called on uninitialized CameraController',
518-
);
519-
}
487+
_throwIfNotInitialized("pauseVideoRecording");
520488
if (!value.isRecordingVideo) {
521489
throw CameraException(
522490
'No video is recording',
@@ -535,12 +503,7 @@ class CameraController extends ValueNotifier<CameraValue> {
535503
///
536504
/// This feature is only available on iOS and Android sdk 24+.
537505
Future<void> resumeVideoRecording() async {
538-
if (!value.isInitialized || _isDisposed) {
539-
throw CameraException(
540-
'Uninitialized CameraController',
541-
'resumeVideoRecording was called on uninitialized CameraController',
542-
);
543-
}
506+
_throwIfNotInitialized("resumeVideoRecording");
544507
if (!value.isRecordingVideo) {
545508
throw CameraException(
546509
'No video is recording',
@@ -557,12 +520,7 @@ class CameraController extends ValueNotifier<CameraValue> {
557520

558521
/// Returns a widget showing a live camera preview.
559522
Widget buildPreview() {
560-
if (!value.isInitialized || _isDisposed) {
561-
throw CameraException(
562-
'Uninitialized CameraController',
563-
'buildView() was called on uninitialized CameraController.',
564-
);
565-
}
523+
_throwIfNotInitialized("buildPreview");
566524
try {
567525
return CameraPlatform.instance.buildPreview(_cameraId);
568526
} on PlatformException catch (e) {
@@ -572,13 +530,7 @@ class CameraController extends ValueNotifier<CameraValue> {
572530

573531
/// Gets the maximum supported zoom level for the selected camera.
574532
Future<double> getMaxZoomLevel() {
575-
if (!value.isInitialized || _isDisposed) {
576-
throw CameraException(
577-
'Uninitialized CameraController',
578-
'getMaxZoomLevel was called on uninitialized CameraController',
579-
);
580-
}
581-
533+
_throwIfNotInitialized("getMaxZoomLevel");
582534
try {
583535
return CameraPlatform.instance.getMaxZoomLevel(_cameraId);
584536
} on PlatformException catch (e) {
@@ -588,13 +540,7 @@ class CameraController extends ValueNotifier<CameraValue> {
588540

589541
/// Gets the minimum supported zoom level for the selected camera.
590542
Future<double> getMinZoomLevel() {
591-
if (!value.isInitialized || _isDisposed) {
592-
throw CameraException(
593-
'Uninitialized CameraController',
594-
'getMinZoomLevel was called on uninitialized CameraController',
595-
);
596-
}
597-
543+
_throwIfNotInitialized("getMinZoomLevel");
598544
try {
599545
return CameraPlatform.instance.getMinZoomLevel(_cameraId);
600546
} on PlatformException catch (e) {
@@ -608,13 +554,7 @@ class CameraController extends ValueNotifier<CameraValue> {
608554
/// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException`
609555
/// when an illegal zoom level is suplied.
610556
Future<void> setZoomLevel(double zoom) {
611-
if (!value.isInitialized || _isDisposed) {
612-
throw CameraException(
613-
'Uninitialized CameraController',
614-
'setZoomLevel was called on uninitialized CameraController',
615-
);
616-
}
617-
557+
_throwIfNotInitialized("setZoomLevel");
618558
try {
619559
return CameraPlatform.instance.setZoomLevel(_cameraId, zoom);
620560
} on PlatformException catch (e) {
@@ -666,13 +606,7 @@ class CameraController extends ValueNotifier<CameraValue> {
666606

667607
/// Gets the minimum supported exposure offset for the selected camera in EV units.
668608
Future<double> getMinExposureOffset() async {
669-
if (!value.isInitialized || _isDisposed) {
670-
throw CameraException(
671-
'Uninitialized CameraController',
672-
'getMinExposureOffset was called on uninitialized CameraController',
673-
);
674-
}
675-
609+
_throwIfNotInitialized("getMinExposureOffset");
676610
try {
677611
return CameraPlatform.instance.getMinExposureOffset(_cameraId);
678612
} on PlatformException catch (e) {
@@ -682,13 +616,7 @@ class CameraController extends ValueNotifier<CameraValue> {
682616

683617
/// Gets the maximum supported exposure offset for the selected camera in EV units.
684618
Future<double> getMaxExposureOffset() async {
685-
if (!value.isInitialized || _isDisposed) {
686-
throw CameraException(
687-
'Uninitialized CameraController',
688-
'getMaxExposureOffset was called on uninitialized CameraController',
689-
);
690-
}
691-
619+
_throwIfNotInitialized("getMaxExposureOffset");
692620
try {
693621
return CameraPlatform.instance.getMaxExposureOffset(_cameraId);
694622
} on PlatformException catch (e) {
@@ -700,13 +628,7 @@ class CameraController extends ValueNotifier<CameraValue> {
700628
///
701629
/// Returns 0 when the camera supports using a free value without stepping.
702630
Future<double> getExposureOffsetStepSize() async {
703-
if (!value.isInitialized || _isDisposed) {
704-
throw CameraException(
705-
'Uninitialized CameraController',
706-
'getExposureOffsetStepSize was called on uninitialized CameraController',
707-
);
708-
}
709-
631+
_throwIfNotInitialized("getExposureOffsetStepSize");
710632
try {
711633
return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId);
712634
} on PlatformException catch (e) {
@@ -726,13 +648,7 @@ class CameraController extends ValueNotifier<CameraValue> {
726648
///
727649
/// Returns the (rounded) offset value that was set.
728650
Future<double> setExposureOffset(double offset) async {
729-
if (!value.isInitialized || _isDisposed) {
730-
throw CameraException(
731-
'Uninitialized CameraController',
732-
'setExposureOffset was called on uninitialized CameraController',
733-
);
734-
}
735-
651+
_throwIfNotInitialized("setExposureOffset");
736652
// Check if offset is in range
737653
List<double> range =
738654
await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]);
@@ -834,4 +750,19 @@ class CameraController extends ValueNotifier<CameraValue> {
834750
await CameraPlatform.instance.dispose(_cameraId);
835751
}
836752
}
753+
754+
void _throwIfNotInitialized(String functionName) {
755+
if (!value.isInitialized) {
756+
throw CameraException(
757+
'Uninitialized CameraController',
758+
'$functionName() was called on an uninitialized CameraController.',
759+
);
760+
}
761+
if (_isDisposed) {
762+
throw CameraException(
763+
'Disposed CameraController',
764+
'$functionName() was called on a disposed CameraController.',
765+
);
766+
}
767+
}
837768
}

packages/camera/camera/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera
22
description: A Flutter plugin for getting information about and controlling the
33
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
44
and streaming image buffers to dart.
5-
version: 0.7.0+1
5+
version: 0.7.0+2
66
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
77

88
dependencies:

packages/camera/camera/test/camera_image_stream_test.dart

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,21 @@ void main() {
2222
ResolutionPreset.max);
2323

2424
expect(
25-
() => cameraController.startImageStream((image) => null),
26-
throwsA(isA<CameraException>().having(
27-
(error) => error.description,
28-
'Uninitialized CameraController.',
29-
'startImageStream was called on uninitialized CameraController.',
30-
)));
25+
() => cameraController.startImageStream((image) => null),
26+
throwsA(
27+
isA<CameraException>()
28+
.having(
29+
(error) => error.code,
30+
'code',
31+
'Uninitialized CameraController',
32+
)
33+
.having(
34+
(error) => error.description,
35+
'description',
36+
'startImageStream() was called on an uninitialized CameraController.',
37+
),
38+
),
39+
);
3140
});
3241

3342
test('startImageStream() throws $CameraException when recording videos',
@@ -107,12 +116,21 @@ void main() {
107116
ResolutionPreset.max);
108117

109118
expect(
110-
cameraController.stopImageStream,
111-
throwsA(isA<CameraException>().having(
112-
(error) => error.description,
113-
'Uninitialized CameraController.',
114-
'stopImageStream was called on uninitialized CameraController.',
115-
)));
119+
cameraController.stopImageStream,
120+
throwsA(
121+
isA<CameraException>()
122+
.having(
123+
(error) => error.code,
124+
'code',
125+
'Uninitialized CameraController',
126+
)
127+
.having(
128+
(error) => error.description,
129+
'description',
130+
'stopImageStream() was called on an uninitialized CameraController.',
131+
),
132+
),
133+
);
116134
});
117135

118136
test('stopImageStream() throws $CameraException when recording videos',

0 commit comments

Comments
 (0)