Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[camera] Fix initialization error in camera example on iOS #3406

Merged
merged 4 commits into from Jan 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
@@ -1,3 +1,7 @@
## 0.7.0+2

* Improved error feedback by differentiating between uninitialized and disposed camera controllers.

## 0.7.0+1

* Fixes picture captures causing a crash on some Huawei devices.
Expand Down
14 changes: 10 additions & 4 deletions packages/camera/camera/example/lib/main.dart
Expand Up @@ -580,10 +580,16 @@ class _CameraExampleHomeState extends State<CameraExampleHome>

try {
await controller.initialize();
_minAvailableExposureOffset = await controller.getMinExposureOffset();
_maxAvailableExposureOffset = await controller.getMaxExposureOffset();
_maxAvailableZoom = await controller.getMaxZoomLevel();
_minAvailableZoom = await controller.getMinZoomLevel();
await Future.wait([
controller
.getMinExposureOffset()
.then((value) => _minAvailableExposureOffset = value),
controller
.getMaxExposureOffset()
.then((value) => _maxAvailableExposureOffset = value),
controller.getMaxZoomLevel().then((value) => _maxAvailableZoom = value),
controller.getMinZoomLevel().then((value) => _minAvailableZoom = value),
]);
} on CameraException catch (e) {
_showCameraException(e);
}
Expand Down
129 changes: 30 additions & 99 deletions packages/camera/camera/lib/src/camera_controller.dart
Expand Up @@ -323,12 +323,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
/// Throws a [CameraException] if the capture fails.
Future<XFile> takePicture() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController.',
'takePicture was called on uninitialized CameraController',
);
}
_throwIfNotInitialized("takePicture");
if (value.isTakingPicture) {
throw CameraException(
'Previous capture has not returned yet.',
Expand Down Expand Up @@ -366,13 +361,7 @@ class CameraController extends ValueNotifier<CameraValue> {
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);

if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'startImageStream was called on uninitialized CameraController.',
);
}
_throwIfNotInitialized("startImageStream");
if (value.isRecordingVideo) {
throw CameraException(
'A video recording is already started.',
Expand Down Expand Up @@ -412,13 +401,7 @@ class CameraController extends ValueNotifier<CameraValue> {
Future<void> stopImageStream() async {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);

if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'stopImageStream was called on uninitialized CameraController.',
);
}
_throwIfNotInitialized("stopImageStream");
if (value.isRecordingVideo) {
throw CameraException(
'A video recording is already started.',
Expand Down Expand Up @@ -448,12 +431,7 @@ class CameraController extends ValueNotifier<CameraValue> {
/// The video is returned as a [XFile] after calling [stopVideoRecording].
/// Throws a [CameraException] if the capture fails.
Future<void> startVideoRecording() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'startVideoRecording was called on uninitialized CameraController',
);
}
_throwIfNotInitialized("startVideoRecording");
if (value.isRecordingVideo) {
throw CameraException(
'A video recording is already started.',
Expand Down Expand Up @@ -483,12 +461,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
/// Throws a [CameraException] if the capture failed.
Future<XFile> stopVideoRecording() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'stopVideoRecording was called on uninitialized CameraController',
);
}
_throwIfNotInitialized("stopVideoRecording");
if (!value.isRecordingVideo) {
throw CameraException(
'No video is recording',
Expand All @@ -511,12 +484,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
/// This feature is only available on iOS and Android sdk 24+.
Future<void> pauseVideoRecording() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'pauseVideoRecording was called on uninitialized CameraController',
);
}
_throwIfNotInitialized("pauseVideoRecording");
if (!value.isRecordingVideo) {
throw CameraException(
'No video is recording',
Expand All @@ -535,12 +503,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
/// This feature is only available on iOS and Android sdk 24+.
Future<void> resumeVideoRecording() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'resumeVideoRecording was called on uninitialized CameraController',
);
}
_throwIfNotInitialized("resumeVideoRecording");
if (!value.isRecordingVideo) {
throw CameraException(
'No video is recording',
Expand All @@ -557,12 +520,7 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Returns a widget showing a live camera preview.
Widget buildPreview() {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'buildView() was called on uninitialized CameraController.',
);
}
_throwIfNotInitialized("buildPreview");
try {
return CameraPlatform.instance.buildPreview(_cameraId);
} on PlatformException catch (e) {
Expand All @@ -572,13 +530,7 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Gets the maximum supported zoom level for the selected camera.
Future<double> getMaxZoomLevel() {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'getMaxZoomLevel was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("getMaxZoomLevel");
try {
return CameraPlatform.instance.getMaxZoomLevel(_cameraId);
} on PlatformException catch (e) {
Expand All @@ -588,13 +540,7 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Gets the minimum supported zoom level for the selected camera.
Future<double> getMinZoomLevel() {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'getMinZoomLevel was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("getMinZoomLevel");
try {
return CameraPlatform.instance.getMinZoomLevel(_cameraId);
} on PlatformException catch (e) {
Expand All @@ -608,13 +554,7 @@ class CameraController extends ValueNotifier<CameraValue> {
/// zoom level returned by the `getMaxZoomLevel`. Throws an `CameraException`
/// when an illegal zoom level is suplied.
Future<void> setZoomLevel(double zoom) {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'setZoomLevel was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("setZoomLevel");
try {
return CameraPlatform.instance.setZoomLevel(_cameraId, zoom);
} on PlatformException catch (e) {
Expand Down Expand Up @@ -666,13 +606,7 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Gets the minimum supported exposure offset for the selected camera in EV units.
Future<double> getMinExposureOffset() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'getMinExposureOffset was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("getMinExposureOffset");
try {
return CameraPlatform.instance.getMinExposureOffset(_cameraId);
} on PlatformException catch (e) {
Expand All @@ -682,13 +616,7 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Gets the maximum supported exposure offset for the selected camera in EV units.
Future<double> getMaxExposureOffset() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'getMaxExposureOffset was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("getMaxExposureOffset");
try {
return CameraPlatform.instance.getMaxExposureOffset(_cameraId);
} on PlatformException catch (e) {
Expand All @@ -700,13 +628,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
/// Returns 0 when the camera supports using a free value without stepping.
Future<double> getExposureOffsetStepSize() async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'getExposureOffsetStepSize was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("getExposureOffsetStepSize");
try {
return CameraPlatform.instance.getExposureOffsetStepSize(_cameraId);
} on PlatformException catch (e) {
Expand All @@ -726,13 +648,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
/// Returns the (rounded) offset value that was set.
Future<double> setExposureOffset(double offset) async {
if (!value.isInitialized || _isDisposed) {
throw CameraException(
'Uninitialized CameraController',
'setExposureOffset was called on uninitialized CameraController',
);
}

_throwIfNotInitialized("setExposureOffset");
// Check if offset is in range
List<double> range =
await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]);
Expand Down Expand Up @@ -834,4 +750,19 @@ class CameraController extends ValueNotifier<CameraValue> {
await CameraPlatform.instance.dispose(_cameraId);
}
}

void _throwIfNotInitialized(String functionName) {
if (!value.isInitialized) {
throw CameraException(
'Uninitialized CameraController',
'$functionName() was called on an uninitialized CameraController.',
);
}
if (_isDisposed) {
throw CameraException(
'Disposed CameraController',
'$functionName() was called on a disposed CameraController.',
);
}
}
}
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Expand Up @@ -2,7 +2,7 @@ name: camera
description: A Flutter plugin for getting information about and controlling the
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
and streaming image buffers to dart.
version: 0.7.0+1
version: 0.7.0+2
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera

dependencies:
Expand Down
42 changes: 30 additions & 12 deletions packages/camera/camera/test/camera_image_stream_test.dart
Expand Up @@ -22,12 +22,21 @@ void main() {
ResolutionPreset.max);

expect(
() => cameraController.startImageStream((image) => null),
throwsA(isA<CameraException>().having(
(error) => error.description,
'Uninitialized CameraController.',
'startImageStream was called on uninitialized CameraController.',
)));
() => cameraController.startImageStream((image) => null),
throwsA(
isA<CameraException>()
.having(
(error) => error.code,
'code',
'Uninitialized CameraController',
)
.having(
(error) => error.description,
'description',
'startImageStream() was called on an uninitialized CameraController.',
),
),
);
});

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

expect(
cameraController.stopImageStream,
throwsA(isA<CameraException>().having(
(error) => error.description,
'Uninitialized CameraController.',
'stopImageStream was called on uninitialized CameraController.',
)));
cameraController.stopImageStream,
throwsA(
isA<CameraException>()
.having(
(error) => error.code,
'code',
'Uninitialized CameraController',
)
.having(
(error) => error.description,
'description',
'stopImageStream() was called on an uninitialized CameraController.',
),
),
);
});

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