From f09d84bbd35a1356c14aae84ba686c7e05b43676 Mon Sep 17 00:00:00 2001 From: Hari07 <22373191+Hari-07@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:41:50 +0530 Subject: [PATCH 1/4] Fix returning the value before dismissing animation finishes --- .../Sources/image_picker_ios/FLTImagePickerPlugin.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m index 82991980d0c7..a39c830d38bc 100644 --- a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m @@ -533,12 +533,15 @@ - (void)picker:(PHPickerViewController *)picker - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { - NSURL *videoURL = info[UIImagePickerControllerMediaURL]; __weak typeof(self) weakSelf = self; [picker dismissViewControllerAnimated:YES completion:^{ [weakSelf removeInteractionBlocker]; + [weakSelf sendCallResultWithPickerInfo:info]; }]; +} + +- (void)sendCallResultWithPickerInfo:(NSDictionary *)info { // The method dismissViewControllerAnimated does not immediately prevent // further didFinishPickingMediaWithInfo invocations. A nil check is necessary // to prevent below code to be unwantly executed multiple times and cause a @@ -546,6 +549,7 @@ - (void)imagePickerController:(UIImagePickerController *)picker if (!self.callContext) { return; } + NSURL *videoURL = info[UIImagePickerControllerMediaURL]; if (videoURL != nil) { if (@available(iOS 13.0, *)) { NSURL *destination = [FLTImagePickerPhotoAssetUtil saveVideoFromURL:videoURL]; @@ -628,8 +632,8 @@ - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { [picker dismissViewControllerAnimated:YES completion:^{ [weakSelf removeInteractionBlocker]; + [weakSelf sendCallResultWithSavedPathList:nil]; }]; - [self sendCallResultWithSavedPathList:nil]; } #pragma mark - From 3361754aec63aa50a662daf936ed35a1252ccb81 Mon Sep 17 00:00:00 2001 From: Hari07 <22373191+Hari-07@users.noreply.github.com> Date: Sun, 12 Jul 2026 13:08:35 +0530 Subject: [PATCH 2/4] Pubspec and changelog --- packages/image_picker/image_picker_ios/CHANGELOG.md | 4 ++++ packages/image_picker/image_picker_ios/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/image_picker/image_picker_ios/CHANGELOG.md b/packages/image_picker/image_picker_ios/CHANGELOG.md index 58dae8e1b635..d049de0ffcdc 100644 --- a/packages/image_picker/image_picker_ios/CHANGELOG.md +++ b/packages/image_picker/image_picker_ios/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.13+7 + +* Fixes early dismissal of image picker. + ## 0.8.13+6 * Replaces deprecated `kUTTypeGIF` with `UTTypeGIF` to fix iOS 15+ deprecation warnings. diff --git a/packages/image_picker/image_picker_ios/pubspec.yaml b/packages/image_picker/image_picker_ios/pubspec.yaml index 076169304097..11d0cd04e4f9 100755 --- a/packages/image_picker/image_picker_ios/pubspec.yaml +++ b/packages/image_picker/image_picker_ios/pubspec.yaml @@ -2,7 +2,7 @@ name: image_picker_ios description: iOS implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_ios issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.13+6 +version: 0.8.13+7 environment: sdk: ^3.10.0 From ba51c6cac9471b7746bf4c6c17e1bdee8e515538 Mon Sep 17 00:00:00 2001 From: Hari07 <22373191+Hari-07@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:21:31 +0530 Subject: [PATCH 3/4] Add tests --- .../ios/RunnerTests/ImagePickerPluginTests.m | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m index 734c0df56bf6..66d45ebe48f8 100644 --- a/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m +++ b/packages/image_picker/image_picker_ios/example/ios/RunnerTests/ImagePickerPluginTests.m @@ -323,6 +323,67 @@ - (void)testPluginPickImageDeviceCancelClickMultipleTimes { [plugin imagePickerControllerDidCancel:controller]; } +- (void)testCancelResultIsNotSentUntilDismissalCompletes { + FLTImagePickerPlugin *plugin = + [[FLTImagePickerPlugin alloc] initWithViewProvider:[[StubViewProvider alloc] init]]; + __block NSArray *pickResult = nil; + plugin.callContext = [[FLTImagePickerMethodCallContext alloc] + initWithResult:^(NSArray *_Nullable result, FlutterError *_Nullable error) { + pickResult = result; + }]; + + __block void (^dismissCompletion)(void) = nil; + id mockPicker = OCMClassMock([UIImagePickerController class]); + OCMStub([mockPicker + dismissViewControllerAnimated:YES + completion:[OCMArg checkWithBlock:^BOOL(void (^completion)(void)) { + dismissCompletion = [completion copy]; + return YES; + }]]); + + [plugin imagePickerControllerDidCancel:mockPicker]; + + XCTAssertNotNil(dismissCompletion); + XCTAssertNil(pickResult, @"The cancel result should not be sent before dismissal completes."); + + dismissCompletion(); + + XCTAssertEqualObjects(pickResult, @[]); +} + +- (void)testPickResultIsNotSentUntilDismissalCompletes { + FLTImagePickerPlugin *plugin = + [[FLTImagePickerPlugin alloc] initWithViewProvider:[[StubViewProvider alloc] init]]; + __block NSArray *pickResult = nil; + plugin.callContext = [[FLTImagePickerMethodCallContext alloc] + initWithResult:^(NSArray *_Nullable result, FlutterError *_Nullable error) { + pickResult = result; + }]; + + // Capture the dismissal completion instead of running it, so the test controls + // when dismissal "finishes". + __block void (^dismissCompletion)(void) = nil; + id mockPicker = OCMClassMock([UIImagePickerController class]); + OCMStub([mockPicker + dismissViewControllerAnimated:YES + completion:[OCMArg checkWithBlock:^BOOL(void (^completion)(void)) { + dismissCompletion = [completion copy]; + return YES; + }]]); + + UIImage *image = [UIImage imageWithData:ImagePickerTestImages.JPGTestData]; + [plugin imagePickerController:mockPicker + didFinishPickingMediaWithInfo:@{UIImagePickerControllerOriginalImage : image}]; + + XCTAssertNotNil(dismissCompletion); + XCTAssertNil(pickResult, @"The picked media should not be sent before dismissal completes."); + + dismissCompletion(); + + XCTAssertEqual(pickResult.count, 1); + XCTAssertGreaterThan(pickResult.firstObject.length, 0); +} + - (void)testCameraPickerInteractionBlockerWindowIsAddedAndRemoved { id mockUIImagePicker = OCMClassMock([UIImagePickerController class]); id mockAVCaptureDevice = OCMClassMock([AVCaptureDevice class]); From a079e52d1a4a85eabb1ef4a76284a8ae5e56c65b Mon Sep 17 00:00:00 2001 From: Hari07 <22373191+Hari-07@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:59:32 +0530 Subject: [PATCH 4/4] Fix race condition --- .../Sources/image_picker_ios/FLTImagePickerPlugin.m | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m index a39c830d38bc..04442a037574 100644 --- a/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m +++ b/packages/image_picker/image_picker_ios/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m @@ -533,11 +533,14 @@ - (void)picker:(PHPickerViewController *)picker - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { + FLTImagePickerMethodCallContext *context = self.callContext; __weak typeof(self) weakSelf = self; [picker dismissViewControllerAnimated:YES completion:^{ [weakSelf removeInteractionBlocker]; - [weakSelf sendCallResultWithPickerInfo:info]; + if (weakSelf.callContext == context) { + [weakSelf sendCallResultWithPickerInfo:info]; + } }]; } @@ -628,11 +631,14 @@ - (void)sendCallResultWithPickerInfo:(NSDictionary *)info { } - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker { + FLTImagePickerMethodCallContext *context = self.callContext; __weak typeof(self) weakSelf = self; [picker dismissViewControllerAnimated:YES completion:^{ [weakSelf removeInteractionBlocker]; - [weakSelf sendCallResultWithSavedPathList:nil]; + if (weakSelf.callContext == context) { + [weakSelf sendCallResultWithSavedPathList:nil]; + } }]; }