Skip to content

[camera_avfoundation] fix race condition when starting image stream on iOS #8733

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

Open
wants to merge 22 commits into
base: main
Choose a base branch
from

Conversation

js2702
Copy link

@js2702 js2702 commented Feb 27, 2025

Fixes flutter/flutter#147702

It's not easy to reproduce this with the example application (as shown in the linked issue). We've been able to reproduce it in our own application which makes heavy usage of Platform channels and different native plugins. After digging into the code we notice that:

The Dart code that starts listening to the event channel was being executed before initializing the stream in the native side.

I don't have much experience with Objective-C, so not sure how to create unit tests for that if necessary but I believe this would be an adecuate solution. I've seen other methods in the plugin that use the same strategy passing the completion object.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@js2702 js2702 requested a review from hellohuanlin as a code owner February 27, 2025 17:24
@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!).

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group.

Copy link

google-cla bot commented Feb 27, 2025

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@js2702 js2702 force-pushed the fix_startstream_race_condition branch from 89cc825 to e902881 Compare February 27, 2025 17:37
@js2702
Copy link
Author

js2702 commented Feb 27, 2025

I'm not sure why but I'm getting a 400 error when signing the CLA (Which I thought I had already signed for other PR)

@hellohuanlin
Copy link
Contributor

This will require unit test in order to land.

@js2702
Copy link
Author

js2702 commented Feb 27, 2025

I'm looking for any example test but I don't see anything. As this doesn't affect the Dart part I don't see how to make a test for this, wouldn't this be test exempt?

Additionally, this bug in particular manifests as a race condition in the communication with Dart and native, requiring an end to end test, which I'm not seeing either.

@stuartmorgan-g
Copy link
Contributor

stuartmorgan-g commented Feb 27, 2025

As this doesn't affect the Dart part I don't see how to make a test for this, wouldn't this be test exempt?

There is no blanket test exemption for native code; if there were, almost nothing in plugins would be tested. Please see https://github.com/flutter/flutter/blob/master/docs/ecosystem/testing/Plugin-Tests.md

js2702 added 3 commits March 1, 2025 15:06
# Conflicts:
#	packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj
#	packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTest.m
@js2702
Copy link
Author

js2702 commented Mar 2, 2025

