-
Notifications
You must be signed in to change notification settings - Fork 3.3k
[camerax] Implement onCameraClosing #3878
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
Conversation
…o/flutter/plugins/camerax/ImageCaptureHostApiImpl.java Co-authored-by: Maurice Parrish <[email protected]>
Co-authored-by: Maurice Parrish <[email protected]>
@@ -312,17 +332,33 @@ class AndroidCameraCameraX extends CameraPlatform { | |||
/// [cameraId] not used. | |||
@override | |||
Future<double> getMaxZoomLevel(int cameraId) async { | |||
final ZoomState exposureState = await cameraInfo!.getZoomState(); | |||
return exposureState.maxZoomRatio; | |||
final LiveData<ZoomState> liveZoomState = await cameraInfo!.getZoomState(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it not be better to have a local zoomState
variable that is updated by an Observer
added to liveZoomState
? Same for getMinZoomLevel
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so, because I think it's safe to assume this won't be called often enough for us to want to set an Observer
on it. If there was a Flutter camera method that suggested we'd want to observe changes in the zoom state, I would agree with you.
packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
Outdated
Show resolved
Hide resolved
packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
Outdated
Show resolved
Hide resolved
/// camera is in error state. | ||
Observer<CameraState> _createCameraClosingObserver(int cameraId) { | ||
// Callback method used to implement the behavior described above: | ||
void onChanged(Object stateAsObject) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Garbage collection for Dart is non-deterministic, so testing for it is a little difficult. I do have a test for leaking in the webview_flutter
integration tests that verifies the WebView
is garbage collected: https://github.com/flutter/packages/blob/main/packages/webview_flutter/webview_flutter_android/example/integration_test/webview_flutter_test.dart#L100
I don't think the implementation of the test should block this PR though. Leaks only prevent the release of the strong reference to the object in the Java InstanceManager
. And the camera resources are explicitly released with the methods provided by the API.
I can also help with writing the test.
...ra/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/LiveDataTest.java
Show resolved
Hide resolved
|
||
if (zoomState == null) { | ||
throw CameraException( | ||
'zoomStateNotSet', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be a constant. Both because it is used in multiple places and because callers that want to branch behavior on different CameraExceptions should be able to reference constants instead of strings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved but not a constant and no comment explaining why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're looking at outdated code?
packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
Show resolved
Hide resolved
@@ -447,11 +486,11 @@ class AndroidCameraCameraX extends CameraPlatform { | |||
height: imageProxy.height, | |||
width: imageProxy.width); | |||
|
|||
cameraImageDataStreamController?.add(cameraImageData); | |||
weakThis.target!.cameraImageDataStreamController?.add(cameraImageData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the ?
is ok for cameraImageDataStreamController
why is it not ok for target
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made them both ! because neither should be null!
packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
Outdated
Show resolved
Hide resolved
Future<T?>? getValueFromInstances<T extends Object>( | ||
LiveData<T> instance) async { | ||
LiveDataSupportedTypeData? typeData; | ||
switch (T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
|
||
/// Adds specified [Observer] instance to instance manager and makes call | ||
/// to native side to create the instance. | ||
Future<void> createFromInstances<T>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be createFromInstance
(singular)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was marked resolved but I dont see anything explaining why it should be plural. Non blocking but I am still curious for the answer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're also looking at outdated code here; I changed it to singular.
|
||
expect(callbackParameter, value); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test to make sure createFromInstances copies the values correctly?
Same for other classes if a similar method exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is handled in the host API create tests. createFromInstances calls through to the mockApis so when we test that call, we ensure createFromInstances is handling the values correctly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
/// | ||
/// [shouldCreateDetachedObjectForTesting] is for testing only and thus, | ||
/// is set to `false` by default. | ||
AndroidCameraCameraX({this.shouldCreateDetachedObjectForTesting = false}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this implementation is it possible to call AndroidCameraCameraX(shouldCreateDetachedObjectForTesting: true) from non testing code?
My gut says yes it is and that instead this should be something like
AndroidCameraCameraX({this.shouldCreateDetachedObjectForTesting = false}); | |
AndroidCameraCameraX():this._shouldCreateDetachedObjectForTesting = false; | |
@visibleForTesting | |
AndroidCameraCameraX.forTesting(this._shouldCreateDetachedObjectForTesting ); |
Notice how the parameter is still final but now private and the value is protected by the constructor being visible for testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's still possible to change from non-testing code :/
How would I change this from testing code, though, with this structure? I need a way to set this to true for testing, so is there a way to do that without exposing it also to non-testing code? I would think using a setter, but that's still visible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a patch file with it working you can apply.
protected-testing-object.patch
@@ -404,7 +450,10 @@ class AndroidCameraCameraX extends CameraPlatform { | |||
|
|||
/// Binds [preview] instance to the camera lifecycle controlled by the | |||
/// [processCameraProvider]. | |||
Future<void> _bindPreviewToLifecycle() async { | |||
/// | |||
/// [cameraId] used to properly update the live camera state for the camera |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non blocking: This still does not help me as someone who does not know the implementation what I should do as a caller.
For example [cameraId] the camera reference id that will have its [CameraState] updated when the process for preview is rebound (if required)
I don't actually know if that is true but that is sort of what I see when I read the code. It implies if you pass a bad camera id you might not get state updates but that everything else will probably work.
@@ -447,11 +499,14 @@ class AndroidCameraCameraX extends CameraPlatform { | |||
height: imageProxy.height, | |||
width: imageProxy.width); | |||
|
|||
cameraImageDataStreamController?.add(cameraImageData); | |||
weakThis.target!.cameraImageDataStreamController!.add(cameraImageData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is likely that I just misunderstand how our weakThis works but wouldnt the point of a weak reference be that target can be null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second issue, best I can tell this is in _bindPreviewToLifecycle is there a promised relationship that this method is called after onStreamedFrameAvailable? if not then cameraImageDataStreamController I think can be null.
Reading this line of code I see 2 places where it should be expected that we cant set cameraImageData.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First issue: @bparrishMines am I misunderstanding how weakThis should be used? Should I allow for the target to be null to avoid leaks?
Second issue: Yes, this is a helper method used to set up the image streaming that is called from listening to the stream returned by onStreamedFrameAvailable, so it should never be called really any other time. cameraImageDataStreamController is set up before that, so it would never be null here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the point of the weak reference is to ensure the callback method doesn't prevent garbage collection of the instance that contains it. The target
COULD be null, but the !
is just a runtime check that can be used if you expect the target
to always be available in the callback.
For example, if you expect the user to eventually ALWAYS call CameraController.dispose
and you do some kind of cleanup in that method that stops image streaming callbacks. Then I think it is reasonable to expect this to always be nonnull. But a test would also be helpful for ensuring this and preventing a null pointer exception.
As a comparison, weview_flutter
always uses weakThis.target?...
because there is no WebViewController.dispose
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you described is what we can expect because dispose
must be called whenever users want to release camera resources. I didn't properly handle this for image streaming so I handled that and updated the test for dispose.
/// If a previous [liveCameraState] was stored, existing observers are | ||
/// removed, as well. | ||
Future<void> _updateLiveCameraState(int cameraId) async { | ||
final CameraInfo cameraInfo = await camera!.getCameraInfo(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non blocking. I still find this weird that we need to pass a cameraId (and use that name) when we have a reference to a camera object already and we dont use one to validate against the other. Is there an oppetunity to use more descriptive variable names here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this gets at the bigger question as to why cameraId
is used throughout the Flutter camera API to begin with. Really, cameraId
is the ID of the Flutter surface texture used to render a preview to, and thus, is almost totally unrelated to the actual camera
instance we use to do any camera functionality. The Flutter surface texture ID is returned by calls to createCamera(...)
, so I assume it is just an ID that folks using the plugin can mostly use to filter through CameraEvents
added to the various camera event streams (because they are constructed with an ID) because otherwise, at least in the Android camera implementations, it goes mostly unused.
I'm on the fence about renaming cameraId
, because while it would be more useful to name it something more like flutterSurfaceTextureId
, it's not named that in the actual camera platform interface, so it could cause more confusion for folks trying to contribute to the plugin.
However, I am on board for bettering documentation in the implementation around what it is (always referring to the Flutter surface texture ID/createCamera
), when it is actually being used in a method, and if it is being used, exactly how. I'll file an issue before landing this for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flutter/flutter#126270 filed for improving documentation
void onChanged(Object stateAsObject) { | ||
final WeakReference<AndroidCameraCameraX> weakThis = | ||
WeakReference<AndroidCameraCameraX>(this); | ||
final CameraState state = stateAsObject as CameraState; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a comment to that effect since one class is relying on the known implementation of another class and not on its api surface.
packages/camera/camera_android_camerax/lib/src/android_camera_camerax.dart
Outdated
Show resolved
Hide resolved
…camerax.dart Co-authored-by: Reid Baker <[email protected]>
### Overview Continuation of flutter#3419. Implements onCameraClosing that will send an event anytime the camera closes, as detected by observing the LiveData of the CameraState of the current camera being used in the plugin. Fixes flutter/flutter#121162. ### Highlights of what has changed since the latest reviews of the [last PR](flutter#3419): - Wrapped `LiveData#getValue` ([docs](https://developer.android.com/reference/androidx/lifecycle/LiveData#getValue())) for the purposes of retrieving current value `LiveData<ZoomState>` to properly implement retrieving zoom information - Modified Dart Host API and FlutterAPI implementations' documentation on their `binaryMessenger` and `instanceManager` documentation to be clearer as per suggestion from comments on the original PR - Changed `binaryMessenger` and `instanceManager` fields to be private in Flutter API implementations - Removed `cast` method and added enum `LiveDataSupportedType` to pigeon file to use for safely casting the `LiveData` generic when received on the Dart or native side. - Moved `CameraStateError` code decoding to Dart side as this was written by me and not directly provided by CameraX. - Added tests for the conversion of objects. - The following issues were filed to capture feedback given but not addressed in this PR: flutter/flutter#125926, flutter/flutter#125928.
Overview
Continuation of #3419.
Implements onCameraClosing that will send an event anytime the camera closes, as detected by observing the LiveData of the CameraState of the current camera being used in the plugin.
Fixes flutter/flutter#121162.
Highlights of what has changed since the latest reviews of the last PR:
LiveData#getValue
(docs) for the purposes of retrieving current valueLiveData<ZoomState>
to properly implement retrieving zoom informationbinaryMessenger
andinstanceManager
documentation to be clearer as per suggestion from comments on the original PRbinaryMessenger
andinstanceManager
fields to be private in Flutter API implementationscast
method and added enumLiveDataSupportedType
to pigeon file to use for safely casting theLiveData
generic when received on the Dart or native side.CameraStateError
code decoding to Dart side as this was written by me and not directly provided by CameraX.Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style.///
).