Commit 70ff50f9 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Integration test for channel communication (#9621)

parent 0770c3c1
a5b64899c9392183b4b5df7e89fa8f0950a8e509 3211d2fca262ba348a0bbcecefeb3e1b0b832faf
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async { Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android; deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createPlatformServiceDriverTest()); await task(createChannelsIntegrationTest());
} }
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
import 'package:flutter_devicelab/framework/adb.dart'; import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart'; import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async { Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios; deviceOperatingSystem = DeviceOperatingSystem.ios;
await task(createPlatformServiceDriverTest()); await task(createChannelsIntegrationTest());
} }
// Copyright 2017 The Chromium 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 'dart:async';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createChannelsIntegrationTest());
}
// Copyright 2017 The Chromium 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 'dart:async';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createPlatformChannelSampleTest());
}
// Copyright 2017 The Chromium 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 'dart:async';
import 'package:flutter_devicelab/framework/adb.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/integration_tests.dart';
Future<Null> main() async {
deviceOperatingSystem = DeviceOperatingSystem.ios;
await task(createPlatformChannelSampleTest());
}
// Copyright (c) 2017 The Chromium 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 'dart:async';
import '../framework/adb.dart';
import '../framework/framework.dart';
import '../framework/ios.dart';
import '../framework/utils.dart';
TaskFunction createChannelsIntegrationTest() {
return new DriverTest(
'${flutterDirectory.path}/dev/integration_tests/channels',
'lib/main.dart',
);
}
TaskFunction createPlatformChannelSampleTest() {
return new DriverTest(
'${flutterDirectory.path}/examples/platform_channel',
'test_driver/button_tap.dart',
);
}
class DriverTest {
DriverTest(this.testDirectory, this.testTarget);
final String testDirectory;
final String testTarget;
Future<TaskResult> call() {
return inDirectory(testDirectory, () async {
final Device device = await devices.workingDevice;
await device.unlock();
final String deviceId = device.deviceId;
await flutter('packages', options: <String>['get']);
if (deviceOperatingSystem == DeviceOperatingSystem.ios) {
await prepareProvisioningCertificates(testDirectory);
// This causes an Xcode project to be created.
await flutter('build', options: <String>['ios', '--profile']);
}
await flutter('drive', options: <String>[
'-v',
'-t',
testTarget,
'-d',
deviceId,
]);
return new TaskResult.success(null);
});
}
}
...@@ -10,14 +10,6 @@ import '../framework/framework.dart'; ...@@ -10,14 +10,6 @@ import '../framework/framework.dart';
import '../framework/ios.dart'; import '../framework/ios.dart';
import '../framework/utils.dart'; import '../framework/utils.dart';
TaskFunction createPlatformServiceDriverTest() {
return new DriverTest(
'${flutterDirectory.path}/examples/platform_channel',
'test_driver/button_tap.dart',
);
}
TaskFunction createComplexLayoutScrollPerfTest() { TaskFunction createComplexLayoutScrollPerfTest() {
return new PerfTest( return new PerfTest(
'${flutterDirectory.path}/dev/benchmarks/complex_layout', '${flutterDirectory.path}/dev/benchmarks/complex_layout',
...@@ -175,40 +167,6 @@ class PerfTest { ...@@ -175,40 +167,6 @@ class PerfTest {
} }
} }
class DriverTest {
DriverTest(this.testDirectory, this.testTarget);
final String testDirectory;
final String testTarget;
Future<TaskResult> call() {
return inDirectory(testDirectory, () async {
final Device device = await devices.workingDevice;
await device.unlock();
final String deviceId = device.deviceId;
await flutter('packages', options: <String>['get']);
if (deviceOperatingSystem == DeviceOperatingSystem.ios) {
await prepareProvisioningCertificates(testDirectory);
// This causes an Xcode project to be created.
await flutter('build', options: <String>['ios', '--profile']);
}
await flutter('drive', options: <String>[
'-v',
'-t',
testTarget,
'-d',
deviceId,
]);
return new TaskResult.success(null);
});
}
}
class BuildTest { class BuildTest {
BuildTest(this.testDirectory); BuildTest(this.testDirectory);
......
...@@ -83,7 +83,13 @@ tasks: ...@@ -83,7 +83,13 @@ tasks:
required_agent_capabilities: ["has-android-device"] required_agent_capabilities: ["has-android-device"]
flaky: true flaky: true
platform_channel_test: channels_integration_test:
description: >
Checks that platform channels work on Android.
stage: devicelab
required_agent_capabilities: ["has-android-device"]
platform_channel_sample_test:
description: > description: >
Runs a driver test on the Platform Channel sample app on Android. Runs a driver test on the Platform Channel sample app on Android.
stage: devicelab stage: devicelab
...@@ -154,7 +160,13 @@ tasks: ...@@ -154,7 +160,13 @@ tasks:
# iOS on-device tests # iOS on-device tests
platform_channel_test_ios: channels_integration_test_ios:
description: >
Checks that platform channels work on iOS.
stage: devicelab_ios
required_agent_capabilities: ["has-ios-device"]
platform_channel_sample_test_ios:
description: > description: >
Runs a driver test on the Platform Channel sample app on iOS. Runs a driver test on the Platform Channel sample app on iOS.
stage: devicelab_ios stage: devicelab_ios
...@@ -207,6 +219,13 @@ tasks: ...@@ -207,6 +219,13 @@ tasks:
# Tests running on Windows host # Tests running on Windows host
channels_integration_test_win:
description: >
Checks that platform channels work when app is launched from Windows.
stage: devicelab_win
required_agent_capabilities: ["windows"]
flaky: true
flutter_gallery_win__build: flutter_gallery_win__build:
description: > description: >
Collects various performance metrics from AOT builds of the Flutter Collects various performance metrics from AOT builds of the Flutter
......
Automated Flutter integration test suites. Each suite consists of a complete
Flutter app and a `flutter_driver` specification that drives tests from the UI.
Intended for use with devicelab.
.DS_Store
.atom/
.idea
.packages
.pub/
build/
ios/.generated/
packages
pubspec.lock
.flutter-plugins
# channels
Integration test of platform channels.
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
/gradle
/gradlew
/gradlew.bat
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withInputStream { stream ->
localProperties.load(stream)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
androidTestCompile 'com.android.support:support-annotations:25.0.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yourcompany.channels"
android:versionCode="1"
android:versionName="0.0.1">
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="21" />
<!-- The INTERNET permission is required for development. Specifically,
flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application android:name="io.flutter.app.FlutterApplication" android:label="channels" android:icon="@mipmap/ic_launcher">
<activity android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package com.yourcompany.channels;
import java.nio.ByteBuffer;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.*;
import io.flutter.plugins.PluginRegistry;
public class MainActivity extends FlutterActivity {
PluginRegistry pluginRegistry;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pluginRegistry = new PluginRegistry();
pluginRegistry.registerAll(this);
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "json-msg", JSONMessageCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "std-msg", StandardMessageCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "json-method", JSONMethodCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", StandardMethodCodec.INSTANCE));
}
private <T> void setupMessageHandshake(final BasicMessageChannel<T> channel) {
// On message receipt, do a send/reply/send round-trip in the other direction,
// then reply to the first message.
channel.setMessageHandler(new BasicMessageChannel.MessageHandler<T>() {
@Override
public void onMessage(final T message, final BasicMessageChannel.Reply<T> reply) {
final T messageEcho = echo(message);
channel.send(messageEcho, new BasicMessageChannel.Reply<T>() {
@Override
public void reply(T replyMessage) {
channel.send(echo(replyMessage));
reply.reply(messageEcho);
}
});
}
});
}
// Outgoing ByteBuffer messages must be direct-allocated and payload placed between
// positon 0 and current position.
@SuppressWarnings("unchecked")
private <T> T echo(T message) {
if (message instanceof ByteBuffer) {
final ByteBuffer buffer = (ByteBuffer) message;
final ByteBuffer echo = ByteBuffer.allocateDirect(buffer.remaining());
echo.put(buffer);
return (T) echo;
}
return message;
}
private void setupMethodHandshake(final MethodChannel channel) {
channel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(final MethodCall methodCall, final MethodChannel.Result result) {
switch (methodCall.method) {
case "success":
doSuccessHandshake(channel, methodCall, result);
break;
case "error":
doErrorHandshake(channel, methodCall, result);
break;
default:
doNotImplementedHandshake(channel, methodCall, result);
break;
}
}
});
}
private void doSuccessHandshake(final MethodChannel channel, final MethodCall methodCall, final MethodChannel.Result result) {
channel.invokeMethod(methodCall.method, methodCall.arguments, new MethodChannel.Result() {
@Override
public void success(Object o) {
channel.invokeMethod(methodCall.method, o);
result.success(methodCall.arguments);
}
@Override
public void error(String code, String message, Object details) {
throw new AssertionError("Should not be called");
}
@Override
public void notImplemented() {
throw new AssertionError("Should not be called");
}
});
}
private void doErrorHandshake(final MethodChannel channel, final MethodCall methodCall, final MethodChannel.Result result) {
channel.invokeMethod(methodCall.method, methodCall.arguments, new MethodChannel.Result() {
@Override
public void success(Object o) {
throw new AssertionError("Should not be called");
}
@Override
public void error(String code, String message, Object details) {
channel.invokeMethod(methodCall.method, details);
result.error(code, message, methodCall.arguments);
}
@Override
public void notImplemented() {
throw new AssertionError("Should not be called");
}
});
}
private void doNotImplementedHandshake(final MethodChannel channel, final MethodCall methodCall, final MethodChannel.Result result) {
channel.invokeMethod(methodCall.method, methodCall.arguments, new MethodChannel.Result() {
@Override
public void success(Object o) {
throw new AssertionError("Should not be called");
}
@Override
public void error(String code, String message, Object details) {
throw new AssertionError("Should not be called");
}
@Override
public void notImplemented() {
channel.invokeMethod(methodCall.method, null);
result.notImplemented();
}
});
}
}
package io.flutter.plugins;
import io.flutter.app.FlutterActivity;
/**
* Generated file. Do not edit.
*/
public class PluginRegistry {
public void registerAll(FlutterActivity activity) {
}
}
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
task wrapper(type: Wrapper) {
gradleVersion = '2.14.1'
}
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withInputStream { stream -> plugins.load(stream) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
.idea/
.vagrant/
.sconsign.dblite
.svn/
.DS_Store
*.swp
profile
DerivedData/
build/
*.pbxuser
*.mode1v3
*.mode2v3
*.perspectivev3
!default.pbxuser
!default.mode1v3
!default.mode2v3
!default.perspectivev3
xcuserdata
*.moved-aside
*.pyc
*sync/
Icon?
.tags*
/Flutter/app.flx
/Flutter/app.zip
/Flutter/App.framework
/Flutter/Flutter.framework
/Flutter/Generated.xcconfig
/ServiceDefinitions.json
Pods/
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "TestConfig.xcconfig"
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "TestConfig.xcconfig"
ProvisioningStyle=Manual
CODE_SIGN_IDENTITY=iPhone Developer
PROVISIONING_PROFILE=Xcode Managed Profile
DEVELOPMENT_TEAM=...
PROVISIONING_PROFILE_SPECIFIER=...
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
if ENV['FLUTTER_FRAMEWORK_DIR'] == nil
abort('Please set FLUTTER_FRAMEWORK_DIR to the directory containing Flutter.framework')
end
target 'Runner' do
use_frameworks!
# Pods for Runner
# Flutter Pods
pod 'Flutter', :path => ENV['FLUTTER_FRAMEWORK_DIR']
if File.exists? '../.flutter-plugins'
flutter_root = File.expand_path('..')
File.foreach('../.flutter-plugins') { |line|
plugin = line.split(pattern='=')
if plugin.length == 2
name = plugin[0].strip()
path = plugin[1].strip()
resolved_path = File.expand_path("#{path}/ios", flutter_root)
pod name, :path => resolved_path
else
puts "Invalid plugin specification: #{line}"
end
}
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
PODS:
- Flutter (1.0.0)
DEPENDENCIES:
- Flutter (from `/Users/mravn/github/engine/src/out/ios_debug_unopt`)
EXTERNAL SOURCES:
Flutter:
:path: "/Users/mravn/github/engine/src/out/ios_debug_unopt"
SPEC CHECKSUMS:
Flutter: d674e78c937094a75ac71dd77e921e840bea3dbf
PODFILE CHECKSUM: cc70c01bca487bebd110b87397f017f3b76a89f1
COCOAPODS: 1.2.1
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
// Copyright 2017 The Chromium 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 <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
@interface AppDelegate : FlutterAppDelegate
@end
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "AppDelegate.h"
#include "PluginRegistry.h"
@implementation AppDelegate {
PluginRegistry *plugins;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController;
plugins = [[PluginRegistry alloc] initWithController:flutterController];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"binary-msg"
binaryMessenger:flutterController
codec:[FlutterBinaryCodec sharedInstance]]];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"string-msg"
binaryMessenger:flutterController
codec:[FlutterStringCodec sharedInstance]]];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"json-msg"
binaryMessenger:flutterController
codec:[FlutterJSONMessageCodec sharedInstance]]];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"std-msg"
binaryMessenger:flutterController
codec:[FlutterStandardMessageCodec sharedInstance]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"json-method"
binaryMessenger:flutterController
codec:[FlutterJSONMethodCodec sharedInstance]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec sharedInstance]]];
return YES;
}
- (void)setupMessagingHandshakeOnChannel:(FlutterBasicMessageChannel*)channel {
[channel setMessageHandler:^(id message, FlutterReply reply) {
[channel sendMessage:message reply:^(id messageReply) {
[channel sendMessage:messageReply];
reply(message);
}];
}];
}
- (void)setupMethodCallSuccessHandshakeOnChannel:(FlutterMethodChannel*)channel {
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
if ([call.method isEqual:@"success"]) {
[channel invokeMethod:call.method arguments:call.arguments result:^(id value) {
[channel invokeMethod:call.method arguments:value];
result(call.arguments);
}];
} else if ([call.method isEqual:@"error"]) {
[channel invokeMethod:call.method arguments:call.arguments result:^(id value) {
FlutterError* error = (FlutterError*) value;
[channel invokeMethod:call.method arguments:error.details];
result(error);
}];
} else {
[channel invokeMethod:call.method arguments:call.arguments result:^(id value) {
NSAssert(value == FlutterMethodNotImplemented, @"Result must be not implemented");
[channel invokeMethod:call.method arguments:nil];
result(FlutterMethodNotImplemented);
}];
}
}];
}
- (void)applicationWillResignActive:(UIApplication *)application {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
- (void)applicationWillTerminate:(UIApplication *)application {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
@end
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>channels</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
//
// Generated file. Do not edit.
//
#ifndef PluginRegistry_h
#define PluginRegistry_h
#import <Flutter/Flutter.h>
@interface PluginRegistry : NSObject
- (instancetype)initWithController:(FlutterViewController *)controller;
@end
#endif /* PluginRegistry_h */
//
// Generated file. Do not edit.
//
#import "PluginRegistry.h"
@implementation PluginRegistry
- (instancetype)initWithController:(FlutterViewController *)controller {
if (self = [super init]) {
}
return self;
}
@end
// Copyright 2017 The Chromium 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 <UIKit/UIKit.h>
#import <Flutter/Flutter.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([AppDelegate class]));
}
}
// Copyright 2017 The Chromium 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 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_driver/driver_extension.dart';
import 'src/basic_messaging.dart';
import 'src/method_calls.dart';
import 'src/test_step.dart';
void main() {
enableFlutterDriverExtension();
runApp(new TestApp());
}
class TestApp extends StatefulWidget {
@override
_TestAppState createState() => new _TestAppState();
}
class _TestAppState extends State<TestApp> {
static final List<dynamic> aList = <dynamic>[
false,
0,
0.0,
'hello',
<dynamic>[
<String, dynamic>{'key': 42}
],
];
static final Map<String, dynamic> aMap = <String, dynamic>{
'a': false,
'b': 0,
'c': 0.0,
'd': 'hello',
'e': <dynamic>[
<String, dynamic>{'key': 42}
]
};
static final Uint8List someUint8s = new Uint8List.fromList(<int>[
0xBA,
0x5E,
0xBA,
0x11,
]);
static final Int32List someInt32s = new Int32List.fromList(<int>[
-0x7fffffff - 1,
0,
0x7fffffff,
]);
static final Int64List someInt64s = new Int64List.fromList(<int>[
-0x7fffffffffffffff - 1,
0,
0x7fffffffffffffff,
]);
static final Float64List someFloat64s =
new Float64List.fromList(<double>[
double.NAN,
double.NEGATIVE_INFINITY,
-double.MAX_FINITE,
-double.MIN_POSITIVE,
-0.0,
0.0,
double.MIN_POSITIVE,
double.MAX_FINITE,
double.INFINITY,
]);
static final List<TestStep> steps = <TestStep>[
() => methodCallJsonSuccessHandshake(null),
() => methodCallJsonSuccessHandshake(true),
() => methodCallJsonSuccessHandshake(7),
() => methodCallJsonSuccessHandshake('world'),
() => methodCallJsonSuccessHandshake(aList),
() => methodCallJsonSuccessHandshake(aMap),
() => methodCallJsonNotImplementedHandshake(),
() => methodCallStandardSuccessHandshake(null),
() => methodCallStandardSuccessHandshake(true),
() => methodCallStandardSuccessHandshake(7),
() => methodCallStandardSuccessHandshake('world'),
() => methodCallStandardSuccessHandshake(aList),
() => methodCallStandardSuccessHandshake(aMap),
() => methodCallJsonErrorHandshake(null),
() => methodCallJsonErrorHandshake('world'),
() => methodCallStandardErrorHandshake(null),
() => methodCallStandardErrorHandshake('world'),
() => methodCallStandardNotImplementedHandshake(),
() => basicBinaryHandshake(null),
() => basicBinaryHandshake(new ByteData(0)),
() => basicBinaryHandshake(new ByteData(4)..setUint32(0, 0x12345678)),
() => basicStringHandshake('hello, world'),
() => basicStringHandshake('hello \u263A \u{1f602} unicode'),
() => basicStringHandshake(''),
() => basicStringHandshake(null),
() => basicJsonHandshake(null),
() => basicJsonHandshake(true),
() => basicJsonHandshake(false),
() => basicJsonHandshake(0),
() => basicJsonHandshake(-7),
() => basicJsonHandshake(7),
() => basicJsonHandshake(1 << 32),
() => basicJsonHandshake(1 << 56),
() => basicJsonHandshake(0.0),
() => basicJsonHandshake(-7.0),
() => basicJsonHandshake(7.0),
() => basicJsonHandshake(''),
() => basicJsonHandshake('hello, world'),
() => basicJsonHandshake('hello, "world"'),
() => basicJsonHandshake('hello \u263A \u{1f602} unicode'),
() => basicJsonHandshake(<dynamic>[]),
() => basicJsonHandshake(aList),
() => basicJsonHandshake(<String, dynamic>{}),
() => basicJsonHandshake(aMap),
() => basicStandardHandshake(null),
() => basicStandardHandshake(true),
() => basicStandardHandshake(false),
() => basicStandardHandshake(0),
() => basicStandardHandshake(-7),
() => basicStandardHandshake(7),
() => basicStandardHandshake(1 << 32),
() => basicStandardHandshake(1 << 64),
() => basicStandardHandshake(1 << 128),
() => basicStandardHandshake(0.0),
() => basicStandardHandshake(-7.0),
() => basicStandardHandshake(7.0),
() => basicStandardHandshake(''),
() => basicStandardHandshake('hello, world'),
() => basicStandardHandshake('hello \u263A \u{1f602} unicode'),
() => basicStandardHandshake(someUint8s),
() => basicStandardHandshake(someInt32s),
() => basicStandardHandshake(someInt64s),
() => basicStandardHandshake(someFloat64s),
() => basicStandardHandshake(<dynamic>[]),
() => basicStandardHandshake(aList),
() => basicStandardHandshake(<String, dynamic>{}),
() => basicStandardHandshake(<dynamic, dynamic>{7: true, false: -7}),
() => basicStandardHandshake(aMap),
() => basicBinaryMessageToUnknownChannel(),
() => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(),
() => basicStandardMessageToUnknownChannel(),
];
Future<TestStepResult> _result;
int _step = 0;
@override
void initState() {
super.initState();
}
void _executeNextStep() {
setState(() {
if (_step < steps.length)
_result = steps[_step++]();
else
_result = new Future<TestStepResult>.value(TestStepResult.complete);
});
}
Widget _buildTestResultWidget(
BuildContext context,
AsyncSnapshot<TestStepResult> snapshot,
) {
return new TestStepResult.fromSnapshot(snapshot).asWidget(context);
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Channels Test',
home: new Scaffold(
appBar: new AppBar(
title: const Text('Channels Test'),
),
body: new Padding(
padding: const EdgeInsets.all(20.0),
child: new FutureBuilder<TestStepResult>(
future: _result,
builder: _buildTestResultWidget,
),
),
floatingActionButton: new FloatingActionButton(
key: const ValueKey<String>('step'),
onPressed: _executeNextStep,
child: new Icon(Icons.navigate_next),
),
),
);
}
}
// Copyright 2017 The Chromium 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 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'test_step.dart';
Future<TestStepResult> basicBinaryHandshake(ByteData message) async {
const BasicMessageChannel<ByteData> channel =
const BasicMessageChannel<ByteData>(
'binary-msg',
const BinaryCodec(),
);
return _basicMessageHandshake<ByteData>(
'Binary >${toString(message)}<', channel, message);
}
Future<TestStepResult> basicStringHandshake(String message) async {
const BasicMessageChannel<String> channel = const BasicMessageChannel<String>(
'string-msg',
const StringCodec(),
);
return _basicMessageHandshake<String>('String >$message<', channel, message);
}
Future<TestStepResult> basicJsonHandshake(dynamic message) async {
const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>(
'json-msg',
const JSONMessageCodec(),
);
return _basicMessageHandshake<dynamic>('JSON >$message<', channel, message);
}
Future<TestStepResult> basicStandardHandshake(dynamic message) async {
const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>(
'std-msg',
const StandardMessageCodec(),
);
return _basicMessageHandshake<dynamic>(
'Standard >${toString(message)}<', channel, message);
}
Future<TestStepResult> basicBinaryMessageToUnknownChannel() async {
const BasicMessageChannel<ByteData> channel =
const BasicMessageChannel<ByteData>(
'binary-unknown',
const BinaryCodec(),
);
return _basicMessageToUnknownChannel<ByteData>('Binary', channel);
}
Future<TestStepResult> basicStringMessageToUnknownChannel() async {
const BasicMessageChannel<String> channel = const BasicMessageChannel<String>(
'string-unknown',
const StringCodec(),
);
return _basicMessageToUnknownChannel<String>('String', channel);
}
Future<TestStepResult> basicJsonMessageToUnknownChannel() async {
const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>(
'json-unknown',
const JSONMessageCodec(),
);
return _basicMessageToUnknownChannel<dynamic>('JSON', channel);
}
Future<TestStepResult> basicStandardMessageToUnknownChannel() async {
const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>(
'std-unknown',
const StandardMessageCodec(),
);
return _basicMessageToUnknownChannel<dynamic>('Standard', channel);
}
/// Sends the specified message to the platform, doing a
/// receive message/send reply/receive reply echo handshake initiated by the
/// platform, then expecting a reply echo to the original message.
///
/// Fails, if an error occurs, or if any message seen is not deeply equal to
/// the original message.
Future<TestStepResult> _basicMessageHandshake<T>(
String description,
BasicMessageChannel<T> channel,
T message,
) async {
final List<dynamic> received = <dynamic>[];
channel.setMessageHandler((T message) async {
received.add(message);
return message;
});
dynamic messageEcho = nothing;
dynamic error = nothing;
try {
messageEcho = await channel.send(message);
} catch (e) {
error = e;
}
return resultOfHandshake(
'Basic message handshake',
description,
message,
received,
messageEcho,
error,
);
}
/// Sends a message on a channel that no one listens on.
Future<TestStepResult> _basicMessageToUnknownChannel<T>(
String description,
BasicMessageChannel<T> channel,
) async {
dynamic messageEcho = nothing;
dynamic error = nothing;
try {
messageEcho = await channel.send(null);
} catch (e) {
error = e;
}
return resultOfHandshake(
'Message on unknown channel',
description,
null,
<dynamic>[null, null],
messageEcho,
error,
);
}
String toString(dynamic message) {
if (message is ByteData)
return message.buffer
.asUint8List(message.offsetInBytes, message.lengthInBytes)
.toString();
else
return '$message';
}
// Copyright 2017 The Chromium 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 'dart:async';
import 'package:flutter/services.dart';
import 'test_step.dart';
Future<TestStepResult> methodCallJsonSuccessHandshake(dynamic payload) async {
const MethodChannel channel =
const MethodChannel('json-method', const JSONMethodCodec());
return _methodCallSuccessHandshake(
'JSON success($payload)', channel, payload);
}
Future<TestStepResult> methodCallJsonErrorHandshake(dynamic payload) async {
const MethodChannel channel =
const MethodChannel('json-method', const JSONMethodCodec());
return _methodCallErrorHandshake('JSON error($payload)', channel, payload);
}
Future<TestStepResult> methodCallJsonNotImplementedHandshake() async {
const MethodChannel channel =
const MethodChannel('json-method', const JSONMethodCodec());
return _methodCallNotImplementedHandshake('JSON notImplemented()', channel);
}
Future<TestStepResult> methodCallStandardSuccessHandshake(
dynamic payload) async {
const MethodChannel channel =
const MethodChannel('std-method', const StandardMethodCodec());
return _methodCallSuccessHandshake(
'Standard success($payload)', channel, payload);
}
Future<TestStepResult> methodCallStandardErrorHandshake(dynamic payload) async {
const MethodChannel channel =
const MethodChannel('std-method', const StandardMethodCodec());
return _methodCallErrorHandshake(
'Standard error($payload)', channel, payload);
}
Future<TestStepResult> methodCallStandardNotImplementedHandshake() async {
const MethodChannel channel =
const MethodChannel('std-method', const StandardMethodCodec());
return _methodCallNotImplementedHandshake(
'Standard notImplemented()', channel);
}
Future<TestStepResult> _methodCallSuccessHandshake(
String description,
MethodChannel channel,
dynamic arguments,
) async {
final List<dynamic> received = <dynamic>[];
channel.setMethodCallHandler((MethodCall call) async {
received.add(call.arguments);
return call.arguments;
});
dynamic result = nothing;
dynamic error = nothing;
try {
result = await channel.invokeMethod('success', arguments);
} catch (e) {
error = e;
}
return resultOfHandshake(
'Method call success handshake',
description,
arguments,
received,
result,
error,
);
}
Future<TestStepResult> _methodCallErrorHandshake(
String description,
MethodChannel channel,
dynamic arguments,
) async {
final List<dynamic> received = <dynamic>[];
channel.setMethodCallHandler((MethodCall call) async {
received.add(call.arguments);
throw new PlatformException(
code: 'error', message: null, details: arguments);
});
dynamic errorDetails = nothing;
dynamic error = nothing;
try {
error = await channel.invokeMethod('error', arguments);
} on PlatformException catch (e) {
errorDetails = e.details;
} catch (e) {
error = e;
}
return resultOfHandshake(
'Method call error handshake',
description,
arguments,
received,
errorDetails,
error,
);
}
Future<TestStepResult> _methodCallNotImplementedHandshake(
String description,
MethodChannel channel,
) async {
final List<dynamic> received = <dynamic>[];
channel.setMethodCallHandler((MethodCall call) async {
received.add(call.arguments);
throw new MissingPluginException();
});
dynamic result = nothing;
dynamic error = nothing;
try {
error = await channel.invokeMethod('notImplemented');
} on MissingPluginException {
result = null;
} catch (e) {
error = e;
}
return resultOfHandshake(
'Method call not implemented handshake',
description,
null,
received,
result,
error,
);
}
// Copyright 2017 The Chromium 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 'dart:async';
import 'dart:typed_data';
import 'package:flutter/material.dart';
enum TestStatus { ok, pending, failed, complete }
typedef Future<TestStepResult> TestStep();
const String nothing = '-';
/// Result of a test step checking a nested communication handshake
/// between the Flutter app and the platform:
///
/// - The Flutter app sends a message to the platform.
/// - The platform, on receipt, echos the message back to Flutter in a separate message.
/// - The Flutter app records the incoming message echo and replies.
/// - The platform, on receipt of reply, echos the reply back to Flutter in a separate message.
/// - The Flutter app records the incoming reply echo.
/// - The platform finally replies to the original message with another echo.
class TestStepResult {
static const TextStyle bold = const TextStyle(fontWeight: FontWeight.bold);
static const TestStepResult complete = const TestStepResult(
'Test complete',
nothing,
TestStatus.complete,
);
const TestStepResult(
this.name,
this.description,
this.status, {
this.messageSent = nothing,
this.messageEcho = nothing,
this.messageReceived = nothing,
this.replyEcho = nothing,
this.error = nothing,
});
factory TestStepResult.fromSnapshot(AsyncSnapshot<TestStepResult> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const TestStepResult('Not started', nothing, TestStatus.ok);
case ConnectionState.waiting:
return const TestStepResult('Executing', nothing, TestStatus.pending);
case ConnectionState.done:
if (snapshot.hasData) {
return snapshot.data;
} else {
final TestStepResult result = snapshot.error;
return result;
}
break;
default:
throw 'Unsupported state ${snapshot.connectionState}';
}
}
final String name;
final String description;
final TestStatus status;
final dynamic messageSent;
final dynamic messageEcho;
final dynamic messageReceived;
final dynamic replyEcho;
final dynamic error;
Widget asWidget(BuildContext context) {
return new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Text('Step: $name', style: bold),
new Text(description),
const Text(' '),
new Text('Msg sent: ${_toString(messageSent)}'),
new Text('Msg rvcd: ${_toString(messageReceived)}'),
new Text('Reply echo: ${_toString(replyEcho)}'),
new Text('Msg echo: ${_toString(messageEcho)}'),
new Text('Error: ${_toString(error)}'),
const Text(' '),
new Text(
status.toString().substring('TestStatus.'.length),
key: new ValueKey<String>(
status == TestStatus.pending ? 'nostatus' : 'status'),
style: bold,
),
],
);
}
}
Future<TestStepResult> resultOfHandshake(
String name,
String description,
dynamic message,
List<dynamic> received,
dynamic messageEcho,
dynamic error,
) async {
assert(message != nothing);
while (received.length < 2) received.add(nothing);
TestStatus status;
if (!_deepEquals(messageEcho, message) ||
received.length != 2 ||
!_deepEquals(received[0], message) ||
!_deepEquals(received[1], message)) {
status = TestStatus.failed;
} else if (error != nothing) {
status = TestStatus.failed;
} else {
status = TestStatus.ok;
}
return new TestStepResult(
name,
description,
status,
messageSent: message,
messageEcho: messageEcho,
messageReceived: received[0],
replyEcho: received[1],
error: error,
);
}
String _toString(dynamic message) {
if (message is ByteData)
return message.buffer
.asUint8List(message.offsetInBytes, message.lengthInBytes)
.toString();
else
return '$message';
}
bool _deepEquals(dynamic a, dynamic b) {
if (a == b) return true;
if (a is double && a.isNaN) return b is double && b.isNaN;
if (a is ByteData) return b is ByteData && _deepEqualsByteData(a, b);
if (a is List) return b is List && _deepEqualsList(a, b);
if (a is Map) return b is Map && _deepEqualsMap(a, b);
return false;
}
bool _deepEqualsByteData(ByteData a, ByteData b) {
return _deepEqualsList(
a.buffer.asUint8List(a.offsetInBytes, a.lengthInBytes),
b.buffer.asUint8List(b.offsetInBytes, b.lengthInBytes),
);
}
bool _deepEqualsList(List<dynamic> a, List<dynamic> b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (!_deepEquals(a[i], b[i])) return false;
}
return true;
}
bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
if (a.length != b.length) return false;
for (dynamic key in a.keys) {
if (!b.containsKey(key) || !_deepEquals(a[key], b[key])) return false;
}
return true;
}
name: channels
description: Integration test for platform channels.
dependencies:
flutter:
sdk: flutter
flutter_driver:
sdk: flutter
flutter:
uses-material-design: true
// Copyright 2017 The Chromium 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 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('channel suite', () {
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
test('step through', () async {
final SerializableFinder stepButton = find.byValueKey('step');
final SerializableFinder statusField = find.byValueKey('status');
int step = 0;
while (await driver.getText(statusField) == 'ok') {
await driver.tap(stepButton);
step++;
}
final String status = await driver.getText(statusField);
if (status != 'complete') {
fail('Failed at step $step with status $status');
}
});
tearDownAll(() async {
driver?.close();
});
});
}
...@@ -7,9 +7,12 @@ import 'dart:ui' show hashValues; ...@@ -7,9 +7,12 @@ import 'dart:ui' show hashValues;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'platform_channel.dart';
/// A message encoding/decoding mechanism. /// A message encoding/decoding mechanism.
/// ///
/// Both operations throw [FormatException], if conversion fails. /// Both operations throw an exception, if conversion fails. Such situations
/// should be treated as programming errors.
/// ///
/// See also: /// See also:
/// ///
...@@ -92,15 +95,7 @@ class MethodCall { ...@@ -92,15 +95,7 @@ class MethodCall {
/// A codec for method calls and enveloped results. /// A codec for method calls and enveloped results.
/// ///
/// Result envelopes are binary messages with enough structure that the codec can /// All operations throw an exception, if conversion fails.
/// distinguish between a successful result and an error. In the former case,
/// the codec must be able to extract the result payload, possibly `null`. In
/// the latter case, the codec must be able to extract an error code string,
/// a (human-readable) error message string, and a value providing any
/// additional error details, possibly `null`. These data items are used to
/// populate a [PlatformException].
///
/// All operations throw [FormatException], if conversion fails.
/// ///
/// See also: /// See also:
/// ///
...@@ -109,7 +104,7 @@ class MethodCall { ...@@ -109,7 +104,7 @@ class MethodCall {
/// * [PlatformEventChannel], which use [MethodCodec]s for communication /// * [PlatformEventChannel], which use [MethodCodec]s for communication
/// between Flutter and platform plugins. /// between Flutter and platform plugins.
abstract class MethodCodec { abstract class MethodCodec {
/// Encodes the specified [methodCall] in binary. /// Encodes the specified [methodCall] into binary.
ByteData encodeMethodCall(MethodCall methodCall); ByteData encodeMethodCall(MethodCall methodCall);
/// Decodes the specified [methodCall] from binary. /// Decodes the specified [methodCall] from binary.
...@@ -139,10 +134,10 @@ abstract class MethodCodec { ...@@ -139,10 +134,10 @@ abstract class MethodCodec {
/// ///
/// * [MethodCodec], which throws a [PlatformException], if a received result /// * [MethodCodec], which throws a [PlatformException], if a received result
/// envelope represents an error. /// envelope represents an error.
/// * [PlatformMethodChannel.invokeMethod], which completes the returned future /// * [MethodChannel.invokeMethod], which completes the returned future
/// with a [PlatformException], if invoking the platform plugin method /// with a [PlatformException], if invoking the platform plugin method
/// results in an error envelope. /// results in an error envelope.
/// * [PlatformEventChannel.receiveBroadcastStream], which emits /// * [EventChannel.receiveBroadcastStream], which emits
/// [PlatformException]s as error events, whenever an event received from the /// [PlatformException]s as error events, whenever an event received from the
/// platform plugin is wrapped in an error envelope. /// platform plugin is wrapped in an error envelope.
class PlatformException implements Exception { class PlatformException implements Exception {
...@@ -175,9 +170,11 @@ class PlatformException implements Exception { ...@@ -175,9 +170,11 @@ class PlatformException implements Exception {
/// ///
/// See also: /// See also:
/// ///
/// * [PlatformMethodChannel.invokeMethod], which completes the returned future /// * [MethodChannel.invokeMethod], which completes the returned future
/// with a [MissingPluginException], if no plugin handler for the method call /// with a [MissingPluginException], if no plugin handler for the method call
/// was found. /// was found.
/// * [OptionalMethodChannel.invokeMethod], which completes the returned future
/// with `null`, if no plugin handler for the method call was found.
class MissingPluginException implements Exception { class MissingPluginException implements Exception {
/// Creates a [MissingPluginException] with an optional human-readable /// Creates a [MissingPluginException] with an optional human-readable
/// error message. /// error message.
......
...@@ -8,17 +8,20 @@ import 'dart:ui' as ui; ...@@ -8,17 +8,20 @@ import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'platform_channel.dart';
typedef Future<ByteData> _MessageHandler(ByteData message); typedef Future<ByteData> _MessageHandler(ByteData message);
/// Sends binary messages to and receives binary messages from platform plugins. /// Sends binary messages to and receives binary messages from platform plugins.
/// ///
/// See also: /// See also:
/// ///
/// * [BasicMessageChannel], which provides messaging services similar to /// * [BasicMessageChannel], which provides basic messaging services similar to
/// [BinaryMessages], but with pluggable message codecs in support of sending /// `BinaryMessages`, but with pluggable message codecs in support of sending
/// strings or semi-structured messages. /// strings or semi-structured messages.
/// * [MethodChannel], which provides higher-level platform /// * [MethodChannel], which provides platform communication using asynchronous
/// communication such as method invocations and event streams. /// method calls.
/// * [EventChannel], which provides platform communication using event streams.
/// ///
/// See: <https://flutter.io/platform-channels/> /// See: <https://flutter.io/platform-channels/>
class BinaryMessages { class BinaryMessages {
...@@ -54,7 +57,7 @@ class BinaryMessages { ...@@ -54,7 +57,7 @@ class BinaryMessages {
/// Typically called by [ServicesBinding] to handle platform messages received /// Typically called by [ServicesBinding] to handle platform messages received
/// from [ui.window.onPlatformMessage]. /// from [ui.window.onPlatformMessage].
/// ///
/// To register a handler for a given message channel, see [PlatformChannel]. /// To register a handler for a given message channel, see [setMessageHandler].
static Future<Null> handlePlatformMessage( static Future<Null> handlePlatformMessage(
String channel, ByteData data, ui.PlatformMessageResponseCallback callback) async { String channel, ByteData data, ui.PlatformMessageResponseCallback callback) async {
ByteData response; ByteData response;
......
...@@ -9,7 +9,7 @@ import 'package:flutter/services.dart'; ...@@ -9,7 +9,7 @@ import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
group('PlatformMessageChannel', () { group('BasicMessageChannel', () {
const MessageCodec<String> string = const StringCodec(); const MessageCodec<String> string = const StringCodec();
const BasicMessageChannel<String> channel = const BasicMessageChannel<String>('ch', string); const BasicMessageChannel<String> channel = const BasicMessageChannel<String>('ch', string);
test('can send string message and get reply', () async { test('can send string message and get reply', () async {
...@@ -34,7 +34,7 @@ void main() { ...@@ -34,7 +34,7 @@ void main() {
}); });
}); });
group('PlatformMethodChannel', () { group('MethodChannel', () {
const MessageCodec<dynamic> jsonMessage = const JSONMessageCodec(); const MessageCodec<dynamic> jsonMessage = const JSONMessageCodec();
const MethodCodec jsonMethod = const JSONMethodCodec(); const MethodCodec jsonMethod = const JSONMethodCodec();
const MethodChannel channel = const MethodChannel('ch7', jsonMethod); const MethodChannel channel = const MethodChannel('ch7', jsonMethod);
...@@ -89,8 +89,75 @@ void main() { ...@@ -89,8 +89,75 @@ void main() {
fail('MissingPluginException expected'); fail('MissingPluginException expected');
} }
}); });
test('can handle method call with no registered plugin', () async {
channel.setMethodCallHandler(null);
final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello'));
ByteData envelope;
await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) {
envelope = result;
}); });
group('PlatformEventChannel', () { expect(envelope, isNull);
});
test('can handle method call of unimplemented method', () async {
channel.setMethodCallHandler((MethodCall call) async {
throw new MissingPluginException();
});
final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello'));
ByteData envelope;
await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) {
envelope = result;
});
expect(envelope, isNull);
});
test('can handle method call with successful result', () async {
channel.setMethodCallHandler((MethodCall call) async => '${call.arguments}, world');
final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello'));
ByteData envelope;
await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) {
envelope = result;
});
expect(jsonMethod.decodeEnvelope(envelope), equals('hello, world'));
});
test('can handle method call with expressive error result', () async {
channel.setMethodCallHandler((MethodCall call) async {
throw new PlatformException(code: 'bad', message: 'sayHello failed', details: null);
});
final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello'));
ByteData envelope;
await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) {
envelope = result;
});
try {
jsonMethod.decodeEnvelope(envelope);
fail('Exception expected');
} on PlatformException catch(e) {
expect(e.code, equals('bad'));
expect(e.message, equals('sayHello failed'));
} catch (e) {
fail('PlatformException expected');
}
});
test('can handle method call with other error result', () async {
channel.setMethodCallHandler((MethodCall call) async {
throw new ArgumentError('bad');
});
final ByteData call = jsonMethod.encodeMethodCall(new MethodCall('sayHello', 'hello'));
ByteData envelope;
await BinaryMessages.handlePlatformMessage('ch7', call, (ByteData result) {
envelope = result;
});
try {
jsonMethod.decodeEnvelope(envelope);
fail('Exception expected');
} on PlatformException catch(e) {
expect(e.code, equals('error'));
expect(e.message, equals('Invalid argument(s): bad'));
} catch (e) {
fail('PlatformException expected');
}
});
});
group('EventChannel', () {
const MessageCodec<dynamic> jsonMessage = const JSONMessageCodec(); const MessageCodec<dynamic> jsonMessage = const JSONMessageCodec();
const MethodCodec jsonMethod = const JSONMethodCodec(); const MethodCodec jsonMethod = const JSONMethodCodec();
const EventChannel channel = const EventChannel('ch', jsonMethod); const EventChannel channel = const EventChannel('ch', jsonMethod);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment