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

[camera] Run iOS methods on UI thread by default #4140

Merged
merged 40 commits into from
Oct 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
dea50fc
Only run `initWithCameraName` on background thread.
renefloor Jul 7, 2021
da83b51
run camera start async
renefloor Jul 7, 2021
277d923
Start with migrating to ThreadSafeFlutterResult
renefloor Jul 8, 2021
9bd75bd
Migrated all results to thread safe class
renefloor Jul 9, 2021
a079af9
make data nonNullable
renefloor Jul 9, 2021
bd7d8eb
copyright
renefloor Jul 9, 2021
f0a2bb0
Merge remote-tracking branch 'flutter/master' into bugfix/camera-thre…
renefloor Jul 9, 2021
4125244
Added method channel tests
renefloor Jul 12, 2021
71e321e
Extend unit test
renefloor Jul 13, 2021
bdfde1a
replace dispatch loop with notifications
renefloor Jul 13, 2021
e6b848c
Made test much cleaner
renefloor Jul 13, 2021
d59a80d
add return for error
renefloor Jul 13, 2021
0f35095
changelog and pubspec
renefloor Jul 13, 2021
0b63dea
Add documentation
renefloor Jul 13, 2021
8bc557f
Merge branch 'master' into bugfix/camera-threading
renefloor Jul 13, 2021
b53ab3e
Make interface an extension
renefloor Jul 14, 2021
b6acf82
Increase timeout
renefloor Jul 14, 2021
9f31156
Merge remote-tracking branch 'flutter/master' into bugfix/camera-thre…
renefloor Aug 16, 2021
a4cf79e
update broken test
renefloor Aug 16, 2021
2b84db2
Documentation improvements
renefloor Aug 16, 2021
92c2d00
Make result methods verbs
renefloor Aug 16, 2021
2722cf4
small doc changes
renefloor Aug 16, 2021
4a1155a
remove notification center
renefloor Aug 17, 2021
04566ae
Added unit test for thread safe result
renefloor Aug 17, 2021
57c63ae
add extra comment
renefloor Aug 17, 2021
5c69728
Merge branch 'master' of https://github.com/flutter/plugins into bugf…
mvanbeusekom Oct 8, 2021
e2ba654
Revert removing handleMethodCallAsync.
mvanbeusekom Oct 8, 2021
17d1f5d
Fix unit tests
mvanbeusekom Oct 11, 2021
a0be148
Merge remote-tracking branch 'upstream/master' into bugfix/camera-thr…
mvanbeusekom Oct 11, 2021
ee453bc
Removed left over GIT merge tag
mvanbeusekom Oct 14, 2021
cd48ddd
Applied feedback on PR
mvanbeusekom Oct 14, 2021
98fe08a
Merge remote-tracking branch 'upstream/master' into bugfix/camera-thr…
mvanbeusekom Oct 14, 2021
a4e5262
Remove development team from project file
mvanbeusekom Oct 14, 2021
39d1438
Remove custom setting from XCScheme
mvanbeusekom Oct 14, 2021
3a12cbf
Clean up mock
mvanbeusekom Oct 14, 2021
8e8bdfc
Clean reference to CameraPlugin
mvanbeusekom Oct 14, 2021
3e86910
Apply feedback from PR.
mvanbeusekom Oct 18, 2021
9093887
Merge remote-tracking branch 'upstream/master' into bugfix/camera-thr…
mvanbeusekom Oct 18, 2021
1d51203
Apply review feedback.
mvanbeusekom Oct 18, 2021
f0b9f53
Merge remote-tracking branch 'upstream/master' into bugfix/camera-thr…
mvanbeusekom Oct 20, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
104 changes: 61 additions & 43 deletions packages/camera/camera/example/ios/Runner.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ @interface FLTCam : NSObject <FlutterTexture,

- (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
Expand Down Expand Up @@ -128,11 +128,11 @@ - (void)testSetFocusPointWithResult_SetsFocusPointOfInterest {
[_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)]);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
stuartmorgan marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -60,8 +66,7 @@ - (void)rotate:(UIDeviceOrientation)deviceOrientation
[methodCall.arguments isEqualToDictionary:@{@"orientation" : channelOrientation}];
}]]);

[self.cameraPlugin
orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
[cameraPlugin orientationChanged:[self createMockNotificationForOrientation:deviceOrientation]];
[self waitForExpectationsWithTimeout:30.0 handler:nil];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you still need this (and an expectation passed to the MockFLTThreadSafeFlutterResult initialization) so that the assertions are guaranteed to run after the result callback?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, the [CameraPlugin pausePreviewWithResult:] method that is tested has a very simple (synchronised) implementation and will not run its logic on a different queue (dispatching on different queue is done by the [CameraPlugin handleMethodCall] method).

Also the result object that is passed in is a simple mock implementation of the FLTThreadSafeFlutterResult class which simply echo's the value that is received by calling the [MockFLTThreadSafeFlutterResult sendSuccessWithData:] method through the MockFLTThreadSafeFlutterResult.receivedResult property.

So as far as I understand everything will run synchronously after each other.

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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See explanation above.

XCTAssertFalse(_camera.isPreviewPaused);
FLTCam *camera = [[FLTCam alloc] init];
MockFLTThreadSafeFlutterResult *resultObject = [[MockFLTThreadSafeFlutterResult alloc] init];

[camera resumePreviewWithResult:resultObject];
XCTAssertFalse(camera.isPreviewPaused);
}

@end
Original file line number Diff line number Diff line change
@@ -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 */
Original file line number Diff line number Diff line change
@@ -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
Loading