Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[camerax] Change buildPreview to return Texture versus FutureBuilder #6021

Merged
merged 2 commits into from
Feb 1, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/camera/camera_android_camerax/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.5.0+29

* Modifies `buildPreview` to return `Texture` that maps to camera preview, building in the assumption
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider linking to the issue that is being fixed.

that `createCamera` should have been called before building the preview. Fixes
https://github.com/flutter/flutter/issues/140567.

## 0.5.0+28

* Wraps CameraX classes needed to implement setting focus and exposure points and exposure offset.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -284,6 +289,7 @@ class AndroidCameraCameraX extends CameraPlatform {
camera = await processCameraProvider!.bindToLifecycle(
cameraSelector!, <UseCase>[preview!, imageCapture!, imageAnalysis!]);
await _updateCameraInfoAndLiveCameraState(flutterSurfaceTextureId);
previewInitiallyBound = true;
_previewIsPaused = false;

return flutterSurfaceTextureId;
Expand Down Expand Up @@ -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<void>(
future: _bindPreviewToLifecycle(cameraId),
builder: (BuildContext context, AsyncSnapshot<void> 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.
Expand Down
2 changes: 1 addition & 1 deletion packages/camera/camera_android_camerax/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<CameraState>, that is created to
// track camera state once preview is bound to the lifecycle.
camera.proxy =
CameraXProxy(createCameraStateObserver: (_) => MockObserver());

when(mockProcessCameraProvider
.bindToLifecycle(camera.cameraSelector, <UseCase>[camera.preview!]))
.thenAnswer((_) async => mockCamera);
when(mockCamera.getCameraInfo()).thenAnswer((_) async => mockCameraInfo);
when(mockCameraInfo.getCameraState())
.thenAnswer((_) async => MockLiveCameraState());
const int cameraId = 73;

final FutureBuilder<void> previewWidget =
camera.buildPreview(textureId) as FutureBuilder<void>;
// 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<void>.nothing()),
isA<SizedBox>());
expect(
previewWidget.builder(
MockBuildContext(), const AsyncSnapshot<void>.waiting()),
isA<SizedBox>());
expect(
previewWidget.builder(MockBuildContext(),
const AsyncSnapshot<void>.withData(ConnectionState.active, null)),
isA<SizedBox>());
() => camera.buildPreview(cameraId), throwsA(isA<CameraException>()));
});

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<CameraState>, 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, <UseCase>[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<void> previewWidget =
camera.buildPreview(textureId) as FutureBuilder<void>;
final Widget widget = camera.buildPreview(cameraId);

final Texture previewTexture = previewWidget.builder(MockBuildContext(),
const AsyncSnapshot<void>.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', () {
Expand Down