Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit b8aea6d

Browse files
authored
[camera] Run iOS methods on UI thread by default (#4140)
1 parent fece8dc commit b8aea6d

15 files changed

+560
-199
lines changed

packages/camera/camera/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.9.4+3
2+
3+
* Fix registerTexture and result being called on background thread on iOS.
4+
15
## 0.9.4+2
26

37
* Updated package description;

packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 61 additions & 43 deletions
Large diffs are not rendered by default.

packages/camera/camera/example/ios/RunnerTests/CameraFocusTests.m

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ @interface FLTCam : NSObject <FlutterTexture,
1919

2020
- (void)applyFocusMode;
2121
- (void)applyFocusMode:(FocusMode)focusMode onDevice:(AVCaptureDevice *)captureDevice;
22-
- (void)setFocusPointWithResult:(FlutterResult)result x:(double)x y:(double)y;
22+
- (void)setFocusPointWithResult:(FLTThreadSafeFlutterResult *)result x:(double)x y:(double)y;
2323
@end
2424

2525
@interface CameraFocusTests : XCTestCase
@@ -128,11 +128,11 @@ - (void)testSetFocusPointWithResult_SetsFocusPointOfInterest {
128128
[_camera setValue:_mockDevice forKey:@"captureDevice"];
129129

130130
// Run test
131-
[_camera
132-
setFocusPointWithResult:^void(id _Nullable result) {
133-
}
134-
x:1
135-
y:1];
131+
[_camera setFocusPointWithResult:[[FLTThreadSafeFlutterResult alloc]
132+
initWithResult:^(id _Nullable result){
133+
}]
134+
x:1
135+
y:1];
136136

137137
// Verify the focus point of interest has been set
138138
OCMVerify([_mockDevice setFocusPointOfInterest:CGPointMake(1, 1)]);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@import camera;
6+
@import camera.Test;
7+
@import XCTest;
8+
@import AVFoundation;
9+
#import <OCMock/OCMock.h>
10+
#import "MockFLTThreadSafeFlutterResult.h"
11+
12+
@interface CameraMethodChannelTests : XCTestCase
13+
@end
14+
15+
@implementation CameraMethodChannelTests
16+
17+
- (void)testCreate_ShouldCallResultOnMainThread {
18+
CameraPlugin *camera = [[CameraPlugin alloc] initWithRegistry:nil messenger:nil];
19+
20+
XCTestExpectation *expectation =
21+
[[XCTestExpectation alloc] initWithDescription:@"Result finished"];
22+
23+
// Set up mocks for initWithCameraName method
24+
id avCaptureDeviceInputMock = OCMClassMock([AVCaptureDeviceInput class]);
25+
OCMStub([avCaptureDeviceInputMock deviceInputWithDevice:[OCMArg any] error:[OCMArg anyObjectRef]])
26+
.andReturn([AVCaptureInput alloc]);
27+
28+
id avCaptureSessionMock = OCMClassMock([AVCaptureSession class]);
29+
OCMStub([avCaptureSessionMock alloc]).andReturn(avCaptureSessionMock);
30+
OCMStub([avCaptureSessionMock canSetSessionPreset:[OCMArg any]]).andReturn(YES);
31+
32+
MockFLTThreadSafeFlutterResult *resultObject =
33+
[[MockFLTThreadSafeFlutterResult alloc] initWithExpectation:expectation];
34+
35+
// Set up method call
36+
FlutterMethodCall *call = [FlutterMethodCall
37+
methodCallWithMethodName:@"create"
38+
arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}];
39+
40+
[camera handleMethodCallAsync:call result:resultObject];
41+
42+
// Verify the result
43+
NSDictionary *dictionaryResult = (NSDictionary *)resultObject.receivedResult;
44+
XCTAssertNotNil(dictionaryResult);
45+
XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]);
46+
}
47+
48+
@end

packages/camera/camera/example/ios/RunnerTests/CameraOrientationTests.m

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,52 @@
1010
#import <OCMock/OCMock.h>
1111