I added a test for the issue. Everything was working before this last Swift refactor. A few things:

  1. Now these tests that were just changed in StreamingTests.swift (I did not change this file in the PR, as I'm not sure why this is needed now) fail because they are not async/await. Not sure why this happens as I don't see anything that would need async. These are the calls that fail:

https://github.com/SkillDevs/flutter_packages/blob/423c038bdb06ee8d75a2e6523e1ce688e0b555a0/packages/camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift#L74

  1. In my tests, I'm not sure if there is a better way to expect the streamHandler is initialized instead of checking the boolean flag.
  2. Also, in this completion call
    https://github.com/SkillDevs/flutter_packages/blob/423c038bdb06ee8d75a2e6523e1ce688e0b555a0/packages/camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m#L1206
    Should the completion be at the beginning, on each early return, or it's fine where it is?

Again, not an expert in Swift/Objective-C.

Also, the test was developed in Master and it failed unless you added a delay before the expect.

@js2702
Copy link
Author

js2702 commented Mar 2, 2025

Ended up adding the async/await because the tests pass with that, I guess it's because of the callback added to the function?

@hellohuanlin
Copy link
Contributor

Ended up adding the async/await because the tests pass with that, I guess it's because of the callback added to the function?

The async/await api is generated. Can you use completion callbacks to be consistent with other tests?

js2702 added 2 commits March 5, 2025 13:04
…ondition

# Conflicts:
#	packages/camera/camera_avfoundation/CHANGELOG.md
@js2702
Copy link
Author

js2702 commented Mar 5, 2025

@hellohuanlin I updated the tests to use the completion callback, I had to move the expectation creation step of the streaming test because the existing expectation only were run when actually capturing output.

@js2702
Copy link
Author

js2702 commented Mar 6, 2025

@hellohuanlin I don't even know what is happening with the formating checks in CI, it keeps flipping between:

-extern const char *FLTCaptureSessionQueueSpecific;
+extern const char* FLTCaptureSessionQueueSpecific;

If I make the change the CI will apply the other formatting.

@stuartmorgan-g
Copy link
Contributor

@hellohuanlin I don't even know what is happening with the formating checks in CI, it keeps flipping between:

-extern const char *FLTCaptureSessionQueueSpecific;
+extern const char* FLTCaptureSessionQueueSpecific;

If I make the change the CI will apply the other formatting.

I think you're conflating the output for two different files: QueueUtils.m, and QueueUtils.h. I see consistent CI output across each file for the last three runs. The formatting between the files isn't consistent, but that's just a quirk of the header not being identified by clang-format as Obj-C rather than C, and our pointer alignment rules are different for Obj-C.

@js2702
Copy link
Author

js2702 commented Mar 6, 2025

@stuartmorgan Thank you but I'm not sure I'm following, what would be the fix? I'm running the provided format command and it doesn't change anything in the current state of the PR.

This is the output

❯ dart run script/tool/bin/flutter_plugin_tools.dart format --packages camera_avfoundation
Formatting .cc, .cpp, .h, .m, and .mm files...
Running command: "clang-format -i --style=file camera/camera_avfoundation/example/ios/Runner/AppDelegate.h camera/camera_avfoundation/example/ios/Runner/AppDelegate.m camera/camera_avfoundation/example/ios/Runner/GeneratedPluginRegistrant.h camera/camera_avfoundation/example/ios/Runner/GeneratedPluginRegistrant.m camera/camera_avfoundation/example/ios/Runner/main.m camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.h camera/camera_avfoundation/example/ios/RunnerTests/CameraTestUtils.m camera/camera_avfoundation/example/ios/RunnerTests/CameraUtilTests.m camera/camera_avfoundation/example/ios/RunnerTests/ExceptionCatcher.h camera/camera_avfoundation/example/ios/RunnerTests/ExceptionCatcher.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockAssetWriter.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockAssetWriter.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCameraDeviceDiscoverer.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCameraDeviceDiscoverer.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureConnection.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureConnection.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureDevice.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureDevice.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureDeviceFormat.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureDeviceFormat.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCapturePhotoOutput.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureSession.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockCaptureSession.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockDeviceOrientationProvider.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockDeviceOrientationProvider.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFlutterBinaryMessenger.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFlutterBinaryMessenger.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFlutterTextureRegistry.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFlutterTextureRegistry.m camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockGlobalEventApi.h camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockGlobalEventApi.m camera/camera_avfoundation/example/ios/RunnerTests/RunnerTests-Bridging-Header.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraPlugin.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/CameraProperties.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTAssetWriter.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCam.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCamConfiguration.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCamMediaSettingsAVWrapper.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraDeviceDiscovering.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCameraPermissionManager.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureConnection.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureDevice.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureDeviceFormat.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCapturePhotoOutput.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTCaptureSession.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTDeviceOrientationProviding.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTPermissionServicing.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTSavePhotoDelegate.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTThreadSafeEventChannel.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/FLTWritableData.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/QueueUtils.m camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation-umbrella.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraPlugin.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraPlugin_Test.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/CameraProperties.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTAssetWriter.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCamConfiguration.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCamMediaSettingsAVWrapper.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCam_Test.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraDeviceDiscovering.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCameraPermissionManager.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCaptureConnection.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCaptureDevice.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCaptureDeviceFormat.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCapturePhotoOutput.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTCaptureSession.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTDeviceOrientationProviding.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTEventChannel.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTPermissionServicing.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTSavePhotoDelegate.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTSavePhotoDelegate_Test.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTThreadSafeEventChannel.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/FLTWritableData.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/QueueUtils.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/include/camera_avfoundation/messages.g.h camera/camera_avfoundation/ios/camera_avfoundation/Sources/camera_avfoundation/messages.g.m" in /Users/jorge/Developer/packages/packages
Formatting .swift files...
Running command: "swift-format -i camera/camera_avfoundation/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift camera/camera_avfoundation/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift camera/camera_avfoundation/example/ios/RunnerTests/AvailableCamerasTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraInitRaceConditionsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginCreateCameraTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginInitializeCameraTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFLTCam.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFLTCameraPermissionManager.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift camera/camera_avfoundation/example/ios/RunnerTests/QueueUtilsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift camera/camera_avfoundation/ios/camera_avfoundation/Package.swift" in /Users/jorge/Developer/packages/packages
Linting .swift files...
Running command: "swift-format lint --parallel --strict camera/camera_avfoundation/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Package.swift camera/camera_avfoundation/example/ios/Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage/Sources/FlutterGeneratedPluginSwiftPackage/FlutterGeneratedPluginSwiftPackage.swift camera/camera_avfoundation/example/ios/RunnerTests/AvailableCamerasTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraExposureTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraFocusTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraInitRaceConditionsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraMethodChannelTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraOrientationTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPermissionTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginCreateCameraTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPluginInitializeCameraTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPreviewPauseTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraPropertiesTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraSessionPresetsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/CameraSettingsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockEventChannel.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFLTCam.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockFLTCameraPermissionManager.swift camera/camera_avfoundation/example/ios/RunnerTests/Mocks/MockWritableData.swift camera/camera_avfoundation/example/ios/RunnerTests/PhotoCaptureTests.swift camera/camera_avfoundation/example/ios/RunnerTests/QueueUtilsTests.swift camera/camera_avfoundation/example/ios/RunnerTests/SampleBufferTests.swift camera/camera_avfoundation/example/ios/RunnerTests/SavePhotoDelegateTests.swift camera/camera_avfoundation/example/ios/RunnerTests/StreamingTests.swift camera/camera_avfoundation/example/ios/RunnerTests/ThreadSafeEventChannelTests.swift camera/camera_avfoundation/ios/camera_avfoundation/Package.swift" in /Users/jorge/Developer/packages/packages

============================================================
|| Running for packages/camera/camera_avfoundation
============================================================

Formatting .dart files...
Running command: "dart format example/integration_test/camera_test.dart example/lib/camera_controller.dart example/lib/camera_preview.dart example/lib/main.dart example/test_driver/integration_test.dart lib/camera_avfoundation.dart lib/src/avfoundation_camera.dart lib/src/messages.g.dart lib/src/type_conversion.dart lib/src/utils.dart pigeons/messages.dart test/avfoundation_camera_test.dart test/avfoundation_camera_test.mocks.dart test/flutter_test_config.dart test/type_conversion_test.dart test/utils_test.dart" in /Users/jorge/Developer/packages/packages/camera/camera_avfoundation
Formatted 16 files (0 changed) in 0.39 seconds.


------------------------------------------------------------
Run overview:
  packages/camera/camera_avfoundation - ran

Ran for 1 package(s)


No issues found!

@stuartmorgan-g
Copy link
Contributor

The fix is to manually change the files to match what the CI diff output says the lines should look like.

I'm running the provided format command and it doesn't change anything in the current state of the PR.

You probably have a newer version of clang-format with slightly different header detection logic. We don't currently enforce a specific version locally, so occasionally it's necessary to make changes manually. The version used in CI is the source of truth.

@@ -1197,6 +1200,7 @@ - (void)startImageStreamWithMessenger:(NSObject<FlutterBinaryMessenger> *)messen

strongSelf.isStreamingImages = YES;
strongSelf.streamingPendingFramesCount = 0;
completion(nil);
Copy link
Contributor

Choose a reason for hiding this comment

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

the format seems off

Copy link
Author

Choose a reason for hiding this comment

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

Do you want the \n removed there?

Copy link
Contributor

Choose a reason for hiding this comment

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

oh sorry the github web ui shows it weirdly. This is good.

Copy link
Contributor

@hellohuanlin hellohuanlin left a comment

Choose a reason for hiding this comment

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

The fix looks reasonable. Does the test fail before the change?

@js2702
Copy link
Author

js2702 commented Mar 18, 2025

Yes, in the current state the test fails.

@@ -224,8 +224,7 @@ - (void)initializeCamera:(NSInteger)cameraId
- (void)startImageStreamWithCompletion:(nonnull void (^)(FlutterError *_Nullable))completion {
__weak typeof(self) weakSelf = self;
dispatch_async(self.captureSessionQueue, ^{
[weakSelf.camera startImageStreamWithMessenger:weakSelf.messenger];
completion(nil);
[weakSelf.camera startImageStreamWithMessenger:weakSelf.messenger withCompletion:completion];
Copy link
Contributor

Choose a reason for hiding this comment

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

This needs to be converted to a strongSelf, and a codepath added that calls completion(nil) if strongSelf is nil. As written, this will never call completion if weakSelf is nil, which violates the API contract of platform channel completion handlers.

Copy link
Author

Choose a reason for hiding this comment

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

I made some changes as you suggested, hopefully is OK now.

@@ -842,7 +842,7 @@ - (void)startVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))com
messengerForStreaming:(nullable NSObject<FlutterBinaryMessenger> *)messenger {
if (!_isRecording) {
if (messenger != nil) {
[self startImageStreamWithMessenger:messenger];
[self startImageStreamWithMessenger:messenger withCompletion:completion];
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is completion being passed to a method and also used below? This looks like it will call completion multiple times, which has undefined behavior.

Copy link
Author

Choose a reason for hiding this comment

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

Good catch, unfortunately providing nil for the completion seems to break the interfaces because _test interface is included in the FLTCam.m. My Objective-C knowledge is quite limited so I put an empty callback there which also works.

If you want a nullable completion I would appreciate if you could finish up this small change.

js2702 added 3 commits March 21, 2025 12:59
…condition

# Conflicts:
#	packages/camera/camera_avfoundation/CHANGELOG.md
#	packages/camera/camera_avfoundation/pubspec.yaml
@js2702 js2702 requested a review from stuartmorgan-g March 24, 2025 12:30
createExpectation.fulfill()
}

waitForExpectations(timeout: 30, handler: nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are multiple expectation waits required? Usually this only needs to be done at the end of a test.

Copy link
Author

Choose a reason for hiding this comment

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

Initially I was using async/await to wait for the camera creation in the test, before starting the stream. From an early review it was mentioned not using async/await in the tests so I changed it to two calls for waitForExpectations. The first one waits for the camera initialization and the second one actually checks for the stream initialization.

finishStartStreamExpectation.fulfill()
})

waitForExpectations(timeout: 30, handler: nil)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here; why are there expectation waits?

Copy link
Author

Choose a reason for hiding this comment

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

Same as before

@@ -847,7 +847,10 @@ - (void)startVideoRecordingWithCompletion:(void (^)(FlutterError *_Nullable))com
messengerForStreaming:(nullable NSObject<FlutterBinaryMessenger> *)messenger {
if (!_isRecording) {
if (messenger != nil) {
[self startImageStreamWithMessenger:messenger];
// Start image stream without waiting for result.
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment should explain why waiting isn't necessary here, as it is non-obvious to me why this case is different.

Copy link
Author

Choose a reason for hiding this comment

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

Maybe you are right, how would I wait here so it does not continue before the completion is called?

Copy link
Contributor

Choose a reason for hiding this comment

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

Code that needs to run after the method completes needs to be in the completion block; that's the purpose of the parameter.

Copy link
Author

Choose a reason for hiding this comment

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

Just updated this to call the setup logic in the completion callback. Let me know if this is a proper change. I'm not comfortable with Objective-C.

js2702 added 3 commits April 8, 2025 13:45
# Conflicts:
#	packages/camera/camera_avfoundation/CHANGELOG.md
#	packages/camera/camera_avfoundation/example/ios/Runner.xcodeproj/project.pbxproj
#	packages/camera/camera_avfoundation/pubspec.yaml
@js2702 js2702 requested a review from stuartmorgan-g April 8, 2025 14:28
# Conflicts:
#	packages/camera/camera_avfoundation/CHANGELOG.md
#	packages/camera/camera_avfoundation/example/ios/RunnerTests/CameraInitRaceConditionsTests.swift
@js2702 js2702 requested a review from vashworth as a code owner April 29, 2025 10:56
chaosue pushed a commit to chaosue/flutter_packages that referenced this pull request May 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[camera] regression: startImageStream throws MissingPluginException on iOS
3 participants