diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md index 2cab8e123ae6..eb15f4804e44 100644 --- a/packages/camera/camera/CHANGELOG.md +++ b/packages/camera/camera/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.1+4 + +* Moved texture registration to the main thread on iOS to improve stability. + ## 0.8.1+3 * Do not change camera orientation when iOS device is flat. 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 ebd5366ba78d..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; } @@ -1315,13 +1316,48 @@ - (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]; }); } -- (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)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; +} + +- (BOOL)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)result { if ([@"availableCameras" isEqualToString:call.method]) { if (@available(iOS 10.0, *)) { AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession @@ -1353,31 +1389,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re result(reply); } 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), - }); + return false; } } else if ([@"startImageStream" isEqualToString:call.method]) { [_camera startImageStreamWithMessenger:_messenger]; @@ -1483,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 diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml index a7df9e0d51be..789910e2c79b 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+3 +version: 0.8.1+4 environment: sdk: ">=2.12.0 <3.0.0"