1212
@interface CameraOrientationTests : XCTestCase
13-
@property(strong, nonatomic) id mockMessenger;
14-
@property(strong, nonatomic) CameraPlugin *cameraPlugin;
1513
@end
1614

1715
@implementation CameraOrientationTests
1816

19-
- (void)setUp {
20-
[super setUp];
21-
22-
self.mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
23-
self.cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil messenger:self.mockMessenger];
24-
}
25-
2617
- (void)testOrientationNotifications {
27-
id mockMessenger = self.mockMessenger;
18+
id mockMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
19+
CameraPlugin *cameraPlugin = [[CameraPlugin alloc] initWithRegistry:nil messenger:mockMessenger];
20+
2821
[mockMessenger setExpectationOrderMatters:YES];
2922

30-
[self rotate:UIDeviceOrientationPortraitUpsideDown expectedChannelOrientation:@"portraitDown"];
31-
[self rotate:UIDeviceOrientationPortrait expectedChannelOrientation:@"portraitUp"];
32-
[self rotate:UIDeviceOrientationLandscapeRight expectedChannelOrientation:@"landscapeLeft"];
33-
[self rotate:UIDeviceOrientationLandscapeLeft expectedChannelOrientation:@"landscapeRight"];
23+
[self rotate:UIDeviceOrientationPortraitUpsideDown
24+
expectedChannelOrientation:@"portraitDown"
25+
cameraPlugin:cameraPlugin
26+
messenger:mockMessenger];
27+
[self rotate:UIDeviceOrientationPortrait
28+
expectedChannelOrientation:@"portraitUp"
29+
cameraPlugin:cameraPlugin
30+
messenger:mockMessenger];
31+
[self rotate:UIDeviceOrientationLandscapeRight
32+
expectedChannelOrientation:@"landscapeLeft"
33+
cameraPlugin:cameraPlugin
34+
messenger:mockMessenger];
35+
[self rotate:UIDeviceOrientationLandscapeLeft
36+
expectedChannelOrientation:@"landscapeRight"
37+
cameraPlugin:cameraPlugin
38+
messenger:mockMessenger];
3439

3540
OCMReject([mockMessenger sendOnChannel:[OCMArg any] message:[OCMArg any]]);
3641

3742
// No notification when flat.
38-
[self.cameraPlugin
43+
[cameraPlugin
3944
orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceUp]];
4045
// No notification when facedown.
41-
[self.cameraPlugin
46+
[cameraPlugin
4247
orientationChanged:[self createMockNotificationForOrientation:UIDeviceOrientationFaceDown]];
4348

4449
OCMVerifyAll(mockMessenger);
4550
}
4651

4752
- (void)rotate:(UIDeviceOrientation)deviceOrientation
48-
expectedChannelOrientation:(NSString *)channelOrientation {
49-
id mockMessenger = self.mockMessenger;
53+
expectedChannelOrientation:(NSString *)channelOrientation
54+
cameraPlugin:(CameraPlugin *)cameraPlugin
55+
messenger:(NSObject<FlutterBinaryMessenger> *)messenger {
5056
XCTestExpectation *orientationExpectation = [self expectationWithDescription:channelOrientation];
5157

52-
OCMExpect([mockMessenger
58+
OCMExpect([messenger
5359
sendOnChannel:[OCMArg any]
5460
message:[OCMArg checkWithBlock:^BOOL(NSData *data) {
5561
NSObject<FlutterMethodCodec> *codec = [FlutterStandardMethodCodec sharedInstance];
@@ -60,8 +66,7 @@ - (void)rotate:(UIDeviceOrientation)deviceOrientation
6066
[methodCall.arguments isEqualToDictionary:@{@"orientation" : channelOrientation}];
6167
}]]);
6268

63-
[self.cameraPlugin
64-
orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
69+
[cameraPlugin orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
6570
[self waitForExpectationsWithTimeout:30.0 handler:nil];
6671
}
6772

packages/camera/camera/example/ios/RunnerTests/CameraPreviewPauseTests.m

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,45 +6,37 @@
66
@import XCTest;
77
@import AVFoundation;
88
#import <OCMock/OCMock.h>
9+
#import "MockFLTThreadSafeFlutterResult.h"
910

1011
@interface FLTCam : NSObject <FlutterTexture,
1112
AVCaptureVideoDataOutputSampleBufferDelegate,
1213
AVCaptureAudioDataOutputSampleBufferDelegate>
1314
@property(assign, nonatomic) BOOL isPreviewPaused;
14-
- (void)pausePreviewWithResult:(FlutterResult)result;
15-
- (void)resumePreviewWithResult:(FlutterResult)result;
15+
16+
- (void)pausePreviewWithResult:(FLTThreadSafeFlutterResult *)result;
17+
18+
- (void)resumePreviewWithResult:(FLTThreadSafeFlutterResult *)result;
1619
@end
1720

1821
@interface CameraPreviewPauseTests : XCTestCase
19-
@property(readonly, nonatomic) FLTCam* camera;
2022
@end
2123

2224
@implementation CameraPreviewPauseTests
2325

24-
- (void)setUp {
25-
_camera = [[FLTCam alloc] init];
26-
}
27-
2826
- (void)testPausePreviewWithResult_shouldPausePreview {
29-
XCTestExpectation* resultExpectation =
30-
[self expectationWithDescription:@"Succeeding result with nil value"];
31-
[_camera pausePreviewWithResult:^void(id _Nullable result) {
32-
XCTAssertNil(result);
33-
[resultExpectation fulfill];
34-
}];
35-
[self waitForExpectationsWithTimeout:2.0 handler:nil];
36-
XCTAssertTrue(_camera.isPreviewPaused);
27+
FLTCam *camera = [[FLTCam alloc] init];
28+
MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init];
29+
30+
[camera pausePreviewWithResult:resultObject];
31+
XCTAssertTrue(camera.isPreviewPaused);
3732
}
3833

3934
- (void)testResumePreviewWithResult_shouldResumePreview {
40-
XCTestExpectation* resultExpectation =
41-
[self expectationWithDescription:@"Succeeding result with nil value"];
42-
[_camera resumePreviewWithResult:^void(id _Nullable result) {
43-
XCTAssertNil(result);
44-
[resultExpectation fulfill];
45-
}];
46-
[self waitForExpectationsWithTimeout:2.0 handler:nil];
47-
XCTAssertFalse(_camera.isPreviewPaused);
35+
FLTCam *camera = [[FLTCam alloc] init];
36+
MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init];
37+
38+
[camera resumePreviewWithResult:resultObject];
39+
XCTAssertFalse(camera.isPreviewPaused);
4840
}
4941

5042
@end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef MockFLTThreadSafeFlutterResult_h
6+
#define MockFLTThreadSafeFlutterResult_h
7+
8+
/**
9+
* Extends FLTThreadSafeFlutterResult to give tests the ability to wait on the result and
10+
* read the received result.
11+
*/
12+
@interface MockFLTThreadSafeFlutterResult : FLTThreadSafeFlutterResult
13+
@property(readonly, nonatomic, nonnull) XCTestExpectation *expectation;
14+
@property(nonatomic, nullable) id receivedResult;
15+
16+
/**
17+
* Initializes the MockFLTThreadSafeFlutterResult with an expectation.
18+
*
19+
* The expectation is fullfilled when a result is called allowing tests to await the result in an
20+
* asynchronous manner.
21+
*/
22+
- (nonnull instancetype)initWithExpectation:(nonnull XCTestExpectation *)expectation;
23+
@end
24+
25+
#endif /* MockFLTThreadSafeFlutterResult_h */
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@import camera;
6+
@import XCTest;
7+
8+
#import "MockFLTThreadSafeFlutterResult.h"
9+
10+
@implementation MockFLTThreadSafeFlutterResult
11+
12+
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation {
13+
self = [super init];
14+
_expectation = expectation;
15+
return self;
16+
}
17+
18+
- (void)sendSuccessWithData:(id)data {
19+
self.receivedResult = data;
20+
[self.expectation fulfill];
21+
}
22+
23+
- (void)sendSuccess {
24+
self.receivedResult = nil;
25+
[self.expectation fulfill];
26+
}
27+
@end

0 commit comments

Comments
 (0)