[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"