[camerax] Implement image capture#3287
Conversation
| private void handleImageFileError( | ||
| @NonNull GeneratedCameraXLibrary.Result<String> result, @NonNull String errorDescription) { | ||
| // Send empty path because image was not saved. | ||
| result.success(""); |
There was a problem hiding this comment.
Looking particularly for feedback on using an empty path to signify a file error.
There was a problem hiding this comment.
What are the other options? In general I would expect either a typed result with meta data or thrown exception to handle (in java). If there was going to be a string return value and neither of the others then I would expect null not empty.
There was a problem hiding this comment.
I agree with you. I think I got caught up in the confusion of resulting with an error versus sending a camera error to the Dart stream. It's my understanding now (based on the current plugin) that we should send a camera error to the stream if an error occurs while an operation is ongoing. Otherwise, we can throw the error directly from the native side. I changed it to that here.
Let me know what you think. Also @bparrishMines will this work well with the wrapping pattern? I want to make sure we throw errors consistently from here on out.
There was a problem hiding this comment.
Yea I agree that this is the ideal way to handle it and is how we handle it for the webview_flutter implementations as well. The native code can just return the exception in result.error and then the Dart platform implementation can handle converting it to a CameraException.
...ra/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/CameraXProxy.java
Outdated
Show resolved
Hide resolved
| return new SystemServicesFlutterApiImpl(binaryMessenger); | ||
| } | ||
|
|
||
| public ImageCapture.Builder createImageCaptureBuilder() { |
There was a problem hiding this comment.
Why make this a method instead of just calling ImageCapture.Builder() were you need it?
There was a problem hiding this comment.
For injecting a mock for testing
| ImageCapture.Builder imageCaptureBuilder = cameraXProxy.createImageCaptureBuilder(); | ||
| if (flashMode != null) { | ||
| // This sets the requested flash mode, but may fail silently. | ||
| imageCaptureBuilder.setFlashMode(flashMode.intValue()); |
There was a problem hiding this comment.
Should there be an error or warning if a flashMode is passed that is larger than an int? Is there a more typesafe variable we can use here like an enum?
There was a problem hiding this comment.
https://developer.android.com/reference/androidx/camera/core/ImageCapture#FLASH_MODE_ON()
The values are not large enough to be a long and in your test you are converting from an int to a long. Why use longs at all?
There was a problem hiding this comment.
Longs are used in the code generated by pigeon. That's the only reason I'm using them here
...ndroid_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java
Outdated
Show resolved
Hide resolved
| private void handleImageFileError( | ||
| @NonNull GeneratedCameraXLibrary.Result<String> result, @NonNull String errorDescription) { | ||
| // Send empty path because image was not saved. | ||
| result.success(""); |
There was a problem hiding this comment.
What are the other options? In general I would expect either a typed result with meta data or thrown exception to handle (in java). If there was going to be a string return value and neither of the others then I would expect null not empty.
...amera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java
Outdated
Show resolved
Hide resolved
...amera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/ImageCaptureTest.java
Outdated
Show resolved
Hide resolved
packages/camera/camera_android_camerax/test/android_camera_camerax_test.dart
Show resolved
Hide resolved
| /// | ||
| /// [cameraId] is not used. | ||
| @override | ||
| Future<XFile> takePicture(int cameraId) async { |
There was a problem hiding this comment.
As I mentioned in the sync meeting. We should leave ImageCapture always bound.
I think something like this would work: Preview, ImageAnalysis and ImageCapture are always bound, since the combination is always supported by the camera hardware. VideoCapture is lazy loaded: it's only bound when the user start recording Video.
The reason to special case VideoCapture is that VideoCapture is not always supported by camera hardware. When the device doesn't support it, CameraX supports it with OpenGL which will introduce an additional cost. We should avoid that cost if necessary.
...ndroid_camerax/android/src/main/java/io/flutter/plugins/camerax/ImageCaptureHostApiImpl.java
Outdated
Show resolved
Hide resolved
packages/camera/camera_android_camerax/lib/src/image_capture.dart
Outdated
Show resolved
Hide resolved
packages/camera/camera_android_camerax/lib/src/image_capture.dart
Outdated
Show resolved
Hide resolved
packages/camera/camera_android_camerax/lib/src/image_capture.dart
Outdated
Show resolved
Hide resolved
|
|
||
| /// Takes a picture and returns the absolute path of where the capture image | ||
| /// was saved. | ||
| Future<String> takePicture() async { |
There was a problem hiding this comment.
I really like this implementation of this method, but I have a few notes to add.
This is returning a String despite the output being a OutputFileResult. I'm assuming this is done because it only has one method, getSavedUri. But when a library returns a class with a single method, there is a pretty good chance they intend to add more at a later date and they don't want to introduce a breaking change.
This is also missing the OutputFileOptions.
But I would guess that the tedious process of wrapping native apis contributed to creating a simpler method. And that's still on me to improve it. So, I would suggest including in the documentation that this method doesn't completely wrap the method from ImageCapture and serves as a convenience method that was created for Dart.
There was a problem hiding this comment.
I didn't wrap those since the goal right now is to reach feature parity, and it was simpler to configure taking a picture the same as the current plugin on the Java side than wrap something we aren't likely to change soon.
I think you make a good point though. I'll leave a comment, and I'm definitely open to coming back to wrap those classes later if needed. Should be straightforward to change.
packages/camera/camera_android_camerax/lib/src/image_capture.dart
Outdated
Show resolved
Hide resolved
…o/flutter/plugins/camerax/ImageCaptureHostApiImpl.java Co-authored-by: Maurice Parrish <[email protected]>
Co-authored-by: Maurice Parrish <[email protected]>
| camera = await processCameraProvider! | ||
| .bindToLifecycle(cameraSelector!, <UseCase>[preview!, imageCapture!]); | ||
| _previewIsPaused = false; | ||
|
|
There was a problem hiding this comment.
@reidbaker @gmackall @bparrishMines To address @xizhang's comment, I did some significant refactoring in this method if any of you are willing to take a look.
The main change is that I configured the Preview and ImageCapture use cases here and bind them to the lifecycle together.
| assert(cameraSelector != null); | ||
| assert(preview != null); | ||
|
|
||
| final bool previewIsBound = await processCameraProvider!.isBound(preview!); |
There was a problem hiding this comment.
@reidbaker @gmackall @bparrishMines The other major change I made is not relying on class variables to know if a UseCase has been bound or not. I wrapped isBound to achieve that.
|
Note to self: This needs to land after #3318 to avoid issues internally. |
[camerax] Implement image capture
Wraps
ImageCaptureCameraXUseCaseand uses it to implementtakePicture().Fixes flutter/flutter#111140.
Pre-launch Checklist
dart format.)[shared_preferences]pubspec.yamlwith an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.mdto add a description of the change, following repository CHANGELOG style.///).