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 @@
import 'dart:async';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
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(createPlatformServiceDriverTest());
await task(createChannelsIntegrationTest());
}
......@@ -4,11 +4,11 @@
import 'dart:async';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
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(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';
import '../framework/ios.dart';
import '../framework/utils.dart';
TaskFunction createPlatformServiceDriverTest() {
return new DriverTest(
'${flutterDirectory.path}/examples/platform_channel',
'test_driver/button_tap.dart',
);
}
TaskFunction createComplexLayoutScrollPerfTest() {
return new PerfTest(
'${flutterDirectory.path}/dev/benchmarks/complex_layout',
......@@ -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 {
BuildTest(this.testDirectory);
......
......@@ -83,7 +83,13 @@ tasks:
required_agent_capabilities: ["has-android-device"]
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: >
Runs a driver test on the Platform Channel sample app on Android.
stage: devicelab
......@@ -154,7 +160,13 @@ tasks:
# 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: >
Runs a driver test on the Platform Channel sample app on iOS.
stage: devicelab_ios
......@@ -207,6 +219,13 @@ tasks:
# 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:
description: >
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
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* PluginRegistry.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
913134F849F6C8DEAC837F96 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C803412E9584DEAC0259A174 /* Pods_Runner.framework */; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; };
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* PluginRegistry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PluginRegistry.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* PluginRegistry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PluginRegistry.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C803412E9584DEAC0259A174 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
913134F849F6C8DEAC837F96 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
840012C8B5EDBCF56B0E4AC1 /* Pods */ = {
isa = PBXGroup;
children = (
);
name = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
9740EEB71CF902C7004384FC /* app.flx */,
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
840012C8B5EDBCF56B0E4AC1 /* Pods */,
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* PluginRegistry.h */,
1498D2331E8E89220040F4C2 /* PluginRegistry.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = {
isa = PBXGroup;
children = (
C803412E9584DEAC0259A174 /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */,
532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = AQ7UHDBEXJ;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9740EEBB1CF902C7004384FC /* app.flx in Resources */,
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
532EA9D341340B1DCD08293D /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* PluginRegistry.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = AQ7UHDBEXJ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.channels;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
ProvisioningStyle = Automatic;
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
DEVELOPMENT_TEAM = AQ7UHDBEXJ;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
PRODUCT_BUNDLE_IDENTIFIER = com.yourcompany.channels;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
ProvisioningStyle = Automatic;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?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;
import 'package:flutter/foundation.dart';
import 'platform_channel.dart';
/// 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:
///
......@@ -92,15 +95,7 @@ class MethodCall {
/// A codec for method calls and enveloped results.
///
/// Result envelopes are binary messages with enough structure that the codec can
/// 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.
/// All operations throw an exception, if conversion fails.
///
/// See also:
///
......@@ -109,7 +104,7 @@ class MethodCall {
/// * [PlatformEventChannel], which use [MethodCodec]s for communication
/// between Flutter and platform plugins.
abstract class MethodCodec {
/// Encodes the specified [methodCall] in binary.
/// Encodes the specified [methodCall] into binary.
ByteData encodeMethodCall(MethodCall methodCall);
/// Decodes the specified [methodCall] from binary.
......@@ -139,10 +134,10 @@ abstract class MethodCodec {
///
/// * [MethodCodec], which throws a [PlatformException], if a received result
/// 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
/// results in an error envelope.
/// * [PlatformEventChannel.receiveBroadcastStream], which emits
/// * [EventChannel.receiveBroadcastStream], which emits
/// [PlatformException]s as error events, whenever an event received from the
/// platform plugin is wrapped in an error envelope.
class PlatformException implements Exception {
......@@ -175,9 +170,11 @@ class PlatformException implements Exception {
///
/// 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
/// 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 {
/// Creates a [MissingPluginException] with an optional human-readable
/// error message.
......
......@@ -17,14 +17,15 @@ import 'platform_messages.dart';
/// Messages are encoded into binary before being sent, and binary messages
/// received are decoded into Dart values. The [MessageCodec] used must be
/// compatible with the one used by the platform plugin. This can be achieved
/// by creating a FlutterMessageChannel counterpart of this channel on the
/// by creating a `BasicMessageChannel` counterpart of this channel on the
/// platform side. The Dart type of messages sent and received is [T],
/// but only the values supported by the specified [MessageCodec] can be used.
/// The use of unsupported values should be considered programming errors, and
/// will result in exceptions being thrown. The `null` message is supported
/// for all codecs.
///
/// The identity of the channel is given by its name, so other uses of that name
/// with may interfere with this channel's communication. Specifically, at most
/// one message handler can be registered with the channel name at any given
/// time.
/// The logical identity of the channel is given by its name. Identically named
/// channels will interfere with each other's communication.
///
/// See: <https://flutter.io/platform-channels/>
class BasicMessageChannel<T> {
......@@ -41,23 +42,21 @@ class BasicMessageChannel<T> {
/// Sends the specified [message] to the platform plugins on this channel.
///
/// Returns a [Future] which completes to the received and decoded response,
/// or to a [FormatException], if encoding or decoding fails.
/// Returns a [Future] which completes to the received response, which may
/// be `null`.
Future<T> send(T message) async {
return codec.decodeMessage(
await BinaryMessages.send(name, codec.encodeMessage(message))
);
return codec.decodeMessage(await BinaryMessages.send(name, codec.encodeMessage(message)));
}
/// Sets a callback for receiving messages from the platform plugins on this
/// channel.
/// channel. Messages may be `null`.
///
/// The given callback will replace the currently registered callback for this
/// channel, if any. To remove the handler, pass `null` as the `handler`
/// argument.
///
/// The handler's return value, if non-null, is sent back to the platform
/// plugins as a response.
/// The handler's return value is sent back to the platform plugins as a
/// message reply. It may be `null`.
void setMessageHandler(Future<T> handler(T message)) {
if (handler == null) {
BinaryMessages.setMessageHandler(name, null);
......@@ -69,12 +68,13 @@ class BasicMessageChannel<T> {
}
/// Sets a mock callback for intercepting messages sent on this channel.
/// Messages may be `null`.
///
/// The given callback will replace the currently registered mock callback for
/// this channel, if any. To remove the mock handler, pass `null` as the
/// `handler` argument.
///
/// The handler's return value, if non-null, is used as a response.
/// The handler's return value is used as a message reply. It may be `null`.
///
/// This is intended for testing. Messages intercepted in this manner are not
/// sent to platform plugins.
......@@ -95,12 +95,15 @@ class BasicMessageChannel<T> {
/// Method calls are encoded into binary before being sent, and binary results
/// received are decoded into Dart values. The [MethodCodec] used must be
/// compatible with the one used by the platform plugin. This can be achieved
/// by creating a FlutterMethodChannel counterpart of this channel on the
/// platform side. The Dart type of messages sent and received is `dynamic`,
/// by creating a `MethodChannel` counterpart of this channel on the
/// platform side. The Dart type of arguments and results is `dynamic`,
/// but only values supported by the specified [MethodCodec] can be used.
/// The use of unsupported values should be considered programming errors, and
/// will result in exceptions being thrown. The `null` value is supported
/// for all codecs.
///
/// The identity of the channel is given by its name, so other uses of that name
/// with may interfere with this channel's communication.
/// The logical identity of the channel is given by its name. Identically named
/// channels will interfere with each other's communication.
///
/// See: <https://flutter.io/platform-channels/>
class MethodChannel {
......@@ -124,8 +127,8 @@ class MethodChannel {
///
/// * a result (possibly `null`), on successful invocation;
/// * a [PlatformException], if the invocation failed in the platform plugin;
/// * a [FormatException], if encoding or decoding failed.
/// * a [MissingPluginException], if the method has not been implemented.
/// * a [MissingPluginException], if the method has not been implemented by a
/// platform plugin.
Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {
assert(method != null);
final dynamic result = await BinaryMessages.send(
......@@ -150,26 +153,12 @@ class MethodChannel {
/// populate an error envelope which is sent back instead. If the future
/// completes with a [MissingPluginException], an empty reply is sent
/// similarly to what happens if no method call handler has been set.
/// Any other exception results in an error envelope being sent.
void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
if (handler == null) {
BinaryMessages.setMessageHandler(name, null);
} else {
BinaryMessages.setMessageHandler(
name,
(ByteData message) async {
final MethodCall call = codec.decodeMethodCall(message);
try {
final dynamic result = await handler(call);
return codec.encodeSuccessEnvelope(result);
} on PlatformException catch (e) {
return codec.encodeErrorEnvelope(
code: e.code, message: e.message, details: e.details);
} on MissingPluginException {
return null;
}
},
);
}
BinaryMessages.setMessageHandler(
name,
handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler),
);
}
/// Sets a mock callback for intercepting method invocations on this channel.
......@@ -186,24 +175,26 @@ class MethodChannel {
/// This is intended for testing. Method calls intercepted in this manner are
/// not sent to platform plugins.
void setMockMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
if (handler == null) {
BinaryMessages.setMockMessageHandler(name, null);
} else {
BinaryMessages.setMockMessageHandler(
name,
(ByteData message) async {
final MethodCall call = codec.decodeMethodCall(message);
try {
final dynamic result = await handler(call);
return codec.encodeSuccessEnvelope(result);
} on PlatformException catch (e) {
return codec.encodeErrorEnvelope(
code: e.code, message: e.message, details: e.details);
} on MissingPluginException {
return null;
}
},
BinaryMessages.setMockMessageHandler(
name,
handler == null ? null : (ByteData message) => _handleAsMethodCall(message, handler),
);
}
Future<ByteData> _handleAsMethodCall(ByteData message, Future<dynamic> handler(MethodCall call)) async {
final MethodCall call = codec.decodeMethodCall(message);
try {
return codec.encodeSuccessEnvelope(await handler(call));
} on PlatformException catch (e) {
return codec.encodeErrorEnvelope(
code: e.code,
message: e.message,
details: e.details,
);
} on MissingPluginException {
return null;
} catch (e) {
return codec.encodeErrorEnvelope(code: 'error', message: e.toString(), details: null);
}
}
}
......@@ -230,18 +221,19 @@ class OptionalMethodChannel extends MethodChannel {
/// A named channel for communicating with platform plugins using event streams.
///
/// Stream setup requests are encoded into binary before being sent,
/// and binary events received are decoded into Dart values. The [MethodCodec]
/// used must be compatible with the one used by the platform plugin. This can
/// be achieved by creating a FlutterEventChannel counterpart of this channel on
/// the platform side. The Dart type of events sent and received is `dynamic`,
/// but only values supported by the specified [MethodCodec] can be used.
/// and binary events and errors received are decoded into Dart values.
/// The [MethodCodec] used must be compatible with the one used by the platform
/// plugin. This can be achieved by creating an `EventChannel` counterpart of
/// this channel on the platform side. The Dart type of events sent and received
/// is `dynamic`, but only values supported by the specified [MethodCodec] can
/// be used.
///
/// The identity of the channel is given by its name, so other uses of that name
/// with may interfere with this channel's communication.
/// The logical identity of the channel is given by its name. Identically named
/// channels will interfere with each other's communication.
///
/// See: <https://flutter.io/platform-channels/>
class EventChannel {
/// Creates a [EventChannel] with the specified [name].
/// Creates an [EventChannel] with the specified [name].
///
/// The [codec] used will be [StandardMethodCodec], unless otherwise
/// specified.
......@@ -263,61 +255,44 @@ class EventChannel {
/// received from the platform plugin;
/// * an error event containing a [PlatformException] for each error event
/// received from the platform plugin;
/// * an error event containing a [FormatException] for each event received
/// where decoding fails;
/// * an error event containing a [PlatformException],
/// [MissingPluginException], or [FormatException] whenever stream setup fails
/// (stream setup is done only when listener count changes from 0 to 1).
///
/// Notes for platform plugin implementers:
///
/// Plugins must expose methods named `listen` and `cancel` suitable for
/// invocations by [MethodChannel.invokeMethod]. Both methods are
/// invoked with the specified [arguments].
///
/// Following the semantics of broadcast streams, `listen` will be called as
/// the first listener registers with the returned stream, and `cancel` when
/// the last listener cancels its registration. This pattern may repeat
/// indefinitely. Platform plugins should consume no stream-related resources
/// while listener count is zero.
/// Errors occurring during stream activation or deactivation are reported
/// through the [FlutterError] facility. Stream activation happens only when
/// stream listener count changes from 0 to 1. Stream deactivation happens
/// only when stream listener count changes from 1 to 0.
Stream<dynamic> receiveBroadcastStream([dynamic arguments]) {
final MethodChannel methodChannel = new MethodChannel(name, codec);
StreamController<dynamic> controller;
controller = new StreamController<dynamic>.broadcast(
onListen: () async {
BinaryMessages.setMessageHandler(
name, (ByteData reply) async {
if (reply == null) {
controller.close();
} else {
try {
controller.add(codec.decodeEnvelope(reply));
} catch (e) {
controller.addError(e);
}
}
}
);
try {
await methodChannel.invokeMethod('listen', arguments);
} catch (e) {
BinaryMessages.setMessageHandler(name, null);
controller.addError(e);
}
}, onCancel: () async {
BinaryMessages.setMessageHandler(name, null);
try {
await methodChannel.invokeMethod('cancel', arguments);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'while de-activating platform stream on channel $name',
));
}
controller = new StreamController<dynamic>.broadcast(onListen: () async {
BinaryMessages.setMessageHandler(name, (ByteData reply) async {
if (reply == null)
controller.close();
else
controller.add(codec.decodeEnvelope(reply));
});
try {
await methodChannel.invokeMethod('listen', arguments);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'while activating platform stream on channel $name',
));
}
);
}, onCancel: () async {
BinaryMessages.setMessageHandler(name, null);
try {
await methodChannel.invokeMethod('cancel', arguments);
} catch (exception, stack) {
FlutterError.reportError(new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: 'while de-activating platform stream on channel $name',
));
}
});
return controller.stream;
}
}
......@@ -8,17 +8,20 @@ import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'platform_channel.dart';
typedef Future<ByteData> _MessageHandler(ByteData message);
/// Sends binary messages to and receives binary messages from platform plugins.
///
/// See also:
///
/// * [BasicMessageChannel], which provides messaging services similar to
/// [BinaryMessages], but with pluggable message codecs in support of sending
/// strings or semi-structured messages.
/// * [MethodChannel], which provides higher-level platform
/// communication such as method invocations and event streams.
/// * [BasicMessageChannel], which provides basic messaging services similar to
/// `BinaryMessages`, but with pluggable message codecs in support of sending
/// strings or semi-structured messages.
/// * [MethodChannel], which provides platform communication using asynchronous
/// method calls.
/// * [EventChannel], which provides platform communication using event streams.
///
/// See: <https://flutter.io/platform-channels/>
class BinaryMessages {
......@@ -54,7 +57,7 @@ class BinaryMessages {
/// Typically called by [ServicesBinding] to handle platform messages received
/// 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(
String channel, ByteData data, ui.PlatformMessageResponseCallback callback) async {
ByteData response;
......
......@@ -9,7 +9,7 @@ import 'package:flutter/services.dart';
import 'package:test/test.dart';
void main() {
group('PlatformMessageChannel', () {
group('BasicMessageChannel', () {
const MessageCodec<String> string = const StringCodec();
const BasicMessageChannel<String> channel = const BasicMessageChannel<String>('ch', string);
test('can send string message and get reply', () async {
......@@ -34,7 +34,7 @@ void main() {
});
});
group('PlatformMethodChannel', () {
group('MethodChannel', () {
const MessageCodec<dynamic> jsonMessage = const JSONMessageCodec();
const MethodCodec jsonMethod = const JSONMethodCodec();
const MethodChannel channel = const MethodChannel('ch7', jsonMethod);
......@@ -89,8 +89,75 @@ void main() {
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;
});
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('PlatformEventChannel', () {
group('EventChannel', () {
const MessageCodec<dynamic> jsonMessage = const JSONMessageCodec();
const MethodCodec jsonMethod = const JSONMethodCodec();
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