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 15 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.8.1+5

* Fix registerTexture and result being called on background thread on iOS.

## 0.8.1+4

* Silenced warnings that may occur during build when using a very
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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 */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */; };
Expand Down Expand Up @@ -44,6 +45,7 @@
/* 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>"; };
Expand Down Expand Up @@ -96,6 +98,7 @@
03BB766A2665316900CE5A93 /* CameraFocusTests.m */,
03BB767226653ABE00CE5A93 /* CameraOrientationTests.m */,
03BB766C2665316900CE5A93 /* Info.plist */,
033B94BD269C40A200B4DF97 /* CameraMethodChannelTests.m */,
);
path = RunnerTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -358,6 +361,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
033B94BE269C40A200B4DF97 /* CameraMethodChannelTests.m in Sources */,
03BB766B2665316900CE5A93 /* CameraFocusTests.m in Sources */,
334733EA2668111C00DCC49E /* CameraOrientationTests.m in Sources */,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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 AVFoundation;
#import <OCMock/OCMock.h>

@interface FLTThreadSafeFlutterResult : NSObject
renefloor marked this conversation as resolved.
Show resolved Hide resolved
@property(readonly, nonatomic) FlutterResult flutterResult;
@end

@interface MockFLTThreadSafeFlutterResult : FLTThreadSafeFlutterResult
stuartmorgan marked this conversation as resolved.
Show resolved Hide resolved
@property(readonly, nonatomic) NSNotificationCenter *notificationCenter;
@property(nonatomic, nullable) id receivedResult;
@end

@implementation MockFLTThreadSafeFlutterResult
/**
Initialize with a notification center.
*/
- (id)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter {
self = [super init];
_notificationCenter = notificationCenter;
renefloor marked this conversation as resolved.
Show resolved Hide resolved
return self;
}

/**
Called when result is successful. Sends "successWithData" to the notification center.
*/
- (void)successWithData:(id)data {
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't there be a thread assertion in this code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean with the thread assertion? That we verify that it is not the main thread?

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 remember what I meant by having that comment in this location unfortunately; maybe I was forgetting the flow and thinking this was after the bounce back to the main thread.

However, the more general questions is still valid: the goal of this PR is to ensure that methods are getting callbacks on the main thread, so why isn't there any code in the test that calls into the camera handler with a result object controlled by the test, and actually asserts that the callback was on the main thread? Like this: https://github.com/flutter/plugins/blob/master/packages/local_auth/example/ios/RunnerTests/FLTLocalAuthPluginTests.m#L62-L64

It is not clear to me what in this test would deterministically fail without the fix from this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stuartmorgan I have now added ThreadSafeFlutterResultTests that test that the result is always called on the main thread. These tests in CameraMethodChannelTests just verify that ThreadSafeFlutterResult is used, not how that is working.

_receivedResult = data;
[self->_notificationCenter postNotificationName:@"successWithData" object:nil];
}
@end

@interface CameraPlugin (Test)
- (void)handleMethodCallWithThreadSafeResult:(FlutterMethodCall *)call
result:(FLTThreadSafeFlutterResult *)result;
@end

@interface CameraMethodChannelTests : XCTestCase
@property(readonly, nonatomic) CameraPlugin *camera;
@property(readonly, nonatomic) MockFLTThreadSafeFlutterResult *resultObject;
Copy link
Contributor

Choose a reason for hiding this comment

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

Per the comment in the other file: please remove these and make them local to the test. (Having the object under test be part of the fixture state is always a major red flag to me unless there's a very compelling reason.)

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

@property(readonly, nonatomic) NSNotificationCenter *notificationCenter;
@end

@implementation CameraMethodChannelTests

- (void)setUp {
_camera = [[CameraPlugin alloc] init];
_notificationCenter = [[NSNotificationCenter alloc] init];

// Setup 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);

_resultObject =
[[MockFLTThreadSafeFlutterResult alloc] initWithNotificationCenter:_notificationCenter];
}

- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the
// class.
}
renefloor marked this conversation as resolved.
Show resolved Hide resolved

- (void)testCreate_ShouldCallResultOnMainThread {
// Setup method call
renefloor marked this conversation as resolved.
Show resolved Hide resolved
XCTNSNotificationExpectation *notificationExpectation =
[[XCTNSNotificationExpectation alloc] initWithName:@"successWithData"
object:nil
notificationCenter:_notificationCenter];

FlutterMethodCall *call = [FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"resolutionPreset" : @"medium", @"enableAudio" : @(1)}];

// Call handleMethodCall
renefloor marked this conversation as resolved.
Show resolved Hide resolved
[_camera handleMethodCallWithThreadSafeResult:call result:_resultObject];

// Don't expect a result yet
XCTAssertNil(_resultObject.receivedResult);
renefloor marked this conversation as resolved.
Show resolved Hide resolved

[self waitForExpectations:[NSArray arrayWithObject:notificationExpectation] timeout:0.1];

// Expect a result after waiting for thread to switch
XCTAssertNotNil(_resultObject.receivedResult);

// Verify the result
NSDictionary *dictionaryResult = (NSDictionary *)_resultObject.receivedResult;
XCTAssertNotNil(dictionaryResult);
XCTAssert([[dictionaryResult allKeys] containsObject:@"cameraId"]);
}

@end
Loading