From f8d7ea08522f74fb7d3e2fde75fc20cb2aefd107 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 3 Jun 2021 10:33:23 +0200 Subject: [PATCH 1/3] Handle "create" method call in main thread on iOS. --- .../camera/camera/ios/Classes/CameraPlugin.m | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 1e818abda0ac..da4fdcf0fa73 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -1310,12 +1310,47 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result _dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL); } - // Invoke the plugin on another dispatch queue to avoid blocking the UI. + // Handle method calls on platform thread for those that require it. + if ([self handleMethodCallSync:call result:result]) { + return; + } + + // Otherwise invoke the plugin on another dispatch queue to avoid blocking the UI. dispatch_async(_dispatchQueue, ^{ [self handleMethodCallAsync:call result:result]; }); } +- (BOOL)handleMethodCallSync:(FlutterMethodCall *)call result:(FlutterResult)result { + if ([@"create" isEqualToString:call.method]) { + NSString *cameraName = call.arguments[@"cameraName"]; + NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; + NSNumber *enableAudio = call.arguments[@"enableAudio"]; + NSError *error; + FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName + resolutionPreset:resolutionPreset + enableAudio:[enableAudio boolValue] + orientation:[[UIDevice currentDevice] orientation] + dispatchQueue:_dispatchQueue + error:&error]; + if (error) { + result(getFlutterError(error)); + } else { + if (_camera) { + [_camera close]; + } + int64_t textureId = [_registry registerTexture:cam]; + _camera = cam; + + result(@{ + @"cameraId" : @(textureId), + }); + } + return true; + } + return false; +} + - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"availableCameras" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { @@ -1349,31 +1384,6 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re } else { result(FlutterMethodNotImplemented); } - } else if ([@"create" isEqualToString:call.method]) { - NSString *cameraName = call.arguments[@"cameraName"]; - NSString *resolutionPreset = call.arguments[@"resolutionPreset"]; - NSNumber *enableAudio = call.arguments[@"enableAudio"]; - NSError *error; - FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName - resolutionPreset:resolutionPreset - enableAudio:[enableAudio boolValue] - orientation:[[UIDevice currentDevice] orientation] - dispatchQueue:_dispatchQueue - error:&error]; - - if (error) { - result(getFlutterError(error)); - } else { - if (_camera) { - [_camera close]; - } - int64_t textureId = [_registry registerTexture:cam]; - _camera = cam; - - result(@{ - @"cameraId" : @(textureId), - }); - } } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; result(nil); From b6f6d9ece59525d224dc239b6511d0ea368889d3 Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Thu, 3 Jun 2021 10:36:58 +0200 Subject: [PATCH 2/3] Updated changelog and pubspec version --- packages/camera/camera/CHANGELOG.md | 4 ++++ packages/camera/camera/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 193feecbf920..d2d1e7f4a2b7 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.1+3 + +* Moved texture registration to the main thread on iOS to improve stability. + ## 0.8.1+2 * Fix iOS crash when selecting an unsupported FocusMode. diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index de286835a0ed..a7df9e0d51be 100644 --- a/packages/camera/camera/pubspec.yaml +++ b/packages/camera/camera/pubspec.yaml @@ -4,7 +4,7 @@ description: A Flutter plugin for getting information about and controlling the and streaming image buffers to 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.8.1+2 +version: 0.8.1+3 environment: sdk: ">=2.12.0 <3.0.0" From c336bcb2559f23e9ffe5421c6c7e0cf1e31af31d Mon Sep 17 00:00:00 2001 From: "Bodhi Mulders (BeMacized)" Date: Fri, 4 Jun 2021 14:18:52 +0200 Subject: [PATCH 3/3] [camera] Add native iOS unit tests --- .../ios/RunnerTests/CameraPluginTests.m | 108 ++++++++++++++++++ .../camera/camera/ios/Classes/CameraPlugin.m | 6 +- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 packages/camera/camera/example/ios/RunnerTests/CameraPluginTests.m diff --git a/packages/camera/camera/example/ios/RunnerTests/CameraPluginTests.m b/packages/camera/camera/example/ios/RunnerTests/CameraPluginTests.m new file mode 100644 index 000000000000..624a805dad12 --- /dev/null +++ b/packages/camera/camera/example/ios/RunnerTests/CameraPluginTests.m @@ -0,0 +1,108 @@ +// 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 + +@interface CameraPlugin () +- (instancetype)initWithRegistry:(NSObject *)registry + messenger:(NSObject *)messenger; +- (BOOL)handleMethodCallSync:(FlutterMethodCall *)call result:(FlutterResult)result; +- (BOOL)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result; +@end + +@interface CameraPluginTests : XCTestCase +@property(strong, nonatomic) id mockRegistrar; +@property(strong, nonatomic) id mockMessenger; +@property(strong, nonatomic) CameraPlugin *plugin; +@end + +@implementation CameraPluginTests + +- (void)setUp { + [super setUp]; + self.mockRegistrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); + self.mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub([self.mockRegistrar messenger]).andReturn(self.mockMessenger); + self.plugin = [[CameraPlugin alloc] initWithRegistry:self.mockRegistrar + messenger:self.mockMessenger]; +} + +- (void)testHandleMethodCallSync_ShouldHandleSyncMethods { + id methodCallMock = OCMClassMock([FlutterMethodCall class]); + OCMStub([methodCallMock method]).andReturn(@"create"); + + BOOL result = [[self plugin] handleMethodCallSync:methodCallMock + result:^(id _Nullable result){ + }]; + + XCTAssertTrue(result); +} + +- (void)testHandleMethodCallSync_ShouldNotHandleAsyncMethods { + id methodCallMock = OCMClassMock([FlutterMethodCall class]); + OCMStub([methodCallMock method]).andReturn(@"initialize"); + + BOOL result = [[self plugin] handleMethodCallSync:methodCallMock + result:^(id _Nullable result){ + }]; + + XCTAssertFalse(result); +} + +- (void)testHandleMethodCallAsync_ShouldHandleAsyncMethods { + id methodCallMock = OCMClassMock([FlutterMethodCall class]); + OCMStub([methodCallMock method]).andReturn(@"initialize"); + + BOOL result = [[self plugin] handleMethodCallAsync:methodCallMock + result:^(id _Nullable result){ + }]; + + XCTAssertTrue(result); +} + +- (void)testHandleMethodCallAsync_ShouldNotHandleSyncMethods { + id methodCallMock = OCMClassMock([FlutterMethodCall class]); + OCMStub([methodCallMock method]).andReturn(@"create"); + + BOOL result = [[self plugin] handleMethodCallAsync:methodCallMock + result:^(id _Nullable result){ + }]; + + XCTAssertFalse(result); +} + +- (void)testHandleMethodCall_ShouldNotCallAsyncHandlerForSyncMethod { + id methodCallMock = OCMClassMock([FlutterMethodCall class]); + OCMStub([methodCallMock method]).andReturn(@"create"); + id mockedPlugin = OCMPartialMock(self.plugin); + id result = ^(id _Nullable result) { + }; + OCMStub([mockedPlugin handleMethodCallSync:methodCallMock result:result]).andReturn(true); + OCMStub([mockedPlugin handleMethodCallAsync:methodCallMock result:result]).andReturn(false); + + [[self plugin] handleMethodCall:methodCallMock result:result]; + + OCMVerify([mockedPlugin handleMethodCallSync:methodCallMock result:result]); + OCMVerify(never(), [mockedPlugin handleMethodCallAsync:methodCallMock result:result]); +} + +- (void)testHandleMethodCall_ShouldCallAsyncHandlerForAsyncMethod { + id methodCallMock = OCMClassMock([FlutterMethodCall class]); + OCMStub([methodCallMock method]).andReturn(@"initialize"); + id mockedPlugin = OCMPartialMock(self.plugin); + id result = ^(id _Nullable result) { + }; + OCMStub([mockedPlugin handleMethodCallSync:methodCallMock result:result]).andReturn(false); + OCMStub([mockedPlugin handleMethodCallAsync:methodCallMock result:result]).andReturn(true); + + [[self plugin] handleMethodCall:methodCallMock result:result]; + + OCMVerify([mockedPlugin handleMethodCallSync:methodCallMock result:result]); + OCMVerify([mockedPlugin handleMethodCallAsync:methodCallMock result:result]); +} + +@end diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 2483f97fbf02..c2d6170ec961 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -1271,6 +1271,7 @@ - (instancetype)initWithRegistry:(NSObject *)registry _messenger = messenger; [self initDeviceEventMethodChannel]; [self startOrientationListener]; + return self; } @@ -1356,7 +1357,7 @@ - (BOOL)handleMethodCallSync:(FlutterMethodCall *)call result:(FlutterResult)res return false; } -- (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { +- (BOOL)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"availableCameras" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession @@ -1388,6 +1389,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(reply); } else { result(FlutterMethodNotImplemented); + return false; } } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; @@ -1493,8 +1495,10 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re [_camera setFocusPointWithResult:result x:x y:y]; } else { result(FlutterMethodNotImplemented); + return false; } } + return true; } @end