From 7eb62a65e5f41a90dbb464139a8fa3dad8f14fe1 Mon Sep 17 00:00:00 2001 From: camsim99 Date: Wed, 31 Jan 2024 10:20:12 -0800 Subject: [PATCH 1/2] Change buildPreview to return Texture directly --- .../camera_android_camerax/CHANGELOG.md | 5 ++ .../lib/src/android_camera_camerax.dart | 31 ++++--- .../camera_android_camerax/pubspec.yaml | 2 +- .../test/android_camera_camerax_test.dart | 82 ++++--------------- 4 files changed, 42 insertions(+), 78 deletions(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index 7c1a948dc08..e7a849a2a64 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,8 @@ +## 0.5.0+29 + +* Modifies `buildPreview` to return `Texture` that maps to camera preview, building in the assumption + that `createCamera` should have been called before building the preview. + ## 0.5.0+28 * Wraps CameraX classes needed to implement setting focus and exposure points and exposure offset. diff --git a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart index bb9956c0f67..bbadd42ddd2 100644 --- a/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart +++ b/packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart @@ -98,6 +98,11 @@ class AndroidCameraCameraX extends CameraPlatform { @visibleForTesting String? videoOutputPath; + /// Whether or not [preview] has been bound to the lifecycle of the camera by + /// [createCamera]. + @visibleForTesting + bool previewInitiallyBound = false; + bool _previewIsPaused = false; /// The prefix used to create the filename for video recording files. @@ -284,6 +289,7 @@ class AndroidCameraCameraX extends CameraPlatform { camera = await processCameraProvider!.bindToLifecycle( cameraSelector!, [preview!, imageCapture!, imageAnalysis!]); await _updateCameraInfoAndLiveCameraState(flutterSurfaceTextureId); + previewInitiallyBound = true; _previewIsPaused = false; return flutterSurfaceTextureId; @@ -520,21 +526,20 @@ class AndroidCameraCameraX extends CameraPlatform { } /// Returns a widget showing a live camera preview. + /// + /// [createCamera] must be called before attempting to build this preview. @override Widget buildPreview(int cameraId) { - return FutureBuilder( - future: _bindPreviewToLifecycle(cameraId), - builder: (BuildContext context, AsyncSnapshot snapshot) { - switch (snapshot.connectionState) { - case ConnectionState.none: - case ConnectionState.waiting: - case ConnectionState.active: - // Do nothing while waiting for preview to be bound to lifecyle. - return const SizedBox.shrink(); - case ConnectionState.done: - return Texture(textureId: cameraId); - } - }); + if (!previewInitiallyBound) { + // No camera has been created, and thus, the preview UseCase has not been + // bound to the camera lifecycle, restricting this preview from being + // built. + throw CameraException( + 'cameraNotFound', + "Camera not found. Please call the 'create' method before calling 'buildPreview'", + ); + } + return Texture(textureId: cameraId); } /// Captures an image and returns the file where it was saved. diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 5ae0942e7d9..6c4d8968c78 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.5.0+28 +version: 0.5.0+29 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart index 6fd99e3cbca..415c5ee6448 100644 --- a/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart +++ b/packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart @@ -388,6 +388,10 @@ void main() { // Verify the camera's CameraInfo instance got updated. expect(camera.cameraInfo, equals(mockCameraInfo)); + + // Verify preview has been marked as bound to the camera lifecycle by + // createCamera. + expect(camera.previewInitiallyBound, isTrue); }); test( @@ -971,83 +975,33 @@ void main() { }); test( - 'buildPreview returns a FutureBuilder that does not return a Texture until the preview is bound to the lifecycle', + 'buildPreview throws an exception if the preview is not bound to the lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); - final MockProcessCameraProvider mockProcessCameraProvider = - MockProcessCameraProvider(); - final MockCamera mockCamera = MockCamera(); - final MockCameraInfo mockCameraInfo = MockCameraInfo(); - const int textureId = 75; - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = mockProcessCameraProvider; - camera.cameraSelector = MockCameraSelector(); - camera.preview = MockPreview(); - - // Tell plugin to create a mock Observer, that is created to - // track camera state once preview is bound to the lifecycle. - camera.proxy = - CameraXProxy(createCameraStateObserver: (_) => MockObserver()); - - when(mockProcessCameraProvider - .bindToLifecycle(camera.cameraSelector, [camera.preview!])) - .thenAnswer((_) async => mockCamera); - when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); - when(mockCameraInfo.getCameraState()) - .thenAnswer((_) async => MockLiveCameraState()); + const int cameraId = 73; - final FutureBuilder previewWidget = - camera.buildPreview(textureId) as FutureBuilder; + // Tell camera that createCamera has not been called and thus, preview has + // not been bound to the lifecycle of the camera. + camera.previewInitiallyBound = false; expect( - previewWidget.builder( - MockBuildContext(), const AsyncSnapshot.nothing()), - isA()); - expect( - previewWidget.builder( - MockBuildContext(), const AsyncSnapshot.waiting()), - isA()); - expect( - previewWidget.builder(MockBuildContext(), - const AsyncSnapshot.withData(ConnectionState.active, null)), - isA()); + () => camera.buildPreview(cameraId), throwsA(isA())); }); test( - 'buildPreview returns a FutureBuilder that returns a Texture once the preview is bound to the lifecycle', + 'buildPreview returns a Texture once the preview is bound to the lifecycle', () async { final AndroidCameraCameraX camera = AndroidCameraCameraX(); - final MockProcessCameraProvider mockProcessCameraProvider = - MockProcessCameraProvider(); - final MockCamera mockCamera = MockCamera(); - final MockCameraInfo mockCameraInfo = MockCameraInfo(); - const int textureId = 75; - - // Set directly for test versus calling createCamera. - camera.processCameraProvider = mockProcessCameraProvider; - camera.cameraSelector = MockCameraSelector(); - camera.preview = MockPreview(); - - // Tell plugin to create a mock Observer, that is created to - // track camera state once preview is bound to the lifecycle. - camera.proxy = - CameraXProxy(createCameraStateObserver: (_) => MockObserver()); + const int cameraId = 37; - when(mockProcessCameraProvider - .bindToLifecycle(camera.cameraSelector, [camera.preview!])) - .thenAnswer((_) async => mockCamera); - when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo); - when(mockCameraInfo.getCameraState()) - .thenAnswer((_) async => MockLiveCameraState()); + // Tell camera that createCamera has been called and thus, preview has been + // bound to the lifecycle of the camera. + camera.previewInitiallyBound = true; - final FutureBuilder previewWidget = - camera.buildPreview(textureId) as FutureBuilder; + final Widget widget = camera.buildPreview(cameraId); - final Texture previewTexture = previewWidget.builder(MockBuildContext(), - const AsyncSnapshot.withData(ConnectionState.done, null)) - as Texture; - expect(previewTexture.textureId, equals(textureId)); + expect(widget is Texture, isTrue); + expect((widget as Texture).textureId, cameraId); }); group('video recording', () { From e46e7ed508948d4d725cb7aae50c3378c3719eda Mon Sep 17 00:00:00 2001 From: camsim99 Date: Thu, 1 Feb 2024 10:33:59 -0800 Subject: [PATCH 2/2] Add github issue to changelog entry --- packages/camera/camera_android_camerax/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index e7a849a2a64..86e407bd0e5 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,7 +1,8 @@ ## 0.5.0+29 * Modifies `buildPreview` to return `Texture` that maps to camera preview, building in the assumption - that `createCamera` should have been called before building the preview. + that `createCamera` should have been called before building the preview. Fixes + https://github.com/flutter/flutter/issues/140567. ## 0.5.0+28