[camera] Run iOS methods on UI thread by default (#4140)
diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index dc83a4f..e6495a8 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.9.4+3
+
+* Fix registerTexture and result being called on background thread on iOS.
+
## 0.9.4+2
* Updated package description;
diff --git a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
index 8520bb0..feb789f 100644
--- a/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj
@@ -7,8 +7,12 @@
objects = {
/* Begin PBXBuildFile section */
+ 033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */; };
03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB766A2665316900CE5A93 /* CameraFocusTests.m */; };
+ 03F6F8B226CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 89D82918721FABF772705DB0 /* libPods-Runner.a */; };
+ 25C3919135C3D981E6F800D0 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */; };
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
@@ -16,9 +20,8 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- A513685080F868CF2695CE75 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5555DD51E06E67921CFA83DD /* libPods-RunnerTests.a */; };
- D065CD815D405ECB22FB1BBA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A4F2DE74AE0C572296A00BF /* libPods-Runner.a */; };
E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */; };
+ F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */ = {isa = PBXBuildFile; fileRef = F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -45,20 +48,22 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraMethodChannelTests.m; sourceTree = "<group>"; };
03BB76682665316900CE5A93 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
03BB766A2665316900CE5A93 /* CameraFocusTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraFocusTests.m; sourceTree = "<group>"; };
03BB766C2665316900CE5A93 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CameraOrientationTests.m; sourceTree = "<group>"; };
+ 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThreadSafeFlutterResultTests.m; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
- 2A4F2DE74AE0C572296A00BF /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+ 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
- 40D9DDFB3787960D28DF3FB3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
- 5555DD51E06E67921CFA83DD /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
- 8F7D83D0CFC9B51065F87CE1 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+ 89D82918721FABF772705DB0 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -67,9 +72,11 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
- A4725B4F24805CD3CA67828F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
- D1FF8C34CA9E9BE702C5EC06 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
+ 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+ A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CameraPreviewPauseTests.m; sourceTree = "<group>"; };
+ F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MockFLTThreadSafeFlutterResult.h; sourceTree = "<group>"; };
+ F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MockFLTThreadSafeFlutterResult.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -77,7 +84,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- A513685080F868CF2695CE75 /* libPods-RunnerTests.a in Frameworks */,
+ 25C3919135C3D981E6F800D0 /* libPods-RunnerTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -85,7 +92,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- D065CD815D405ECB22FB1BBA /* libPods-Runner.a in Frameworks */,
+ 236906D1621AE863A5B2E770 /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -98,16 +105,20 @@
03BB766A2665316900CE5A93 /* CameraFocusTests.m */,
03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */,
03BB766C2665316900CE5A93 /* Info.plist */,
+ 033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */,
+ 03F6F8B126CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m */,
E487C85F26D686A10034AC92 /* CameraPreviewPauseTests.m */,
+ F6EE622E2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m */,
+ F63F9EED27143B19002479BF /* MockFLTThreadSafeFlutterResult.h */,
);
path = RunnerTests;
sourceTree = "<group>";
};
- 78D1009194BD06C03BED950D /* Frameworks */ = {
+ 3242FD2B467C15C62200632F /* Frameworks */ = {
isa = PBXGroup;
children = (
- 2A4F2DE74AE0C572296A00BF /* libPods-Runner.a */,
- 5555DD51E06E67921CFA83DD /* libPods-RunnerTests.a */,
+ 89D82918721FABF772705DB0 /* libPods-Runner.a */,
+ 1944D8072499F3B5E7653D44 /* libPods-RunnerTests.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -131,7 +142,7 @@
03BB76692665316900CE5A93 /* RunnerTests */,
97C146EF1CF9000F007C117D /* Products */,
FD386F00E98D73419C929072 /* Pods */,
- 78D1009194BD06C03BED950D /* Frameworks */,
+ 3242FD2B467C15C62200632F /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -171,10 +182,10 @@
FD386F00E98D73419C929072 /* Pods */ = {
isa = PBXGroup;
children = (
- 8F7D83D0CFC9B51065F87CE1 /* Pods-Runner.debug.xcconfig */,
- A4725B4F24805CD3CA67828F /* Pods-Runner.release.xcconfig */,
- 40D9DDFB3787960D28DF3FB3 /* Pods-RunnerTests.debug.xcconfig */,
- D1FF8C34CA9E9BE702C5EC06 /* Pods-RunnerTests.release.xcconfig */,
+ 59848A7CA98C1FADF8840207 /* Pods-Runner.debug.xcconfig */,
+ 14AE82C910C2A12F2ECB2094 /* Pods-Runner.release.xcconfig */,
+ 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */,
+ A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
@@ -186,7 +197,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 03BB76712665316900CE5A93 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 604FC00FF5713F40F2A4441D /* [CP] Check Pods Manifest.lock */,
+ 422786A96136AA9087A2041B /* [CP] Check Pods Manifest.lock */,
03BB76642665316900CE5A93 /* Sources */,
03BB76652665316900CE5A93 /* Frameworks */,
03BB76662665316900CE5A93 /* Resources */,
@@ -205,7 +216,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 1D0D227A6719C1144CAE5AB5 /* [CP] Check Pods Manifest.lock */,
+ 9872F2A25E8A171A111468CD /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
@@ -282,28 +293,6 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 1D0D227A6719C1144CAE5AB5 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -318,7 +307,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 604FC00FF5713F40F2A4441D /* [CP] Check Pods Manifest.lock */ = {
+ 422786A96136AA9087A2041B /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -354,6 +343,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
+ 9872F2A25E8A171A111468CD /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -361,8 +372,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 03F6F8B226CBB4670024B8D3 /* ThreadSafeFlutterResultTests.m in Sources */,
+ 033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */,
03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */,
E487C86026D686A10034AC92 /* CameraPreviewPauseTests.m in Sources */,
+ F6EE622F2710A6FC00905E4A /* MockFLTThreadSafeFlutterResult.m in Sources */,
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -409,7 +423,7 @@
/* Begin XCBuildConfiguration section */
03BB766F2665316900CE5A93 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 40D9DDFB3787960D28DF3FB3 /* Pods-RunnerTests.debug.xcconfig */;
+ baseConfigurationReference = 9C5CC6CAD53AD388B2694F3A /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -419,6 +433,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = RunnerTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
@@ -434,7 +449,7 @@
};
03BB76702665316900CE5A93 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = D1FF8C34CA9E9BE702C5EC06 /* Pods-RunnerTests.release.xcconfig */;
+ baseConfigurationReference = A24F9E418BA48BCC7409B117 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -444,6 +459,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = "";
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = RunnerTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
@@ -567,6 +583,7 @@
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
@@ -588,6 +605,7 @@
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ DEVELOPMENT_TEAM = "";
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraFocusTests.m b/packages/camera/camera/example/ios/RunnerTests/CameraFocusTests.m
index 27537e7..fdc2be9 100644
--- a/packages/camera/camera/example/ios/RunnerTests/CameraFocusTests.m
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraFocusTests.m
@@ -19,7 +19,7 @@
- (void)applyFocusMode;
- (void)applyFocusMode:(FocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice;
-- (void)setFocusPointWithResult:(FlutterResult)result x:(double)x y:(double)y;
+- (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y;
@end
@interface CameraFocusTests : XCTestCase
@@ -128,11 +128,11 @@
[_camera setValue:_mockDevice forKey:@"captureDevice"];
// Run test
- [_camera
- setFocusPointWithResult:^void(id _Nullable result) {
- }
- x:1
- y:1];
+ [_camera setFocusPointWithResult:[[FLTThreadSafeFlutterResult alloc]
+ initWithResult:^(id _Nullable result){
+ }]
+ x:1
+ y:1];
// Verify the focus point of interest has been set
OCMVerify([_mockDevice setFocusPointOfInterest:CGPointMake(1, 1)]);
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraMethodChannelTests.m b/packages/camera/camera/example/ios/RunnerTests/CameraMethodChannelTests.m
new file mode 100644
index 0000000..254a33c
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraMethodChannelTests.m
@@ -0,0 +1,48 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@import camera;
+@import camera.Test;
+@import XCTest;
+@import AVFoundation;
+#import <OCMock/OCMock.h>
+#import "MockFLTThreadSafeFlutterResult.h"
+
+@interface CameraMethodChannelTests : XCTestCase
+@end
+
+@implementation CameraMethodChannelTests
+
+- (void)testCreate_ShouldCallResultOnMainThread {
+ CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
+
+ XCTestExpectation *expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ // Set up mocks for initWithCameraName method
+ id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
+ OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]])
+ .andReturn([AVCaptureInput alloc]);
+
+ id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]);
+ OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock);
+ OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
+
+ MockFLTThreadSafeFlutterResult *resultObject =
+ [[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation];
+
+ // Set up method call
+ FlutterMethodCall *call = [FlutterMethodCall
+ methodCallWithMethodName:@"create"
+ arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}];
+
+ [camera handleMethodCallAsync:call result:resultObject];
+
+ // Verify the result
+ NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult;
+ XCTAssertNotNil(dictionaryResult);
+ XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]);
+}
+
+@end
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraOrientationTests.m b/packages/camera/camera/example/ios/RunnerTests/CameraOrientationTests.m
index 246eab9..efd65a4 100644
--- a/packages/camera/camera/example/ios/RunnerTests/CameraOrientationTests.m
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraOrientationTests.m
@@ -10,46 +10,52 @@
#import <OCMock/OCMock.h>
@interface CameraOrientationTests : XCTestCase
-@property(strong, nonatomic) id mockMessenger;
-@property(strong, nonatomic) CameraPlugin *cameraPlugin;
@end
@implementation CameraOrientationTests
-- (void)setUp {
- [super setUp];
-
- self.mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
- self.cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil messenger:self.mockMessenger];
-}
-
- (void)testOrientationNotifications {
- id mockMessenger = self.mockMessenger;
+ id mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
+ CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil messenger:mockMessenger];
+
[mockMessenger setExpectationOrderMatters:YES];
- [self rotate:UIDeviceOrientationPortraitUpsideDown expectedChannelOrientation:@"portraitDown"];
- [self rotate:UIDeviceOrientationPortrait expectedChannelOrientation:@"portraitUp"];
- [self rotate:UIDeviceOrientationLandscapeRight expectedChannelOrientation:@"landscapeLeft"];
- [self rotate:UIDeviceOrientationLandscapeLeft expectedChannelOrientation:@"landscapeRight"];
+ [self rotate:UIDeviceOrientationPortraitUpsideDown
+ expectedChannelOrientation:@"portraitDown"
+ cameraPlugin:cameraPlugin
+ messenger:mockMessenger];
+ [self rotate:UIDeviceOrientationPortrait
+ expectedChannelOrientation:@"portraitUp"
+ cameraPlugin:cameraPlugin
+ messenger:mockMessenger];
+ [self rotate:UIDeviceOrientationLandscapeRight
+ expectedChannelOrientation:@"landscapeLeft"
+ cameraPlugin:cameraPlugin
+ messenger:mockMessenger];
+ [self rotate:UIDeviceOrientationLandscapeLeft
+ expectedChannelOrientation:@"landscapeRight"
+ cameraPlugin:cameraPlugin
+ messenger:mockMessenger];
OCMReject([mockMessenger sendOnChannel:[OCMArg any] message:[OCMArg any]]);
// No notification when flat.
- [self.cameraPlugin
+ [cameraPlugin
orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceUp]];
// No notification when facedown.
- [self.cameraPlugin
+ [cameraPlugin
orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceDown]];
OCMVerifyAll(mockMessenger);
}
- (void)rotate:(UIDeviceOrientation)deviceOrientation
- expectedChannelOrientation:(NSString *)channelOrientation {
- id mockMessenger = self.mockMessenger;
+ expectedChannelOrientation:(NSString *)channelOrientation
+ cameraPlugin:(CameraPlugin *)cameraPlugin
+ messenger:(NSObject<FlutterBinaryMessenger> *)messenger {
XCTestExpectation *orientationExpectation = [self expectationWithDescription:channelOrientation];
- OCMExpect([mockMessenger
+ OCMExpect([messenger
sendOnChannel:[OCMArg any]
message:[OCMArg checkWithBlock:^BOOL(NSData *data) {
NSObject<FlutterMethodCodec> *codec = [FlutterStandardMethodCodec sharedInstance];
@@ -60,8 +66,7 @@
[methodCall.arguments isEqualToDictionary:@{@"orientation" : channelOrientation}];
}]]);
- [self.cameraPlugin
- orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
+ [cameraPlugin orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}
diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m b/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m
index 549b40a..eb6c007 100644
--- a/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m
+++ b/packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m
@@ -6,45 +6,37 @@
@import XCTest;
@import AVFoundation;
#import <OCMock/OCMock.h>
+#import "MockFLTThreadSafeFlutterResult.h"
@interface FLTCam : NSObject <FlutterTexture,
AVCaptureVideoDataOutputSampleBufferDelegate,
AVCaptureAudioDataOutputSampleBufferDelegate>
@property(assign, nonatomic) BOOL isPreviewPaused;
-- (void)pausePreviewWithResult:(FlutterResult)result;
-- (void)resumePreviewWithResult:(FlutterResult)result;
+
+- (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result;
+
+- (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result;
@end
@interface CameraPreviewPauseTests : XCTestCase
-@property(readonly, nonatomic) FLTCam* camera;
@end
@implementation CameraPreviewPauseTests
-- (void)setUp {
- _camera = [[FLTCam alloc] init];
-}
-
- (void)testPausePreviewWithResult_shouldPausePreview {
- XCTestExpectation* resultExpectation =
- [self expectationWithDescription:@"Succeeding result with nil value"];
- [_camera pausePreviewWithResult:^void(id _Nullable result) {
- XCTAssertNil(result);
- [resultExpectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- XCTAssertTrue(_camera.isPreviewPaused);
+ FLTCam *camera = [[FLTCam alloc] init];
+ MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init];
+
+ [camera pausePreviewWithResult:resultObject];
+ XCTAssertTrue(camera.isPreviewPaused);
}
- (void)testResumePreviewWithResult_shouldResumePreview {
- XCTestExpectation* resultExpectation =
- [self expectationWithDescription:@"Succeeding result with nil value"];
- [_camera resumePreviewWithResult:^void(id _Nullable result) {
- XCTAssertNil(result);
- [resultExpectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:nil];
- XCTAssertFalse(_camera.isPreviewPaused);
+ FLTCam *camera = [[FLTCam alloc] init];
+ MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init];
+
+ [camera resumePreviewWithResult:resultObject];
+ XCTAssertFalse(camera.isPreviewPaused);
}
@end
diff --git a/packages/camera/camera/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.h b/packages/camera/camera/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.h
new file mode 100644
index 0000000..8685f3f
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.h
@@ -0,0 +1,25 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MockFLTThreadSafeFlutterResult_h
+#define MockFLTThreadSafeFlutterResult_h
+
+/**
+ * Extends FLTThreadSafeFlutterResult to give tests the ability to wait on the result and
+ * read the received result.
+ */
+@interface MockFLTThreadSafeFlutterResult : FLTThreadSafeFlutterResult
+@property(readonly, nonatomic, nonnull) XCTestExpectation *expectation;
+@property(nonatomic, nullable) id receivedResult;
+
+/**
+ * Initializes the MockFLTThreadSafeFlutterResult with an expectation.
+ *
+ * The expectation is fullfilled when a result is called allowing tests to await the result in an
+ * asynchronous manner.
+ */
+- (nonnull instancetype)initWithExpectation:(nonnull XCTestExpectation *)expectation;
+@end
+
+#endif /* MockFLTThreadSafeFlutterResult_h */
diff --git a/packages/camera/camera/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.m b/packages/camera/camera/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.m
new file mode 100644
index 0000000..da2fc2d
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/MockFLTThreadSafeFlutterResult.m
@@ -0,0 +1,27 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@import camera;
+@import XCTest;
+
+#import "MockFLTThreadSafeFlutterResult.h"
+
+@implementation MockFLTThreadSafeFlutterResult
+
+- (instancetype)initWithExpectation:(XCTestExpectation *)expectation {
+ self = [super init];
+ _expectation = expectation;
+ return self;
+}
+
+- (void)sendSuccessWithData:(id)data {
+ self.receivedResult = data;
+ [self.expectation fulfill];
+}
+
+- (void)sendSuccess {
+ self.receivedResult = nil;
+ [self.expectation fulfill];
+}
+@end
diff --git a/packages/camera/camera/example/ios/RunnerTests/ThreadSafeFlutterResultTests.m b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeFlutterResultTests.m
new file mode 100644
index 0000000..8cd4b8b
--- /dev/null
+++ b/packages/camera/camera/example/ios/RunnerTests/ThreadSafeFlutterResultTests.m
@@ -0,0 +1,122 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@import camera;
+@import XCTest;
+
+@interface ThreadSafeFlutterResultTests : XCTestCase
+@end
+
+@implementation ThreadSafeFlutterResultTests
+- (void)testAsyncSendSuccess_ShouldCallResultOnMainThread {
+ XCTestExpectation* expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ FLTThreadSafeFlutterResult* threadSafeFlutterResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) {
+ XCTAssert(NSThread.isMainThread);
+ [expectation fulfill];
+ }];
+ dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL);
+ dispatch_async(dispatchQueue, ^{
+ [threadSafeFlutterResult sendSuccess];
+ });
+
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:1];
+}
+
+- (void)testSyncSendSuccess_ShouldCallResultOnMainThread {
+ XCTestExpectation* expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ FLTThreadSafeFlutterResult* threadSafeFlutterResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) {
+ XCTAssert(NSThread.isMainThread);
+ [expectation fulfill];
+ }];
+ [threadSafeFlutterResult sendSuccess];
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:1];
+}
+
+- (void)testSendNotImplemented_ShouldSendNotImplementedToFlutterResult {
+ XCTestExpectation* expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ FLTThreadSafeFlutterResult* threadSafeFlutterResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) {
+ XCTAssert([result isKindOfClass:FlutterMethodNotImplemented.class]);
+ [expectation fulfill];
+ }];
+ dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL);
+ dispatch_async(dispatchQueue, ^{
+ [threadSafeFlutterResult sendNotImplemented];
+ });
+
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:1];
+}
+
+- (void)testSendErrorDetails_ShouldSendErrorToFlutterResult {
+ NSString* errorCode = @"errorCode";
+ NSString* errorMessage = @"message";
+ NSString* errorDetails = @"error details";
+ XCTestExpectation* expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ FLTThreadSafeFlutterResult* threadSafeFlutterResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) {
+ XCTAssert([result isKindOfClass:FlutterError.class]);
+ FlutterError* error = (FlutterError*)result;
+ XCTAssertEqualObjects(error.code, errorCode);
+ XCTAssertEqualObjects(error.message, errorMessage);
+ XCTAssertEqualObjects(error.details, errorDetails);
+ [expectation fulfill];
+ }];
+ dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL);
+ dispatch_async(dispatchQueue, ^{
+ [threadSafeFlutterResult sendErrorWithCode:errorCode message:errorMessage details:errorDetails];
+ });
+
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:1];
+}
+
+- (void)testSendNSError_ShouldSendErrorToFlutterResult {
+ NSError* originalError = [[NSError alloc] initWithDomain:NSURLErrorDomain code:404 userInfo:nil];
+ XCTestExpectation* expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ FLTThreadSafeFlutterResult* threadSafeFlutterResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) {
+ XCTAssert([result isKindOfClass:FlutterError.class]);
+ FlutterError* error = (FlutterError*)result;
+ NSString* constructedErrorCode =
+ [NSString stringWithFormat:@"Error %d", (int)originalError.code];
+ XCTAssertEqualObjects(error.code, constructedErrorCode);
+ [expectation fulfill];
+ }];
+ dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL);
+ dispatch_async(dispatchQueue, ^{
+ [threadSafeFlutterResult sendError:originalError];
+ });
+
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:1];
+}
+
+- (void)testSendResult_ShouldSendResultToFlutterResult {
+ NSString* resultData = @"resultData";
+ XCTestExpectation* expectation =
+ [[XCTestExpectation alloc] initWithDescription:@"Result finished"];
+
+ FLTThreadSafeFlutterResult* threadSafeFlutterResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:^(id _Nullable result) {
+ XCTAssertEqualObjects(result, resultData);
+ [expectation fulfill];
+ }];
+ dispatch_queue_t dispatchQueue = dispatch_queue_create("test dispatchqueue", NULL);
+ dispatch_async(dispatchQueue, ^{
+ [threadSafeFlutterResult sendSuccessWithData:resultData];
+ });
+
+ [self waitForExpectations:[NSArray arrayWithObject:expectation] timeout:1];
+}
+@end
diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m
index 78c5a34..2c12081 100644
--- a/packages/camera/camera/ios/Classes/CameraPlugin.m
+++ b/packages/camera/camera/ios/Classes/CameraPlugin.m
@@ -10,16 +10,11 @@
#import <CoreMotion/CoreMotion.h>
#import <libkern/OSAtomic.h>
#import <uuid/uuid.h>
-
-static FlutterError *getFlutterError(NSError *error) {
- return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code]
- message:error.localizedDescription
- details:error.domain];
-}
+#import "FLTThreadSafeFlutterResult.h"
@interface FLTSavePhotoDelegate : NSObject <AVCapturePhotoCaptureDelegate>
@property(readonly, nonatomic) NSString *path;
-@property(readonly, nonatomic) FlutterResult result;
+@property(readonly, nonatomic) FLTThreadSafeFlutterResult *result;
@end
@interface FLTImageStreamHandler : NSObject <FlutterStreamHandler>
@@ -45,7 +40,7 @@
FLTSavePhotoDelegate *selfReference;
}
-- initWithPath:(NSString *)path result:(FlutterResult)result {
+- initWithPath:(NSString *)path result:(FLTThreadSafeFlutterResult *)result {
self = [super init];
NSAssert(self, @"super init cannot be nil");
_path = path;
@@ -62,7 +57,7 @@
error:(NSError *)error API_AVAILABLE(ios(10)) {
selfReference = nil;
if (error) {
- _result(getFlutterError(error));
+ [_result sendError:error];
return;
}
@@ -74,10 +69,10 @@
bool success = [data writeToFile:_path atomically:YES];
if (!success) {
- _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]);
+ [_result sendErrorWithCode:@"IOError" message:@"Unable to write file" details:nil];
return;
}
- _result(_path);
+ [_result sendSuccessWithData:_path];
}
- (void)captureOutput:(AVCapturePhotoOutput *)output
@@ -85,7 +80,7 @@
error:(NSError *)error API_AVAILABLE(ios(11.0)) {
selfReference = nil;
if (error) {
- _result(getFlutterError(error));
+ [_result sendError:error];
return;
}
@@ -93,10 +88,10 @@
bool success = [photoData writeToFile:_path atomically:YES];
if (!success) {
- _result([FlutterError errorWithCode:@"IOError" message:@"Unable to write file" details:nil]);
+ [_result sendErrorWithCode:@"IOError" message:@"Unable to write file" details:nil];
return;
}
- _result(_path);
+ [_result sendSuccessWithData:_path];
}
@end
@@ -460,7 +455,7 @@
}
}
-- (void)captureToFile:(FlutterResult)result API_AVAILABLE(ios(10)) {
+- (void)captureToFile:(FLTThreadSafeFlutterResult *)result API_AVAILABLE(ios(10)) {
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
if (_resolutionPreset == max) {
[settings setHighResolutionPhotoEnabled:YES];
@@ -476,7 +471,7 @@
prefix:@"CAP_"
error:error];
if (error) {
- result(getFlutterError(error));
+ [result sendError:error];
return;
}
@@ -816,7 +811,7 @@
return pixelBuffer;
}
-- (void)startVideoRecordingWithResult:(FlutterResult)result {
+- (void)startVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result {
if (!_isRecording) {
NSError *error;
_videoRecordingPath = [self getTemporaryFilePathWithExtension:@"mp4"
@@ -824,11 +819,11 @@
prefix:@"REC_"
error:error];
if (error) {
- result(getFlutterError(error));
+ [result sendError:error];
return;
}
if (![self setupWriterForPath:_videoRecordingPath]) {
- result([FlutterError errorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil]);
+ [result sendErrorWithCode:@"IOError" message:@"Setup Writer Failed" details:nil];
return;
}
_isRecording = YES;
@@ -837,13 +832,13 @@
_audioTimeOffset = CMTimeMake(0, 1);
_videoIsDisconnected = NO;
_audioIsDisconnected = NO;
- result(nil);
+ [result sendSuccess];
} else {
- result([FlutterError errorWithCode:@"Error" message:@"Video is already recording" details:nil]);
+ [result sendErrorWithCode:@"Error" message:@"Video is already recording" details:nil];
}
}
-- (void)stopVideoRecordingWithResult:(FlutterResult)result {
+- (void)stopVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result {
if (_isRecording) {
_isRecording = NO;
@@ -851,12 +846,12 @@
[_videoWriter finishWritingWithCompletionHandler:^{
if (self->_videoWriter.status == AVAssetWriterStatusCompleted) {
[self updateOrientation];
- result(self->_videoRecordingPath);
+ [result sendSuccessWithData:self->_videoRecordingPath];
self->_videoRecordingPath = nil;
} else {
- result([FlutterError errorWithCode:@"IOError"
- message:@"AVAssetWriter could not finish writing!"
- details:nil]);
+ [result sendErrorWithCode:@"IOError"
+ message:@"AVAssetWriter could not finish writing!"
+ details:nil];
}
}];
}
@@ -865,29 +860,29 @@
[NSError errorWithDomain:NSCocoaErrorDomain
code:NSURLErrorResourceUnavailable
userInfo:@{NSLocalizedDescriptionKey : @"Video is not recording!"}];
- result(getFlutterError(error));
+ [result sendError:error];
}
}
-- (void)pauseVideoRecordingWithResult:(FlutterResult)result {
+- (void)pauseVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result {
_isRecordingPaused = YES;
_videoIsDisconnected = YES;
_audioIsDisconnected = YES;
- result(nil);
+ [result sendSuccess];
}
-- (void)resumeVideoRecordingWithResult:(FlutterResult)result {
+- (void)resumeVideoRecordingWithResult:(FLTThreadSafeFlutterResult *)result {
_isRecordingPaused = NO;
- result(nil);
+ [result sendSuccess];
}
-- (void)lockCaptureOrientationWithResult:(FlutterResult)result
+- (void)lockCaptureOrientationWithResult:(FLTThreadSafeFlutterResult *)result
orientation:(NSString *)orientationStr {
UIDeviceOrientation orientation;
@try {
orientation = getUIDeviceOrientationForString(orientationStr);
} @catch (NSError *e) {
- result(getFlutterError(e));
+ [result sendError:e];
return;
}
@@ -896,34 +891,34 @@
[self updateOrientation];
}
- result(nil);
+ [result sendSuccess];
}
-- (void)unlockCaptureOrientationWithResult:(FlutterResult)result {
+- (void)unlockCaptureOrientationWithResult:(FLTThreadSafeFlutterResult *)result {
_lockedCaptureOrientation = UIDeviceOrientationUnknown;
[self updateOrientation];
- result(nil);
+ [result sendSuccess];
}
-- (void)setFlashModeWithResult:(FlutterResult)result mode:(NSString *)modeStr {
+- (void)setFlashModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr {
FlashMode mode;
@try {
mode = getFlashModeForString(modeStr);
} @catch (NSError *e) {
- result(getFlutterError(e));
+ [result sendError:e];
return;
}
if (mode == FlashModeTorch) {
if (!_captureDevice.hasTorch) {
- result([FlutterError errorWithCode:@"setFlashModeFailed"
- message:@"Device does not support torch mode"
- details:nil]);
+ [result sendErrorWithCode:@"setFlashModeFailed"
+ message:@"Device does not support torch mode"
+ details:nil];
return;
}
if (!_captureDevice.isTorchAvailable) {
- result([FlutterError errorWithCode:@"setFlashModeFailed"
- message:@"Torch mode is currently not available"
- details:nil]);
+ [result sendErrorWithCode:@"setFlashModeFailed"
+ message:@"Torch mode is currently not available"
+ details:nil];
return;
}
if (_captureDevice.torchMode != AVCaptureTorchModeOn) {
@@ -933,17 +928,17 @@
}
} else {
if (!_captureDevice.hasFlash) {
- result([FlutterError errorWithCode:@"setFlashModeFailed"
- message:@"Device does not have flash capabilities"
- details:nil]);
+ [result sendErrorWithCode:@"setFlashModeFailed"
+ message:@"Device does not have flash capabilities"
+ details:nil];
return;
}
AVCaptureFlashMode avFlashMode = getAVCaptureFlashModeForFlashMode(mode);
if (![_capturePhotoOutput.supportedFlashModes
containsObject:[NSNumber numberWithInt:((int)avFlashMode)]]) {
- result([FlutterError errorWithCode:@"setFlashModeFailed"
- message:@"Device does not support this specific flash mode"
- details:nil]);
+ [result sendErrorWithCode:@"setFlashModeFailed"
+ message:@"Device does not support this specific flash mode"
+ details:nil];
return;
}
if (_captureDevice.torchMode != AVCaptureTorchModeOff) {
@@ -953,20 +948,20 @@
}
}
_flashMode = mode;
- result(nil);
+ [result sendSuccess];
}
-- (void)setExposureModeWithResult:(FlutterResult)result mode:(NSString *)modeStr {
+- (void)setExposureModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr {
ExposureMode mode;
@try {
mode = getExposureModeForString(modeStr);
} @catch (NSError *e) {
- result(getFlutterError(e));
+ [result sendError:e];
return;
}
_exposureMode = mode;
[self applyExposureMode];
- result(nil);
+ [result sendSuccess];
}
- (void)applyExposureMode {
@@ -986,17 +981,17 @@
[_captureDevice unlockForConfiguration];
}
-- (void)setFocusModeWithResult:(FlutterResult)result mode:(NSString *)modeStr {
+- (void)setFocusModeWithResult:(FLTThreadSafeFlutterResult *)result mode:(NSString *)modeStr {
FocusMode mode;
@try {
mode = getFocusModeForString(modeStr);
} @catch (NSError *e) {
- result(getFlutterError(e));
+ [result sendError:e];
return;
}
_focusMode = mode;
[self applyFocusMode];
- result(nil);
+ [result sendSuccess];
}
- (void)applyFocusMode {
@@ -1036,14 +1031,14 @@
[captureDevice unlockForConfiguration];
}
-- (void)pausePreviewWithResult:(FlutterResult)result {
+- (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result {
_isPreviewPaused = true;
- result(nil);
+ [result sendSuccess];
}
-- (void)resumePreviewWithResult:(FlutterResult)result {
+- (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result {
_isPreviewPaused = false;
- result(nil);
+ [result sendSuccess];
}
- (CGPoint)getCGPointForCoordsWithOrientation:(UIDeviceOrientation)orientation
@@ -1071,11 +1066,11 @@
return CGPointMake(x, y);
}
-- (void)setExposurePointWithResult:(FlutterResult)result x:(double)x y:(double)y {
+- (void)setExposurePointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y {
if (!_captureDevice.isExposurePointOfInterestSupported) {
- result([FlutterError errorWithCode:@"setExposurePointFailed"
- message:@"Device does not have exposure point capabilities"
- details:nil]);
+ [result sendErrorWithCode:@"setExposurePointFailed"
+ message:@"Device does not have exposure point capabilities"
+ details:nil];
return;
}
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
@@ -1086,14 +1081,14 @@
[_captureDevice unlockForConfiguration];
// Retrigger auto exposure
[self applyExposureMode];
- result(nil);
+ [result sendSuccess];
}
-- (void)setFocusPointWithResult:(FlutterResult)result x:(double)x y:(double)y {
+- (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y {
if (!_captureDevice.isFocusPointOfInterestSupported) {
- result([FlutterError errorWithCode:@"setFocusPointFailed"
- message:@"Device does not have focus point capabilities"
- details:nil]);
+ [result sendErrorWithCode:@"setFocusPointFailed"
+ message:@"Device does not have focus point capabilities"
+ details:nil];
return;
}
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
@@ -1105,15 +1100,14 @@
[_captureDevice unlockForConfiguration];
// Retrigger auto focus
[self applyFocusMode];
-
- result(nil);
+ [result sendSuccess];
}
-- (void)setExposureOffsetWithResult:(FlutterResult)result offset:(double)offset {
+- (void)setExposureOffsetWithResult:(FLTThreadSafeFlutterResult *)result offset:(double)offset {
[_captureDevice lockForConfiguration:nil];
[_captureDevice setExposureTargetBias:offset completionHandler:nil];
[_captureDevice unlockForConfiguration];
- result(@(offset));
+ [result sendSuccessWithData:@(offset)];
}
- (void)startImageStreamWithMessenger:(NSObject<FlutterBinaryMessenger> *)messenger {
@@ -1141,19 +1135,18 @@
}
}
-- (void)getMaxZoomLevelWithResult:(FlutterResult)result {
+- (void)getMaxZoomLevelWithResult:(FLTThreadSafeFlutterResult *)result {
CGFloat maxZoomFactor = [self getMaxAvailableZoomFactor];
- result([NSNumber numberWithFloat:maxZoomFactor]);
+ [result sendSuccessWithData:[NSNumber numberWithFloat:maxZoomFactor]];
}
-- (void)getMinZoomLevelWithResult:(FlutterResult)result {
+- (void)getMinZoomLevelWithResult:(FLTThreadSafeFlutterResult *)result {
CGFloat minZoomFactor = [self getMinAvailableZoomFactor];
-
- result([NSNumber numberWithFloat:minZoomFactor]);
+ [result sendSuccessWithData:[NSNumber numberWithFloat:minZoomFactor]];
}
-- (void)setZoomLevel:(CGFloat)zoom Result:(FlutterResult)result {
+- (void)setZoomLevel:(CGFloat)zoom Result:(FLTThreadSafeFlutterResult *)result {
CGFloat maxAvailableZoomFactor = [self getMaxAvailableZoomFactor];
CGFloat minAvailableZoomFactor = [self getMinAvailableZoomFactor];
@@ -1161,22 +1154,20 @@
NSString *errorMessage = [NSString
stringWithFormat:@"Zoom level out of bounds (zoom level should be between %f and %f).",
minAvailableZoomFactor, maxAvailableZoomFactor];
- FlutterError *error = [FlutterError errorWithCode:@"ZOOM_ERROR"
- message:errorMessage
- details:nil];
- result(error);
+
+ [result sendErrorWithCode:@"ZOOM_ERROR" message:errorMessage details:nil];
return;
}
NSError *error = nil;
if (![_captureDevice lockForConfiguration:&error]) {
- result(getFlutterError(error));
+ [result sendError:error];
return;
}
_captureDevice.videoZoomFactor = zoom;
[_captureDevice unlockForConfiguration];
- result(nil);
+ [result sendSuccess];
}
- (CGFloat)getMinAvailableZoomFactor {
@@ -1303,6 +1294,7 @@
@implementation CameraPlugin {
dispatch_queue_t _dispatchQueue;
}
+
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/camera"
@@ -1366,11 +1358,15 @@
// Invoke the plugin on another dispatch queue to avoid blocking the UI.
dispatch_async(_dispatchQueue, ^{
- [self handleMethodCallAsync:call result:result];
+ FLTThreadSafeFlutterResult *threadSafeResult =
+ [[FLTThreadSafeFlutterResult alloc] initWithResult:result];
+
+ [self handleMethodCallAsync:call result:threadSafeResult];
});
}
-- (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result {
+- (void)handleMethodCallAsync:(FlutterMethodCall *)call
+ result:(FLTThreadSafeFlutterResult *)result {
if ([@"availableCameras" isEqualToString:call.method]) {
if (@available(iOS 10.0, *)) {
AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
@@ -1399,9 +1395,9 @@
@"sensorOrientation" : @90,
}];
}
- result(reply);
+ [result sendSuccessWithData:reply];
} else {
- result(FlutterMethodNotImplemented);
+ [result sendNotImplemented];
}
} else if ([@"create" isEqualToString:call.method]) {
NSString *cameraName = call.arguments[@"cameraName"];
@@ -1416,24 +1412,23 @@
error:&error];
if (error) {
- result(getFlutterError(error));
+ [result sendError:error];
} else {
if (_camera) {
[_camera close];
}
- int64_t textureId = [_registry registerTexture:cam];
+ int64_t textureId = [self.registry registerTexture:cam];
_camera = cam;
-
- result(@{
+ [result sendSuccessWithData:@{
@"cameraId" : @(textureId),
- });
+ }];
}
} else if ([@"startImageStream" isEqualToString:call.method]) {
[_camera startImageStreamWithMessenger:_messenger];
- result(nil);
+ [result sendSuccess];
} else if ([@"stopImageStream" isEqualToString:call.method]) {
[_camera stopImageStream];
- result(nil);
+ [result sendSuccess];
} else {
NSDictionary *argsMap = call.arguments;
NSUInteger cameraId = ((NSNumber *)argsMap[@"cameraId"]).unsignedIntegerValue;
@@ -1465,21 +1460,21 @@
}];
[self sendDeviceOrientation:[UIDevice currentDevice].orientation];
[_camera start];
- result(nil);
+ [result sendSuccess];
} else if ([@"takePicture" isEqualToString:call.method]) {
if (@available(iOS 10.0, *)) {
[_camera captureToFile:result];
} else {
- result(FlutterMethodNotImplemented);
+ [result sendNotImplemented];
}
} else if ([@"dispose" isEqualToString:call.method]) {
[_registry unregisterTexture:cameraId];
[_camera close];
_dispatchQueue = nil;
- result(nil);
+ [result sendSuccess];
} else if ([@"prepareForVideoRecording" isEqualToString:call.method]) {
[_camera setUpCaptureSessionForAudio];
- result(nil);
+ [result sendSuccess];
} else if ([@"startVideoRecording" isEqualToString:call.method]) {
[_camera startVideoRecordingWithResult:result];
} else if ([@"stopVideoRecording" isEqualToString:call.method]) {
@@ -1509,11 +1504,11 @@
}
[_camera setExposurePointWithResult:result x:x y:y];
} else if ([@"getMinExposureOffset" isEqualToString:call.method]) {
- result(@(_camera.captureDevice.minExposureTargetBias));
+ [result sendSuccessWithData:@(_camera.captureDevice.minExposureTargetBias)];
} else if ([@"getMaxExposureOffset" isEqualToString:call.method]) {
- result(@(_camera.captureDevice.maxExposureTargetBias));
+ [result sendSuccessWithData:@(_camera.captureDevice.maxExposureTargetBias)];
} else if ([@"getExposureOffsetStepSize" isEqualToString:call.method]) {
- result(@(0.0));
+ [result sendSuccessWithData:@(0.0)];
} else if ([@"setExposureOffset" isEqualToString:call.method]) {
[_camera setExposureOffsetWithResult:result
offset:((NSNumber *)call.arguments[@"offset"]).doubleValue];
@@ -1537,7 +1532,7 @@
} else if ([@"resumePreview" isEqualToString:call.method]) {
[_camera resumePreviewWithResult:result];
} else {
- result(FlutterMethodNotImplemented);
+ [result sendNotImplemented];
}
}
}
diff --git a/packages/camera/camera/ios/Classes/CameraPlugin_Test.h b/packages/camera/camera/ios/Classes/CameraPlugin_Test.h
index 6952ae0..afbf686 100644
--- a/packages/camera/camera/ios/Classes/CameraPlugin_Test.h
+++ b/packages/camera/camera/ios/Classes/CameraPlugin_Test.h
@@ -5,15 +5,30 @@
// This header is available in the Test module. Import via "@import camera.Test;"
#import <camera/CameraPlugin.h>
+#import <camera/FLTThreadSafeFlutterResult.h>
/// Methods exposed for unit testing.
@interface CameraPlugin ()
+/// Inject @p FlutterTextureRegistry and @p FlutterBinaryMessenger for unit testing.
- (instancetype)initWithRegistry:(NSObject<FlutterTextureRegistry> *)registry
messenger:(NSObject<FlutterBinaryMessenger> *)messenger
NS_DESIGNATED_INITIALIZER;
+
+/// Hide the default public constructor.
- (instancetype)init NS_UNAVAILABLE;
+/// Handles `FlutterMethodCall`s and ensures result is send on the main dispatch queue.
+///
+/// @param call The method call command object.
+/// @param result A wrapper around the `FlutterResult` callback which ensures the callback is called
+/// on the main dispatch queue.
+- (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FLTThreadSafeFlutterResult *)result;
+
+/// Called by the @c NSNotificationManager each time the device's orientation is changed.
+///
+/// @param notification @c NSNotification instance containing a reference to the `UIDevice` object
+/// that triggered the orientation change.
- (void)orientationChanged:(NSNotification *)notification;
@end
diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeFlutterResult.h b/packages/camera/camera/ios/Classes/FLTThreadSafeFlutterResult.h
new file mode 100644
index 0000000..f290ca0
--- /dev/null
+++ b/packages/camera/camera/ios/Classes/FLTThreadSafeFlutterResult.h
@@ -0,0 +1,51 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+
+/**
+ * Wrapper for FlutterResult that always delivers the result on the main thread.
+ */
+@interface FLTThreadSafeFlutterResult : NSObject
+
+/**
+ * Gets the original FlutterResult object wrapped by this FLTThreadSafeFlutterResult instance.
+ */
+@property(readonly, nonatomic, nonnull) FlutterResult flutterResult;
+
+/**
+ * Initializes with a FlutterResult object.
+ * @param result The FlutterResult object that the result will be given to.
+ */
+- (nonnull instancetype)initWithResult:(nonnull FlutterResult)result;
+
+/**
+ * Sends a successful result without any data.
+ */
+- (void)sendSuccess;
+
+/**
+ * Sends a successful result with data.
+ * @param data Result data that is send to the Flutter Dart side.
+ */
+- (void)sendSuccessWithData:(nonnull id)data;
+
+/**
+ * Sends an NSError as result
+ * @param error Error that will be send as FlutterError.
+ */
+- (void)sendError:(nonnull NSError*)error;
+
+/**
+ * Sends a FlutterError as result.
+ */
+- (void)sendErrorWithCode:(nonnull NSString*)code
+ message:(nullable NSString*)message
+ details:(nullable id)details;
+
+/**
+ * Sends FlutterMethodNotImplemented as result.
+ */
+- (void)sendNotImplemented;
+@end
diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeFlutterResult.m b/packages/camera/camera/ios/Classes/FLTThreadSafeFlutterResult.m
new file mode 100644
index 0000000..caa4788
--- /dev/null
+++ b/packages/camera/camera/ios/Classes/FLTThreadSafeFlutterResult.m
@@ -0,0 +1,58 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "FLTThreadSafeFlutterResult.h"
+#import <Foundation/Foundation.h>
+
+@implementation FLTThreadSafeFlutterResult {
+}
+
+- (id)initWithResult:(FlutterResult)result {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+ _flutterResult = result;
+ return self;
+}
+
+- (void)sendSuccess {
+ [self send:nil];
+}
+
+- (void)sendSuccessWithData:(id)data {
+ [self send:data];
+}
+
+- (void)sendError:(NSError*)error {
+ [self sendErrorWithCode:[NSString stringWithFormat:@"Error %d", (int)error.code]
+ message:error.localizedDescription
+ details:error.domain];
+}
+
+- (void)sendErrorWithCode:(NSString*)code
+ message:(NSString* _Nullable)message
+ details:(id _Nullable)details {
+ FlutterError* flutterError = [FlutterError errorWithCode:code message:message details:details];
+ [self send:flutterError];
+}
+
+- (void)sendNotImplemented {
+ [self send:FlutterMethodNotImplemented];
+}
+
+/**
+ * Sends result to flutterResult on the main thread.
+ */
+- (void)send:(id _Nullable)result {
+ if (!NSThread.isMainThread) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self->_flutterResult(result);
+ });
+ } else {
+ _flutterResult(result);
+ }
+}
+
+@end
diff --git a/packages/camera/camera/ios/Classes/camera-umbrella.h b/packages/camera/camera/ios/Classes/camera-umbrella.h
index 5c39401..b0fd493 100644
--- a/packages/camera/camera/ios/Classes/camera-umbrella.h
+++ b/packages/camera/camera/ios/Classes/camera-umbrella.h
@@ -4,6 +4,7 @@
#import <Foundation/Foundation.h>
#import <camera/CameraPlugin.h>
+#import <camera/FLTThreadSafeFlutterResult.h>
FOUNDATION_EXPORT double cameraVersionNumber;
FOUNDATION_EXPORT const unsigned char cameraVersionString[];
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index f68d026..6b4db7c 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -4,7 +4,7 @@
Dart.
repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
-version: 0.9.4+2
+version: 0.9.4+3
environment:
sdk: ">=2.14.0 <3.0.0"