Commit dce4bf85 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Remove old platform messaging API (#8837)

Breaking change: removed deprecated methods of PlatformMessages, leaving only binary messaging there. All other use of platform communication now goes through PlatformMessageChannel and PlatformMethodChannels. Retained use of String and JSON codecs for now.

Companion engine PR: flutter/engine#3482
parent 3d390087
4a5a32466958dab49b9940e4528ee6d523f4a5ac c4edec741704ace2c4edeff57ac348435cd1f898
package com.example.flutter; package com.example.flutter;
import android.app.Instrumentation;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule; import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import io.flutter.view.FlutterView; import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import android.app.Instrumentation; import java.util.Arrays;
import android.support.test.InstrumentationRegistry;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*; import io.flutter.plugin.common.FlutterMethodChannel;
import io.flutter.view.FlutterView;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest { public class ExampleInstrumentedTest {
@Rule @Rule
public ActivityTestRule<ExampleActivity> activityRule = public ActivityTestRule<ExampleActivity> activityRule =
new ActivityTestRule<>(ExampleActivity.class); new ActivityTestRule<>(ExampleActivity.class);
@Test @Test
public void testFlutterMessage() { public void testFlutterMessage() {
final Instrumentation instr = InstrumentationRegistry.getInstrumentation(); final Instrumentation instr = InstrumentationRegistry.getInstrumentation();
final JSONObject message = new JSONObject();
final int RANDOM_MIN = 1; final int RANDOM_MIN = 1;
final int RANDOM_MAX = 1000; final int RANDOM_MAX = 1000;
try {
message.put("min", RANDOM_MIN);
message.put("max", RANDOM_MAX);
} catch (JSONException e) {
fail(e.getMessage());
}
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
final AtomicInteger random = new AtomicInteger(); final AtomicInteger random = new AtomicInteger();
...@@ -46,17 +41,18 @@ public class ExampleInstrumentedTest { ...@@ -46,17 +41,18 @@ public class ExampleInstrumentedTest {
instr.runOnMainSync(new Runnable() { instr.runOnMainSync(new Runnable() {
public void run() { public void run() {
final FlutterView flutterView = (FlutterView) activityRule.getActivity().findViewById( final FlutterView flutterView = (FlutterView) activityRule.getActivity().findViewById(
R.id.flutter_view); R.id.flutter_view);
flutterView.sendToFlutter("getRandom", message.toString(), new FlutterView.MessageReplyCallback() { final FlutterMethodChannel randomChannel = new FlutterMethodChannel(flutterView, "random");
public void onReply(String json) { randomChannel.invokeMethod("getRandom", Arrays.asList(RANDOM_MIN, RANDOM_MAX), new FlutterMethodChannel.Response() {
try { @Override
JSONObject reply = new JSONObject(json); public void success(Object o) {
random.set(reply.getInt("value")); random.set(((Number) o).intValue());
} catch (JSONException e) { latch.countDown();
fail(e.getMessage()); }
} finally {
latch.countDown(); @Override
} public void error(String code, String message, Object details) {
} }
}); });
} }
...@@ -78,7 +74,7 @@ public class ExampleInstrumentedTest { ...@@ -78,7 +74,7 @@ public class ExampleInstrumentedTest {
instr.runOnMainSync(new Runnable() { instr.runOnMainSync(new Runnable() {
public void run() { public void run() {
final FlutterView flutterView = (FlutterView) activityRule.getActivity().findViewById( final FlutterView flutterView = (FlutterView) activityRule.getActivity().findViewById(
R.id.flutter_view); R.id.flutter_view);
// Call onPostResume to start the engine's renderer even if the activity // Call onPostResume to start the engine's renderer even if the activity
// is paused in the test environment. // is paused in the test environment.
...@@ -105,13 +101,23 @@ public class ExampleInstrumentedTest { ...@@ -105,13 +101,23 @@ public class ExampleInstrumentedTest {
// Waits on a FlutterView until it is able to produce a bitmap. // Waits on a FlutterView until it is able to produce a bitmap.
private class BitmapPoller { private class BitmapPoller {
private final int delayMsec = 1000;
private int triesPending; private int triesPending;
private int waitMsec; private int waitMsec;
private FlutterView flutterView; private FlutterView flutterView;
private Bitmap bitmap; private Bitmap bitmap;
private CountDownLatch latch = new CountDownLatch(1); private CountDownLatch latch = new CountDownLatch(1);
private Runnable checkBitmap = new Runnable() {
private final int delayMsec = 1000; public void run() {
bitmap = flutterView.getBitmap();
triesPending--;
if (bitmap != null || triesPending == 0) {
latch.countDown();
} else {
flutterView.postDelayed(checkBitmap, delayMsec);
}
}
};
BitmapPoller(int tries) { BitmapPoller(int tries) {
triesPending = tries; triesPending = tries;
...@@ -127,17 +133,5 @@ public class ExampleInstrumentedTest { ...@@ -127,17 +133,5 @@ public class ExampleInstrumentedTest {
latch.await(waitMsec, TimeUnit.MILLISECONDS); latch.await(waitMsec, TimeUnit.MILLISECONDS);
return bitmap; return bitmap;
} }
private Runnable checkBitmap = new Runnable() {
public void run() {
bitmap = flutterView.getBitmap();
triesPending--;
if (bitmap != null || triesPending == 0) {
latch.countDown();
} else {
flutterView.postDelayed(checkBitmap, delayMsec);
}
}
};
} }
} }
...@@ -6,28 +6,26 @@ package com.example.flutter; ...@@ -6,28 +6,26 @@ package com.example.flutter;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.location.Location; import android.location.Location;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import io.flutter.plugin.common.FlutterMethodChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.view.FlutterMain; import io.flutter.view.FlutterMain;
import io.flutter.view.FlutterView; import io.flutter.view.FlutterView;
import java.io.File; import java.util.Arrays;
import org.json.JSONException;
import org.json.JSONObject;
public class ExampleActivity extends Activity { public class ExampleActivity extends Activity {
private static final String TAG = "ExampleActivity"; private static final String TAG = "ExampleActivity";
private FlutterView flutterView; private FlutterView flutterView;
private FlutterMethodChannel randomChannel;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
...@@ -39,20 +37,24 @@ public class ExampleActivity extends Activity { ...@@ -39,20 +37,24 @@ public class ExampleActivity extends Activity {
flutterView = (FlutterView) findViewById(R.id.flutter_view); flutterView = (FlutterView) findViewById(R.id.flutter_view);
flutterView.runFromBundle(FlutterMain.findAppBundlePath(getApplicationContext()), null); flutterView.runFromBundle(FlutterMain.findAppBundlePath(getApplicationContext()), null);
flutterView.addOnMessageListener("getLocation", FlutterMethodChannel locationChannel = new FlutterMethodChannel(flutterView, "location");
new FlutterView.OnMessageListener() { randomChannel = new FlutterMethodChannel(flutterView, "random");
@Override
public String onMessage(FlutterView view, String message) { locationChannel.setMethodCallHandler(new FlutterMethodChannel.MethodCallHandler() {
return onGetLocation(message); @Override
public void onMethodCall(MethodCall methodCall, FlutterMethodChannel.Response response) {
if (methodCall.method.equals("getLocation")) {
getLocation((String) methodCall.arguments, response);
} else {
response.error("unknown method", "Unknown method: " + methodCall.method, null);
} }
}); }
});
Button getRandom = (Button) findViewById(R.id.get_random); Button getRandom = (Button) findViewById(R.id.get_random);
getRandom.setOnClickListener(new View.OnClickListener() { getRandom.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) { getRandom(); }
sendGetRandom();
}
}); });
} }
...@@ -76,79 +78,44 @@ public class ExampleActivity extends Activity { ...@@ -76,79 +78,44 @@ public class ExampleActivity extends Activity {
flutterView.onPostResume(); flutterView.onPostResume();
} }
private void sendGetRandom() { private void getRandom() {
JSONObject message = new JSONObject(); randomChannel.invokeMethod("getRandom", Arrays.asList(1, 1000), new FlutterMethodChannel.Response() {
try { TextView textView = (TextView) findViewById(R.id.random_value);
message.put("min", 1);
message.put("max", 1000);
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return;
}
flutterView.sendToFlutter("getRandom", message.toString(),
new FlutterView.MessageReplyCallback() {
@Override
public void onReply(String json) {
onRandomReply(json);
}
});
}
private void onRandomReply(String json) { @Override
double value; public void success(Object result) {
try { textView.setText(result.toString());
JSONObject reply = new JSONObject(json); }
value = reply.getDouble("value");
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return;
}
TextView randomValue = (TextView) findViewById(R.id.random_value); @Override
randomValue.setText(Double.toString(value)); public void error(String code, String message, Object details) {
textView.setText("Error: " + message);
}
});
} }
private String onGetLocation(String json) { private void getLocation(String provider, FlutterMethodChannel.Response response) {
String provider;
try {
JSONObject message = new JSONObject(json);
provider = message.getString("provider");
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return null;
}
String locationProvider; String locationProvider;
if (provider.equals("network")) { if (provider.equals("network")) {
locationProvider = LocationManager.NETWORK_PROVIDER; locationProvider = LocationManager.NETWORK_PROVIDER;
} else if (provider.equals("gps")) { } else if (provider.equals("gps")) {
locationProvider = LocationManager.GPS_PROVIDER; locationProvider = LocationManager.GPS_PROVIDER;
} else { } else {
return null; response.error("unknown provider", "Unknown location provider: " + provider, null);
return;
} }
String permission = "android.permission.ACCESS_FINE_LOCATION"; String permission = "android.permission.ACCESS_FINE_LOCATION";
Location location = null;
if (checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) { if (checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
location = locationManager.getLastKnownLocation(locationProvider); Location location = locationManager.getLastKnownLocation(locationProvider);
}
JSONObject reply = new JSONObject();
try {
if (location != null) { if (location != null) {
reply.put("latitude", location.getLatitude()); response.success(Arrays.asList(location.getLatitude(), location.getLongitude()));
reply.put("longitude", location.getLongitude());
} else { } else {
reply.put("latitude", 0); response.error("location unavailable", "Location is not available", null);
reply.put("longitude", 0);
} }
} catch (JSONException e) { } else {
Log.e(TAG, "JSON exception", e); response.error("access error", "Location permissions not granted", null);
return null;
} }
return reply.toString();
} }
} }
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
9740EEB41CF90195004384FC /* Flutter.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Flutter.xcconfig */; }; 9740EEB41CF90195004384FC /* Flutter.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Flutter.xcconfig */; };
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; }; 9740EEBB1CF902C7004384FC /* app.flx in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB71CF902C7004384FC /* app.flx */; };
977505191CFDF23500BC28DA /* LocationProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = 977505181CFDF23500BC28DA /* LocationProvider.m */; };
97A38A351CFDEC880099F1B4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 97A38A341CFDEC880099F1B4 /* AppDelegate.m */; }; 97A38A351CFDEC880099F1B4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 97A38A341CFDEC880099F1B4 /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
...@@ -42,8 +41,6 @@ ...@@ -42,8 +41,6 @@
9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = "<group>"; }; 9740EEB71CF902C7004384FC /* app.flx */ = {isa = PBXFileReference; lastKnownFileType = file; name = app.flx; path = Flutter/app.flx; sourceTree = "<group>"; };
9740EEB81CF902C7004384FC /* app.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = app.dylib; path = Flutter/app.dylib; sourceTree = "<group>"; }; 9740EEB81CF902C7004384FC /* app.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = app.dylib; path = Flutter/app.dylib; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
977505171CFDF21E00BC28DA /* LocationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocationProvider.h; sourceTree = "<group>"; };
977505181CFDF23500BC28DA /* LocationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationProvider.m; sourceTree = "<group>"; };
97A38A331CFDEC680099F1B4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; 97A38A331CFDEC680099F1B4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
97A38A341CFDEC880099F1B4 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; 97A38A341CFDEC880099F1B4 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
...@@ -105,8 +102,6 @@ ...@@ -105,8 +102,6 @@
97C146F11CF9000F007C117D /* Supporting Files */, 97C146F11CF9000F007C117D /* Supporting Files */,
97A38A331CFDEC680099F1B4 /* AppDelegate.h */, 97A38A331CFDEC680099F1B4 /* AppDelegate.h */,
97A38A341CFDEC880099F1B4 /* AppDelegate.m */, 97A38A341CFDEC880099F1B4 /* AppDelegate.m */,
977505171CFDF21E00BC28DA /* LocationProvider.h */,
977505181CFDF23500BC28DA /* LocationProvider.m */,
); );
path = Runner; path = Runner;
sourceTree = "<group>"; sourceTree = "<group>";
......
...@@ -5,24 +5,34 @@ ...@@ -5,24 +5,34 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#import <Flutter/Flutter.h> #import <Flutter/Flutter.h>
#import "LocationProvider.h" #import <CoreLocation/CoreLocation.h>
@implementation AppDelegate { @implementation AppDelegate {
LocationProvider* _locationProvider; CLLocationManager* _locationManager;
} }
- (BOOL)application:(UIApplication*)application
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterDartProject* project = [[FlutterDartProject alloc] initFromDefaultSourceForConfiguration]; FlutterViewController* controller =
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; (FlutterViewController*)self.window.rootViewController;
FlutterViewController* flutterController = [[FlutterViewController alloc] initWithProject:project FlutterMethodChannel* locationChannel = [FlutterMethodChannel
nibName:nil methodChannelNamed:@"location"
bundle:nil]; binaryMessenger:controller
_locationProvider = [[LocationProvider alloc] init]; codec:[FlutterStandardMethodCodec sharedInstance]];
[flutterController addMessageListener:_locationProvider]; [locationChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResultReceiver result) {
if ([@"getLocation" isEqualToString:call.method]) {
self.window.rootViewController = flutterController; if (_locationManager == nil) {
[self.window makeKeyAndVisible]; _locationManager = [[CLLocationManager alloc] init];
return YES; [_locationManager startMonitoringSignificantLocationChanges];
}
CLLocation* location = _locationManager.location;
result(@[@(location.coordinate.latitude), @(location.coordinate.longitude)], nil);
} else {
result(nil, [FlutterError errorWithCode:@"unknown method"
message:@"Unknown location method called"
details:nil]);
}
}];
return YES;
} }
@end @end
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<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"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies> <dependencies>
<deployment identifier="iOS"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--View Controller--> <!--View Controller-->
...@@ -14,9 +18,9 @@ ...@@ -14,9 +18,9 @@
<viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/> <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6211" systemVersion="14A298i" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6204"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
<!--View Controller--> <!--View Controller-->
<scene sceneID="tne-QT-ifu"> <scene sceneID="tne-QT-ifu">
<objects> <objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController"> <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides> <layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view> </view>
</viewController> </viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
......
// Copyright 2016 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 <Flutter/Flutter.h>
@interface LocationProvider : NSObject <FlutterMessageListener>
@end
// Copyright 2016 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 "LocationProvider.h"
#import <CoreLocation/CoreLocation.h>
@implementation LocationProvider {
CLLocationManager* _locationManager;
}
@synthesize messageName = _messageName;
- (instancetype) init {
self = [super init];
if (self)
self->_messageName = @"getLocation";
return self;
}
- (NSString*)didReceiveString:(NSString*)message {
if (_locationManager == nil) {
_locationManager = [[CLLocationManager alloc] init];
[_locationManager startMonitoringSignificantLocationChanges];
}
CLLocation* location = _locationManager.location;
NSDictionary* response = @{
@"latitude": @(location.coordinate.latitude),
@"longitude": @(location.coordinate.longitude),
};
NSData* data = [NSJSONSerialization dataWithJSONObject:response options:0 error:nil];
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
@end
...@@ -10,13 +10,14 @@ import 'package:flutter/services.dart'; ...@@ -10,13 +10,14 @@ import 'package:flutter/services.dart';
final Random random = new Random(); final Random random = new Random();
Future<dynamic> handleGetRandom(Map<String, dynamic> message) async { final PlatformMethodChannel randomChannel = new PlatformMethodChannel('random');
final double min = message['min'].toDouble();
final double max = message['max'].toDouble();
return <String, double>{ Future<dynamic> handleGetRandom(MethodCall call) async {
'value': (random.nextDouble() * (max - min)) + min if (call.method == 'getRandom') {
}; final int min = call.arguments[0];
final int max = call.arguments[1];
return random.nextInt(max - min) + min;
}
} }
class HelloServices extends StatefulWidget { class HelloServices extends StatefulWidget {
...@@ -25,8 +26,8 @@ class HelloServices extends StatefulWidget { ...@@ -25,8 +26,8 @@ class HelloServices extends StatefulWidget {
} }
class _HelloServicesState extends State<HelloServices> { class _HelloServicesState extends State<HelloServices> {
double _latitude; static PlatformMethodChannel locationChannel = new PlatformMethodChannel('location');
double _longitude; String _location = 'Press button to get location';
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -38,32 +39,37 @@ class _HelloServicesState extends State<HelloServices> { ...@@ -38,32 +39,37 @@ class _HelloServicesState extends State<HelloServices> {
new Text('Hello from Flutter!'), new Text('Hello from Flutter!'),
new RaisedButton( new RaisedButton(
child: new Text('Get Location'), child: new Text('Get Location'),
onPressed: _getLocation onPressed: _getLocation,
), ),
new Text('Latitude: $_latitude, Longitude: $_longitude'), new Text(_location),
] ],
) ),
) ),
); );
} }
Future<Null> _getLocation() async { Future<Null> _getLocation() async {
final Map<String, String> message = <String, String>{'provider': 'network'}; String location;
final Map<String, dynamic> reply = await PlatformMessages.sendJSON('getLocation', message); try {
final List<double> reply = await locationChannel.invokeMethod(
'getLocation',
'network',
);
location = 'Latitude: ${reply[0]}, Longitude: ${reply[1]}';
} on PlatformException catch(e) {
location = 'Error: ' + e.message;
}
// If the widget was removed from the tree while the message was in flight, // If the widget was removed from the tree while the message was in flight,
// we want to discard the reply rather than calling setState to update our // we want to discard the reply rather than calling setState to update our
// non-existent appearance. // non-existent appearance.
if (!mounted) if (!mounted) return;
return;
setState(() { setState(() {
_latitude = reply['latitude'].toDouble(); _location = location;
_longitude = reply['longitude'].toDouble();
}); });
} }
} }
void main() { void main() {
runApp(new HelloServices()); runApp(new HelloServices());
randomChannel.setMethodCallHandler(handleGetRandom);
PlatformMessages.setJSONMessageHandler('getRandom', handleGetRandom);
} }
...@@ -26,6 +26,7 @@ export 'src/services/path_provider.dart'; ...@@ -26,6 +26,7 @@ export 'src/services/path_provider.dart';
export 'src/services/platform_channel.dart'; export 'src/services/platform_channel.dart';
export 'src/services/platform_messages.dart'; export 'src/services/platform_messages.dart';
export 'src/services/raw_keyboard.dart'; export 'src/services/raw_keyboard.dart';
export 'src/services/system_channels.dart';
export 'src/services/system_chrome.dart'; export 'src/services/system_chrome.dart';
export 'src/services/system_navigator.dart'; export 'src/services/system_navigator.dart';
export 'src/services/system_sound.dart'; export 'src/services/system_sound.dart';
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'platform_messages.dart'; import 'system_channels.dart';
/// Data stored on the system clipboard. /// Data stored on the system clipboard.
/// ///
...@@ -18,8 +18,6 @@ class ClipboardData { ...@@ -18,8 +18,6 @@ class ClipboardData {
final String text; final String text;
} }
const String _kChannelName = 'flutter/platform';
/// Utility methods for interacting with the system's clipboard. /// Utility methods for interacting with the system's clipboard.
class Clipboard { class Clipboard {
Clipboard._(); Clipboard._();
...@@ -33,12 +31,11 @@ class Clipboard { ...@@ -33,12 +31,11 @@ class Clipboard {
/// Stores the given clipboard data on the clipboard. /// Stores the given clipboard data on the clipboard.
static Future<Null> setData(ClipboardData data) async { static Future<Null> setData(ClipboardData data) async {
await PlatformMessages.invokeMethod( await SystemChannels.platform.invokeMethod(
_kChannelName,
'Clipboard.setData', 'Clipboard.setData',
<Map<String, dynamic>>[<String, dynamic>{ <String, dynamic>{
'text': data.text, 'text': data.text,
}], },
); );
} }
...@@ -50,10 +47,9 @@ class Clipboard { ...@@ -50,10 +47,9 @@ class Clipboard {
/// Returns a future which completes to null if the data could not be /// Returns a future which completes to null if the data could not be
/// obtained, and to a [ClipboardData] object if it could. /// obtained, and to a [ClipboardData] object if it could.
static Future<ClipboardData> getData(String format) async { static Future<ClipboardData> getData(String format) async {
final Map<String, dynamic> result = await PlatformMessages.invokeMethod( final Map<String, dynamic> result = await SystemChannels.platform.invokeMethod(
_kChannelName,
'Clipboard.getData', 'Clipboard.getData',
<String>[format] format,
); );
if (result == null) if (result == null)
return null; return null;
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'platform_messages.dart'; import 'system_channels.dart';
/// Allows access to the haptic feedback interface on the device. /// Allows access to the haptic feedback interface on the device.
/// ///
...@@ -21,6 +21,6 @@ class HapticFeedback { ...@@ -21,6 +21,6 @@ class HapticFeedback {
/// On Android, this uses the platform haptic feedback API to simulates a /// On Android, this uses the platform haptic feedback API to simulates a
/// short tap on a virtual keyboard. /// short tap on a virtual keyboard.
static Future<Null> vibrate() async { static Future<Null> vibrate() async {
await PlatformMessages.invokeMethod('flutter/platform', 'HapticFeedback.vibrate'); await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate');
} }
} }
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' show hashValues;
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
...@@ -26,6 +27,68 @@ abstract class MessageCodec<T> { ...@@ -26,6 +27,68 @@ abstract class MessageCodec<T> {
T decodeMessage(ByteData message); T decodeMessage(ByteData message);
} }
/// An command object representing the invocation of a named method.
class MethodCall {
/// Creates a [MethodCall] representing the invocation of [method] with the
/// specified [arguments].
MethodCall(this.method, [this.arguments]) {
assert(method != null);
}
/// The name of the method to be called.
final String method;
/// The arguments for the method.
///
/// Must be a valid value for the [MethodCodec] used.
final dynamic arguments;
@override
bool operator== (dynamic other) {
if (identical(this, other))
return true;
if (runtimeType != other.runtimeType)
return false;
return method == other.method && _deepEquals(arguments, other.arguments);
}
@override
int get hashCode => hashValues(method, arguments);
bool _deepEquals(dynamic a, dynamic b) {
if (a == b)
return true;
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 _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;
}
@override
String toString() => '$runtimeType($method, $arguments)';
}
/// 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 /// Result envelopes are binary messages with enough structure that the codec can
...@@ -43,15 +106,26 @@ abstract class MessageCodec<T> { ...@@ -43,15 +106,26 @@ abstract class MessageCodec<T> {
/// * [PlatformMethodChannel], which use [MethodCodec]s for communication /// * [PlatformMethodChannel], 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 method call in binary. /// Encodes the specified [methodCall] in binary.
/// ByteData encodeMethodCall(MethodCall methodCall);
/// The [name] of the method must be non-null. The [arguments] may be `null`.
ByteData encodeMethodCall(String name, dynamic arguments); /// Decodes the specified [methodCall] from binary.
MethodCall decodeMethodCall(ByteData methodCall);
/// Decodes the specified result [envelope] from binary. /// Decodes the specified result [envelope] from binary.
/// ///
/// Throws [PlatformException], if [envelope] represents an error. /// Throws [PlatformException], if [envelope] represents an error, otherwise
/// returns the enveloped result.
dynamic decodeEnvelope(ByteData envelope); dynamic decodeEnvelope(ByteData envelope);
/// Encodes a successful [result] into a binary envelope.
ByteData encodeSuccessEnvelope(dynamic result);
/// Encodes an error result into a binary envelope.
///
/// The specified error [code], human-readable error [message], and error
/// [details] correspond to the fields of [PlatformException].
ByteData encodeErrorEnvelope({@required String code, String message, dynamic details});
} }
......
...@@ -6,6 +6,7 @@ import 'dart:convert'; ...@@ -6,6 +6,7 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:meta/meta.dart';
import 'message_codec.dart'; import 'message_codec.dart';
...@@ -86,8 +87,8 @@ class JSONMethodCodec implements MethodCodec { ...@@ -86,8 +87,8 @@ class JSONMethodCodec implements MethodCodec {
// //
// * Individual values are serialized as defined by the JSON codec of the // * Individual values are serialized as defined by the JSON codec of the
// dart:convert package. // dart:convert package.
// * Method calls are serialized as two-element lists with the method name // * Method calls are serialized as two-element maps, with the method name
// string as first element and the method call arguments as the second. // keyed by 'method' and the arguments keyed by 'args'.
// * Reply envelopes are serialized as either: // * Reply envelopes are serialized as either:
// * one-element lists containing the successful result as its single // * one-element lists containing the successful result as its single
// element, or // element, or
...@@ -99,9 +100,23 @@ class JSONMethodCodec implements MethodCodec { ...@@ -99,9 +100,23 @@ class JSONMethodCodec implements MethodCodec {
const JSONMethodCodec(); const JSONMethodCodec();
@override @override
ByteData encodeMethodCall(String name, dynamic arguments) { ByteData encodeMethodCall(MethodCall call) {
assert(name != null); return const JSONMessageCodec().encodeMessage(<String, dynamic>{
return const JSONMessageCodec().encodeMessage(<dynamic>[name, arguments]); 'method': call.method,
'args': call.arguments,
});
}
@override
MethodCall decodeMethodCall(ByteData methodCall) {
final dynamic decoded = const JSONMessageCodec().decodeMessage(methodCall);
if (decoded is! Map)
throw new FormatException('Expected method call Map, got $decoded');
final dynamic method = decoded['method'];
final dynamic arguments = decoded['args'];
if (method is String)
return new MethodCall(method, arguments);
throw new FormatException('Invalid method call: $decoded');
} }
@override @override
...@@ -119,7 +134,18 @@ class JSONMethodCodec implements MethodCodec { ...@@ -119,7 +134,18 @@ class JSONMethodCodec implements MethodCodec {
message: decoded[1], message: decoded[1],
details: decoded[2], details: decoded[2],
); );
throw new FormatException('Invalid envelope $decoded'); throw new FormatException('Invalid envelope: $decoded');
}
@override
ByteData encodeSuccessEnvelope(dynamic result) {
return const JSONMessageCodec().encodeMessage(<dynamic>[result]);
}
@override
ByteData encodeErrorEnvelope({@required String code, String message, dynamic details}) {
assert(code != null);
return const JSONMessageCodec().encodeMessage(<dynamic>[code, message, details]);
} }
} }
...@@ -393,11 +419,42 @@ class StandardMethodCodec implements MethodCodec { ...@@ -393,11 +419,42 @@ class StandardMethodCodec implements MethodCodec {
const StandardMethodCodec(); const StandardMethodCodec();
@override @override
ByteData encodeMethodCall(String name, dynamic arguments) { ByteData encodeMethodCall(MethodCall call) {
assert(name != null); final WriteBuffer buffer = new WriteBuffer();
StandardMessageCodec._writeValue(buffer, call.method);
StandardMessageCodec._writeValue(buffer, call.arguments);
return buffer.done();
}
@override
MethodCall decodeMethodCall(ByteData methodCall) {
final ReadBuffer buffer = new ReadBuffer(methodCall);
final dynamic method = StandardMessageCodec._readValue(buffer);
final dynamic arguments = StandardMessageCodec._readValue(buffer);
if (method is String && !buffer.hasRemaining)
return new MethodCall(method, arguments);
else
throw const FormatException('Invalid method call');
}
@override
ByteData encodeSuccessEnvelope(dynamic result) {
final WriteBuffer buffer = new WriteBuffer();
buffer.putUint8(0);
StandardMessageCodec._writeValue(buffer, result);
return buffer.done();
}
@override
ByteData encodeErrorEnvelope({@required String code, String message, dynamic details}) {
final WriteBuffer buffer = new WriteBuffer(); final WriteBuffer buffer = new WriteBuffer();
StandardMessageCodec._writeValue(buffer, name); buffer.putUint8(1);
StandardMessageCodec._writeValue(buffer, arguments); StandardMessageCodec._writeValue(buffer, code);
StandardMessageCodec._writeValue(buffer, message);
StandardMessageCodec._writeValue(buffer, details);
return buffer.done(); return buffer.done();
} }
...@@ -412,7 +469,7 @@ class StandardMethodCodec implements MethodCodec { ...@@ -412,7 +469,7 @@ class StandardMethodCodec implements MethodCodec {
final dynamic errorCode = StandardMessageCodec._readValue(buffer); final dynamic errorCode = StandardMessageCodec._readValue(buffer);
final dynamic errorMessage = StandardMessageCodec._readValue(buffer); final dynamic errorMessage = StandardMessageCodec._readValue(buffer);
final dynamic errorDetails = StandardMessageCodec._readValue(buffer); final dynamic errorDetails = StandardMessageCodec._readValue(buffer);
if (errorCode is String && (errorMessage == null || errorMessage is String)) if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining)
throw new PlatformException(code: errorCode, message: errorMessage, details: errorDetails); throw new PlatformException(code: errorCode, message: errorMessage, details: errorDetails);
else else
throw const FormatException('Invalid envelope'); throw const FormatException('Invalid envelope');
......
...@@ -5,9 +5,7 @@ ...@@ -5,9 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'platform_messages.dart'; import 'system_channels.dart';
const String _kChannelName = 'flutter/platform';
/// Returns commonly used locations on the filesystem. /// Returns commonly used locations on the filesystem.
class PathProvider { class PathProvider {
...@@ -24,11 +22,10 @@ class PathProvider { ...@@ -24,11 +22,10 @@ class PathProvider {
/// ///
/// On Android, this uses the `getCacheDir` API on the context. /// On Android, this uses the `getCacheDir` API on the context.
static Future<Directory> getTemporaryDirectory() async { static Future<Directory> getTemporaryDirectory() async {
final Map<String, dynamic> result = await PlatformMessages.invokeMethod( final String path = await SystemChannels.platform.invokeMethod('PathProvider.getTemporaryDirectory');
_kChannelName, 'PathProvider.getTemporaryDirectory'); if (path == null)
if (result == null)
return null; return null;
return new Directory(result['path']); return new Directory(path);
} }
/// Path to a directory where the application may place files that are private /// Path to a directory where the application may place files that are private
...@@ -39,10 +36,9 @@ class PathProvider { ...@@ -39,10 +36,9 @@ class PathProvider {
/// ///
/// On Android, this returns the AppData directory. /// On Android, this returns the AppData directory.
static Future<Directory> getApplicationDocumentsDirectory() async { static Future<Directory> getApplicationDocumentsDirectory() async {
final Map<String, dynamic> result = await PlatformMessages.invokeMethod( final String path = await SystemChannels.platform.invokeMethod('PathProvider.getApplicationDocumentsDirectory');
_kChannelName, 'PathProvider.getApplicationDocumentsDirectory'); if (path == null)
if (result == null)
return null; return null;
return new Directory(result['path']); return new Directory(path);
} }
} }
...@@ -25,6 +25,8 @@ import 'platform_messages.dart'; ...@@ -25,6 +25,8 @@ import 'platform_messages.dart';
/// with may interfere with this channel's communication. Specifically, at most /// with may interfere with this channel's communication. Specifically, at most
/// one message handler can be registered with the channel name at any given /// one message handler can be registered with the channel name at any given
/// time. /// time.
///
/// See: <https://flutter.io/platform-services/>
class PlatformMessageChannel<T> { class PlatformMessageChannel<T> {
/// Creates a [PlatformMessageChannel] with the specified [name] and [codec]. /// Creates a [PlatformMessageChannel] with the specified [name] and [codec].
/// ///
...@@ -51,14 +53,19 @@ class PlatformMessageChannel<T> { ...@@ -51,14 +53,19 @@ class PlatformMessageChannel<T> {
/// channel. /// channel.
/// ///
/// The given callback will replace the currently registered callback for this /// The given callback will replace the currently registered callback for this
/// channel's name. /// 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 /// The handler's return value, if non-null, is sent back to the platform
/// plugins as a response. /// plugins as a response.
void setMessageHandler(Future<T> handler(T message)) { void setMessageHandler(Future<T> handler(T message)) {
PlatformMessages.setBinaryMessageHandler(name, (ByteData message) async { if (handler == null) {
return codec.encodeMessage(await handler(codec.decodeMessage(message))); PlatformMessages.setBinaryMessageHandler(name, null);
}); } else {
PlatformMessages.setBinaryMessageHandler(name, (ByteData message) async {
return codec.encodeMessage(await handler(codec.decodeMessage(message)));
});
}
} }
/// Sets a mock callback for intercepting messages sent on this channel. /// Sets a mock callback for intercepting messages sent on this channel.
...@@ -94,6 +101,8 @@ class PlatformMessageChannel<T> { ...@@ -94,6 +101,8 @@ class PlatformMessageChannel<T> {
/// ///
/// The identity of the channel is given by its name, so other uses of that name /// The identity of the channel is given by its name, so other uses of that name
/// with may interfere with this channel's communication. /// with may interfere with this channel's communication.
///
/// See: <https://flutter.io/platform-services/>
class PlatformMethodChannel { class PlatformMethodChannel {
/// Creates a [PlatformMethodChannel] with the specified [name]. /// Creates a [PlatformMethodChannel] with the specified [name].
/// ///
...@@ -120,10 +129,73 @@ class PlatformMethodChannel { ...@@ -120,10 +129,73 @@ class PlatformMethodChannel {
assert(method != null); assert(method != null);
return codec.decodeEnvelope(await PlatformMessages.sendBinary( return codec.decodeEnvelope(await PlatformMessages.sendBinary(
name, name,
codec.encodeMethodCall(method, arguments), codec.encodeMethodCall(new MethodCall(method, arguments)),
)); ));
} }
/// Sets a callback for receiving method calls on this channel.
///
/// The given callback will replace the currently registered callback for this
/// channel, if any. To remove the handler, pass `null` as the
/// `handler` argument.
///
/// If the future returned by the handler completes with a result, that value
/// is sent back to the platform plugin caller wrapped in a success envelope
/// as defined by the [codec] of this channel. If the future completes with
/// a [PlatformException], the fields of that exception will be used to
/// populate an error envelope which is sent back instead.
void setMethodCallHandler(Future<dynamic> handler(MethodCall call)) {
if (handler == null) {
PlatformMessages.setBinaryMessageHandler(name, null);
} else {
PlatformMessages.setBinaryMessageHandler(
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);
}
},
);
}
}
/// Sets a mock callback for intercepting method invocations on this channel.
///
/// 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.
///
/// If the future returned by the handler completes with a result, that value
/// is used as the result of the method invocation. If the future completes
/// with a [PlatformException], that will be thrown instead.
///
/// 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) {
PlatformMessages.setMockBinaryMessageHandler(name, null);
} else {
PlatformMessages.setMockBinaryMessageHandler(
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);
}
},
);
}
}
/// Sets up a broadcast stream for receiving events on this channel. /// Sets up a broadcast stream for receiving events on this channel.
/// ///
/// Returns a broadcast [Stream] which emits events to listeners as follows: /// Returns a broadcast [Stream] which emits events to listeners as follows:
......
...@@ -3,34 +3,22 @@ ...@@ -3,34 +3,22 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
ByteData _encodeUTF8(String message) {
if (message == null)
return null;
final Uint8List encoded = UTF8.encoder.convert(message);
return encoded.buffer.asByteData();
}
String _decodeUTF8(ByteData message) {
return message != null ? UTF8.decoder.convert(message.buffer.asUint8List()) : null;
}
String _encodeJSON(dynamic message) {
return message != null ? JSON.encode(message) : null;
}
dynamic _decodeJSON(String message) {
return message != null ? JSON.decode(message) : null;
}
typedef Future<ByteData> _PlatformMessageHandler(ByteData message); typedef Future<ByteData> _PlatformMessageHandler(ByteData message);
/// Sends message to and receives messages from platform plugins. /// Sends binary messages to and receives binary messages from platform plugins.
///
/// See also:
///
/// * [PlatformMessageChannel], which provides messaging services similar to
/// PlatformMessages, but with pluggable message codecs in support of sending
/// strings or semi-structured messages.
/// * [PlatformMethodChannel], which provides higher-level platform
/// communication such as method invocations and event streams.
/// ///
/// See: <https://flutter.io/platform-services/> /// See: <https://flutter.io/platform-services/>
class PlatformMessages { class PlatformMessages {
...@@ -97,91 +85,19 @@ class PlatformMessages { ...@@ -97,91 +85,19 @@ class PlatformMessages {
return _sendPlatformMessage(channel, message); return _sendPlatformMessage(channel, message);
} }
/// Send a string message to the platform plugins on the given channel.
///
/// The message is encoded as UTF-8.
///
/// Returns a [Future] which completes to the received response, decoded as a
/// UTF-8 string, or to an error, if the decoding fails.
///
/// Deprecated, use [PlatformMessageChannel.send] instead.
static Future<String> sendString(String channel, String message) async {
return _decodeUTF8(await sendBinary(channel, _encodeUTF8(message)));
}
/// Send a JSON-encoded message to the platform plugins on the given channel.
///
/// The message is encoded as JSON, then the JSON is encoded as UTF-8.
///
/// Returns a [Future] which completes to the received response, decoded as a
/// UTF-8-encoded JSON representation of a JSON value (a [String], [bool],
/// [double], [List], or [Map]), or to an error, if the decoding fails.
///
/// Deprecated, use [PlatformMessageChannel.send] instead.
static Future<dynamic> sendJSON(String channel, dynamic json) async {
return _decodeJSON(await sendString(channel, _encodeJSON(json)));
}
/// Send a method call to the platform plugins on the given channel.
///
/// Method calls are encoded as a JSON object with two keys, `method` with the
/// string given in the `method` argument, and `args` with the arguments given
/// in the `args` optional argument, as a JSON list. This JSON object is then
/// encoded as a UTF-8 string.
///
/// The response from the method call is decoded as UTF-8, then the UTF-8 is
/// decoded as JSON. The returned [Future] completes to this fully decoded
/// response, or to an error, if the decoding fails.
///
/// Deprecated, use [PlatformMethodChannel.invokeMethod] instead.
static Future<dynamic> invokeMethod(String channel, String method, [ List<dynamic> args = const <Null>[] ]) {
return sendJSON(channel, <String, dynamic>{
'method': method,
'args': args,
});
}
/// Set a callback for receiving messages from the platform plugins on the /// Set a callback for receiving messages from the platform plugins on the
/// given channel, without decoding them. /// given channel, without decoding them.
/// ///
/// The given callback will replace the currently registered callback for that /// The given callback will replace the currently registered callback for that
/// channel, if any. /// channel, if any. To remove the handler, pass `null` as the `handler`
/// argument.
/// ///
/// The handler's return value, if non-null, is sent as a response, unencoded. /// The handler's return value, if non-null, is sent as a response, unencoded.
static void setBinaryMessageHandler(String channel, Future<ByteData> handler(ByteData message)) { static void setBinaryMessageHandler(String channel, Future<ByteData> handler(ByteData message)) {
_handlers[channel] = handler; if (handler == null)
} _handlers.remove(channel);
else
/// Set a callback for receiving messages from the platform plugins on the _handlers[channel] = handler;
/// given channel, decoding the data as UTF-8.
///
/// The given callback will replace the currently registered callback for that
/// channel, if any.
///
/// The handler's return value, if non-null, is sent as a response, encoded as
/// a UTF-8 string.
///
/// Deprecated, use [PlatformMessageChannel.setMessageHandler] instead.
static void setStringMessageHandler(String channel, Future<String> handler(String message)) {
setBinaryMessageHandler(channel, (ByteData message) async {
return _encodeUTF8(await handler(_decodeUTF8(message)));
});
}
/// Set a callback for receiving messages from the platform plugins on the
/// given channel, decoding the data as UTF-8 JSON.
///
/// The given callback will replace the currently registered callback for that
/// channel, if any.
///
/// The handler's return value, if non-null, is sent as a response, encoded as
/// JSON and then as a UTF-8 string.
///
/// Deprecated, use [PlatformMessageChannel.setMessageHandler] instead.
static void setJSONMessageHandler(String channel, Future<dynamic> handler(dynamic message)) {
setStringMessageHandler(channel, (String message) async {
return _encodeJSON(await handler(_decodeJSON(message)));
});
} }
/// Set a mock callback for intercepting messages from the `send*` methods on /// Set a mock callback for intercepting messages from the `send*` methods on
...@@ -201,52 +117,4 @@ class PlatformMessages { ...@@ -201,52 +117,4 @@ class PlatformMessages {
else else
_mockHandlers[channel] = handler; _mockHandlers[channel] = handler;
} }
/// Set a mock callback for intercepting messages from the `send*` methods on
/// this class, on the given channel, decoding them as UTF-8.
///
/// The given callback will replace the currently registered mock callback for
/// that 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, encoded as
/// UTF-8.
///
/// This is intended for testing. Messages intercepted in this manner are not
/// sent to platform plugins.
///
/// Deprecated, use [PlatformMessageChannel.setMockMessageHandler] instead.
static void setMockStringMessageHandler(String channel, Future<String> handler(String message)) {
if (handler == null) {
setMockBinaryMessageHandler(channel, null);
} else {
setMockBinaryMessageHandler(channel, (ByteData message) async {
return _encodeUTF8(await handler(_decodeUTF8(message)));
});
}
}
/// Set a mock callback for intercepting messages from the `send*` methods on
/// this class, on the given channel, decoding them as UTF-8 JSON.
///
/// The given callback will replace the currently registered mock callback for
/// that 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, encoded as
/// UTF-8 JSON.
///
/// This is intended for testing. Messages intercepted in this manner are not
/// sent to platform plugins.
///
/// Deprecated, use [PlatformMessageChannel.setMockMessageHandler] instead.
static void setMockJSONMessageHandler(String channel, Future<dynamic> handler(dynamic message)) {
if (handler == null) {
setMockStringMessageHandler(channel, null);
} else {
setMockStringMessageHandler(channel, (String message) async {
return _encodeJSON(await handler(_decodeJSON(message)));
});
}
}
} }
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'platform_messages.dart'; import 'system_channels.dart';
/// Base class for platform specific key event data. /// Base class for platform specific key event data.
/// ///
...@@ -182,7 +182,7 @@ RawKeyEvent _toRawKeyEvent(Map<String, dynamic> message) { ...@@ -182,7 +182,7 @@ RawKeyEvent _toRawKeyEvent(Map<String, dynamic> message) {
/// * [RawKeyUpEvent] /// * [RawKeyUpEvent]
class RawKeyboard { class RawKeyboard {
RawKeyboard._() { RawKeyboard._() {
PlatformMessages.setJSONMessageHandler('flutter/keyevent', _handleKeyEvent); SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent);
} }
/// The shared instance of [RawKeyboard]. /// The shared instance of [RawKeyboard].
......
// 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 'platform_channel.dart';
import 'message_codecs.dart';
/// Platform channels used by the Flutter system.
class SystemChannels {
SystemChannels._();
/// A JSON [PlatformMethodChannel] for navigation.
static const PlatformMethodChannel navigation = const PlatformMethodChannel(
'flutter/navigation',
const JSONMethodCodec(),
);
/// A JSON [PlatformMethodChannel] for invoking miscellaneous platform methods.
static const PlatformMethodChannel platform = const PlatformMethodChannel(
'flutter/platform',
const JSONMethodCodec(),
);
/// A JSON [PlatformMethodChannel] for handling text input.
static const PlatformMethodChannel textInput = const PlatformMethodChannel(
'flutter/textinput',
const JSONMethodCodec(),
);
/// A JSON [PlatformMessageChannel] for key events.
static const PlatformMessageChannel<dynamic> keyEvent = const PlatformMessageChannel<dynamic>(
'flutter/keyevent',
const JSONMessageCodec(),
);
/// A string [PlatformMessageChannel] for lifecycle events.
static const PlatformMessageChannel<String> lifecycle = const PlatformMessageChannel<String>(
'flutter/lifecycle',
const StringCodec(),
);
/// A JSON [PlatformMessageChannel] for system events.
static const PlatformMessageChannel<dynamic> system = const PlatformMessageChannel<dynamic>(
'flutter/system',
const JSONMessageCodec(),
);
}
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'platform_messages.dart'; import 'system_channels.dart';
/// Specifies a particular device orientation. /// Specifies a particular device orientation.
/// ///
...@@ -87,8 +87,6 @@ enum SystemUiOverlayStyle { ...@@ -87,8 +87,6 @@ enum SystemUiOverlayStyle {
dark, dark,
} }
const String _kChannelName = 'flutter/platform';
List<String> _stringify(List<dynamic> list) { List<String> _stringify(List<dynamic> list) {
final List<String> result = <String>[]; final List<String> result = <String>[];
for (dynamic item in list) for (dynamic item in list)
...@@ -107,10 +105,9 @@ class SystemChrome { ...@@ -107,10 +105,9 @@ class SystemChrome {
/// The `orientation` argument is a list of [DeviceOrientation] enum values. /// The `orientation` argument is a list of [DeviceOrientation] enum values.
/// The empty list is synonymous with having all options enabled. /// The empty list is synonymous with having all options enabled.
static Future<Null> setPreferredOrientations(List<DeviceOrientation> orientations) async { static Future<Null> setPreferredOrientations(List<DeviceOrientation> orientations) async {
await PlatformMessages.invokeMethod( await SystemChannels.platform.invokeMethod(
_kChannelName,
'SystemChrome.setPreferredOrientations', 'SystemChrome.setPreferredOrientations',
<List<String>>[ _stringify(orientations) ], _stringify(orientations),
); );
} }
...@@ -120,13 +117,12 @@ class SystemChrome { ...@@ -120,13 +117,12 @@ class SystemChrome {
/// Any part of the description that is unsupported on the current platform /// Any part of the description that is unsupported on the current platform
/// will be ignored. /// will be ignored.
static Future<Null> setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async { static Future<Null> setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async {
await PlatformMessages.invokeMethod( await SystemChannels.platform.invokeMethod(
_kChannelName,
'SystemChrome.setApplicationSwitcherDescription', 'SystemChrome.setApplicationSwitcherDescription',
<Map<String, dynamic>>[<String, dynamic>{ <String, dynamic>{
'label': description.label, 'label': description.label,
'primaryColor': description.primaryColor, 'primaryColor': description.primaryColor,
}], },
); );
} }
...@@ -139,10 +135,9 @@ class SystemChrome { ...@@ -139,10 +135,9 @@ class SystemChrome {
/// If a particular overlay is unsupported on the platform, enabling or /// If a particular overlay is unsupported on the platform, enabling or
/// disabling that overlay will be ignored. /// disabling that overlay will be ignored.
static Future<Null> setEnabledSystemUIOverlays(List<SystemUiOverlay> overlays) async { static Future<Null> setEnabledSystemUIOverlays(List<SystemUiOverlay> overlays) async {
await PlatformMessages.invokeMethod( await SystemChannels.platform.invokeMethod(
_kChannelName,
'SystemChrome.setEnabledSystemUIOverlays', 'SystemChrome.setEnabledSystemUIOverlays',
<List<String>>[ _stringify(overlays) ], _stringify(overlays),
); );
} }
...@@ -175,10 +170,9 @@ class SystemChrome { ...@@ -175,10 +170,9 @@ class SystemChrome {
scheduleMicrotask(() { scheduleMicrotask(() {
assert(_pendingStyle != null); assert(_pendingStyle != null);
if (_pendingStyle != _latestStyle) { if (_pendingStyle != _latestStyle) {
PlatformMessages.invokeMethod( SystemChannels.platform.invokeMethod(
_kChannelName,
'SystemChrome.setSystemUIOverlayStyle', 'SystemChrome.setSystemUIOverlayStyle',
<String>[ _pendingStyle.toString() ], _pendingStyle.toString(),
); );
_latestStyle = _pendingStyle; _latestStyle = _pendingStyle;
} }
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'platform_messages.dart'; import 'system_channels.dart';
/// Controls specific aspects of the system navigation stack. /// Controls specific aspects of the system navigation stack.
class SystemNavigator { class SystemNavigator {
...@@ -20,6 +20,6 @@ class SystemNavigator { ...@@ -20,6 +20,6 @@ class SystemNavigator {
/// the latter may cause the underlying platform to act as if the application /// the latter may cause the underlying platform to act as if the application
/// had crashed. /// had crashed.
static Future<Null> pop() async { static Future<Null> pop() async {
await PlatformMessages.invokeMethod('flutter/platform', 'SystemNavigator.pop'); await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
} }
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'platform_messages.dart'; import 'system_channels.dart';
/// A sound provided by the system. /// A sound provided by the system.
enum SystemSoundType { enum SystemSoundType {
...@@ -20,10 +20,9 @@ class SystemSound { ...@@ -20,10 +20,9 @@ class SystemSound {
/// Play the specified system sound. If that sound is not present on the /// Play the specified system sound. If that sound is not present on the
/// system, the call is ignored. /// system, the call is ignored.
static Future<Null> play(SystemSoundType type) async { static Future<Null> play(SystemSoundType type) async {
await PlatformMessages.invokeMethod( await SystemChannels.platform.invokeMethod(
'flutter/platform',
'SystemSound.play', 'SystemSound.play',
<String>[ type.toString() ], type.toString(),
); );
} }
} }
...@@ -7,7 +7,8 @@ import 'dart:ui' show TextAffinity; ...@@ -7,7 +7,8 @@ import 'dart:ui' show TextAffinity;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'platform_messages.dart'; import 'system_channels.dart';
import 'message_codec.dart';
export 'dart:ui' show TextAffinity; export 'dart:ui' show TextAffinity;
...@@ -172,8 +173,6 @@ abstract class TextInputClient { ...@@ -172,8 +173,6 @@ abstract class TextInputClient {
void performAction(TextInputAction action); void performAction(TextInputAction action);
} }
const String _kChannelName = 'flutter/textinput';
/// A interface for interacting with a text input control. /// A interface for interacting with a text input control.
/// ///
/// See also: /// See also:
...@@ -195,16 +194,15 @@ class TextInputConnection { ...@@ -195,16 +194,15 @@ class TextInputConnection {
/// Requests that the text input control become visible. /// Requests that the text input control become visible.
void show() { void show() {
assert(attached); assert(attached);
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.show'); SystemChannels.textInput.invokeMethod('TextInput.show');
} }
/// Requests that the text input control change its internal state to match the given state. /// Requests that the text input control change its internal state to match the given state.
void setEditingState(TextEditingState state) { void setEditingState(TextEditingState state) {
assert(attached); assert(attached);
PlatformMessages.invokeMethod( SystemChannels.textInput.invokeMethod(
_kChannelName,
'TextInput.setEditingState', 'TextInput.setEditingState',
<dynamic>[ state.toJSON() ], state.toJSON(),
); );
} }
...@@ -214,7 +212,7 @@ class TextInputConnection { ...@@ -214,7 +212,7 @@ class TextInputConnection {
/// other client attaches to it within this animation frame. /// other client attaches to it within this animation frame.
void close() { void close() {
if (attached) { if (attached) {
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.clearClient'); SystemChannels.textInput.invokeMethod('TextInput.clearClient');
_clientHandler _clientHandler
.._currentConnection = null .._currentConnection = null
.._scheduleHide(); .._scheduleHide();
...@@ -233,16 +231,16 @@ TextInputAction _toTextInputAction(String action) { ...@@ -233,16 +231,16 @@ TextInputAction _toTextInputAction(String action) {
class _TextInputClientHandler { class _TextInputClientHandler {
_TextInputClientHandler() { _TextInputClientHandler() {
PlatformMessages.setJSONMessageHandler('flutter/textinputclient', _handleMessage); SystemChannels.textInput.setMethodCallHandler(_handleTextInputInvocation);
} }
TextInputConnection _currentConnection; TextInputConnection _currentConnection;
Future<Null> _handleMessage(dynamic message) async { Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
if (_currentConnection == null) if (_currentConnection == null)
return; return;
final String method = message['method']; final String method = methodCall.method;
final List<dynamic> args = message['args']; final List<dynamic> args = methodCall.arguments;
final int client = args[0]; final int client = args[0];
// The incoming message was for a different client. // The incoming message was for a different client.
if (client != _currentConnection._id) if (client != _currentConnection._id)
...@@ -270,7 +268,7 @@ class _TextInputClientHandler { ...@@ -270,7 +268,7 @@ class _TextInputClientHandler {
scheduleMicrotask(() { scheduleMicrotask(() {
_hidePending = false; _hidePending = false;
if (_currentConnection == null) if (_currentConnection == null)
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.hide'); SystemChannels.textInput.invokeMethod('TextInput.hide');
}); });
} }
} }
...@@ -296,8 +294,7 @@ class TextInput { ...@@ -296,8 +294,7 @@ class TextInput {
assert(configuration != null); assert(configuration != null);
final TextInputConnection connection = new TextInputConnection._(client); final TextInputConnection connection = new TextInputConnection._(client);
_clientHandler._currentConnection = connection; _clientHandler._currentConnection = connection;
PlatformMessages.invokeMethod( SystemChannels.textInput.invokeMethod(
_kChannelName,
'TextInput.setClient', 'TextInput.setClient',
<dynamic>[ connection._id, configuration.toJSON() ], <dynamic>[ connection._id, configuration.toJSON() ],
); );
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'platform_messages.dart'; import 'system_channels.dart';
/// Allows applications to delegate responsbility of handling certain URLs to /// Allows applications to delegate responsbility of handling certain URLs to
/// the underlying platform. /// the underlying platform.
...@@ -14,10 +14,9 @@ class UrlLauncher { ...@@ -14,10 +14,9 @@ class UrlLauncher {
/// Parse the specified URL string and delegate handling of the same to the /// Parse the specified URL string and delegate handling of the same to the
/// underlying platform. /// underlying platform.
static Future<Null> launch(String urlString) async { static Future<Null> launch(String urlString) async {
await PlatformMessages.invokeMethod( await SystemChannels.platform.invokeMethod(
'flutter/platform',
'UrlLauncher.launch', 'UrlLauncher.launch',
<String>[ urlString ], urlString,
); );
} }
} }
...@@ -61,9 +61,9 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren ...@@ -61,9 +61,9 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
_instance = this; _instance = this;
buildOwner.onBuildScheduled = _handleBuildScheduled; buildOwner.onBuildScheduled = _handleBuildScheduled;
ui.window.onLocaleChanged = handleLocaleChanged; ui.window.onLocaleChanged = handleLocaleChanged;
PlatformMessages.setJSONMessageHandler('flutter/navigation', _handleNavigationMessage); SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
PlatformMessages.setStringMessageHandler('flutter/lifecycle', _handleLifecycleMessage); SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
PlatformMessages.setJSONMessageHandler('flutter/system', _handleSystemMessage); SystemChannels.system.setMessageHandler(_handleSystemMessage);
} }
/// The current [WidgetsBinding], if one has been created. /// The current [WidgetsBinding], if one has been created.
...@@ -189,9 +189,8 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren ...@@ -189,9 +189,8 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
SystemNavigator.pop(); SystemNavigator.pop();
} }
Future<dynamic> _handleNavigationMessage(Map<String, dynamic> message) async { Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) async {
final String method = message['method']; if (methodCall.method == 'popRoute')
if (method == 'popRoute')
handlePopRoute(); handlePopRoute();
// TODO(abarth): Handle 'pushRoute'. // TODO(abarth): Handle 'pushRoute'.
} }
......
...@@ -275,8 +275,8 @@ void main() { ...@@ -275,8 +275,8 @@ void main() {
int hapticFeedbackCount; int hapticFeedbackCount;
setUpAll(() { setUpAll(() {
PlatformMessages.setMockJSONMessageHandler('flutter/platform', (dynamic message) { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
if (message['method'] == "HapticFeedback.vibrate") if (methodCall.method == "HapticFeedback.vibrate")
hapticFeedbackCount++; hapticFeedbackCount++;
}); });
}); });
......
...@@ -118,8 +118,8 @@ void main() { ...@@ -118,8 +118,8 @@ void main() {
int hapticFeedbackCount; int hapticFeedbackCount;
setUpAll(() { setUpAll(() {
PlatformMessages.setMockJSONMessageHandler('flutter/platform', (dynamic message) { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) {
if (message['method'] == "HapticFeedback.vibrate") if (methodCall.method == "HapticFeedback.vibrate")
hapticFeedbackCount++; hapticFeedbackCount++;
}); });
}); });
......
...@@ -7,14 +7,14 @@ import 'package:test/test.dart'; ...@@ -7,14 +7,14 @@ import 'package:test/test.dart';
void main() { void main() {
test('Haptic feedback control test', () async { test('Haptic feedback control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await HapticFeedback.vibrate(); await HapticFeedback.vibrate();
expect(log, equals(<String>['{"method":"HapticFeedback.vibrate","args":[]}'])); expect(log, equals(<MethodCall>[new MethodCall('HapticFeedback.vibrate')]));
}); });
} }
...@@ -9,27 +9,27 @@ import 'package:test/test.dart'; ...@@ -9,27 +9,27 @@ import 'package:test/test.dart';
void main() { void main() {
test('Path provider control test', () async { test('Path provider control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
String response; String response;
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
return response; return response;
}); });
Directory directory = await PathProvider.getTemporaryDirectory(); Directory directory = await PathProvider.getTemporaryDirectory();
expect(log, equals(<String>['{"method":"PathProvider.getTemporaryDirectory","args":[]}'])); expect(log, equals(<MethodCall>[new MethodCall('PathProvider.getTemporaryDirectory')]));
expect(directory, isNull); expect(directory, isNull);
log.clear(); log.clear();
directory = await PathProvider.getApplicationDocumentsDirectory(); directory = await PathProvider.getApplicationDocumentsDirectory();
expect(log, equals(<String>['{"method":"PathProvider.getApplicationDocumentsDirectory","args":[]}'])); expect(log, equals(<MethodCall>[new MethodCall('PathProvider.getApplicationDocumentsDirectory')]));
expect(directory, isNull); expect(directory, isNull);
final String fakePath = "/foo/bar/baz"; final String fakePath = "/foo/bar/baz";
response = '{"path":"$fakePath"}'; response = fakePath;
directory = await PathProvider.getTemporaryDirectory(); directory = await PathProvider.getTemporaryDirectory();
expect(directory.path, equals(fakePath)); expect(directory.path, equals(fakePath));
......
...@@ -42,9 +42,9 @@ void main() { ...@@ -42,9 +42,9 @@ void main() {
PlatformMessages.setMockBinaryMessageHandler( PlatformMessages.setMockBinaryMessageHandler(
'ch', 'ch',
(ByteData message) async { (ByteData message) async {
final List<dynamic> methodCall = jsonMessage.decodeMessage(message); final Map<dynamic, dynamic> methodCall = jsonMessage.decodeMessage(message);
if (methodCall[0] == 'sayHello') if (methodCall['method'] == 'sayHello')
return jsonMessage.encodeMessage(<dynamic>['${methodCall[1]} world']); return jsonMessage.encodeMessage(<dynamic>['${methodCall['args']} world']);
else else
return jsonMessage.encodeMessage(<dynamic>['unknown', null, null]); return jsonMessage.encodeMessage(<dynamic>['unknown', null, null]);
}, },
...@@ -84,14 +84,14 @@ void main() { ...@@ -84,14 +84,14 @@ void main() {
PlatformMessages.setMockBinaryMessageHandler( PlatformMessages.setMockBinaryMessageHandler(
'ch', 'ch',
(ByteData message) async { (ByteData message) async {
final List<dynamic> methodCall = jsonMessage.decodeMessage(message); final Map<dynamic, dynamic> methodCall = jsonMessage.decodeMessage(message);
if (methodCall[0] == 'listen') { if (methodCall['method'] == 'listen') {
final String argument = methodCall[1]; final String argument = methodCall['args'];
emitEvent(jsonMessage.encodeMessage(<dynamic>[argument + '1'])); emitEvent(jsonMessage.encodeMessage(<dynamic>[argument + '1']));
emitEvent(jsonMessage.encodeMessage(<dynamic>[argument + '2'])); emitEvent(jsonMessage.encodeMessage(<dynamic>[argument + '2']));
emitEvent(null); emitEvent(null);
return jsonMessage.encodeMessage(<dynamic>[null]); return jsonMessage.encodeMessage(<dynamic>[null]);
} else if (methodCall[0] == 'cancel') { } else if (methodCall['method'] == 'cancel') {
cancelled = true; cancelled = true;
return jsonMessage.encodeMessage(<dynamic>[null]); return jsonMessage.encodeMessage(<dynamic>[null]);
} else { } else {
......
...@@ -2,39 +2,26 @@ ...@@ -2,39 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
void main() { void main() {
test('Mock string message handler control test', () async { test('Mock binary message handler control test', () async {
final List<String> log = <String>[]; final List<ByteData> log = <ByteData>[];
PlatformMessages.setMockStringMessageHandler('test1', (String message) async {
log.add(message);
});
await PlatformMessages.sendString('test1', 'hello');
expect(log, equals(<String>['hello']));
log.clear();
PlatformMessages.setMockStringMessageHandler('test1', null);
await PlatformMessages.sendString('test1', 'fail');
expect(log, isEmpty);
});
test('Mock JSON message handler control test', () async {
final List<dynamic> log = <dynamic>[];
PlatformMessages.setMockJSONMessageHandler('test2', (dynamic message) async { PlatformMessages.setMockBinaryMessageHandler('test1', (ByteData message) async {
log.add(message); log.add(message);
}); });
await PlatformMessages.sendString('test2', '{"hello": "world"}'); final ByteData message = new ByteData(2)..setUint16(0, 0xABCD);
expect(log, equals(<Map<String, String>>[<String, String>{'hello': 'world'}])); await PlatformMessages.sendBinary('test1', message);
expect(log, equals(<ByteData>[message]));
log.clear(); log.clear();
PlatformMessages.setMockStringMessageHandler('test2', null); PlatformMessages.setMockBinaryMessageHandler('test1', null);
await PlatformMessages.sendString('test2', '{"fail": "message"}'); await PlatformMessages.sendBinary('test1', message);
expect(log, isEmpty); expect(log, isEmpty);
}); });
} }
...@@ -21,42 +21,51 @@ void main() { ...@@ -21,42 +21,51 @@ void main() {
}); });
test('setPreferredOrientations control test', () async { test('setPreferredOrientations control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await SystemChrome.setPreferredOrientations(<DeviceOrientation>[ await SystemChrome.setPreferredOrientations(<DeviceOrientation>[
DeviceOrientation.portraitUp, DeviceOrientation.portraitUp,
]); ]);
expect(log, equals(<String>['{"method":"SystemChrome.setPreferredOrientations","args":[["DeviceOrientation.portraitUp"]]}'])); expect(log, equals(<MethodCall>[new MethodCall(
'SystemChrome.setPreferredOrientations',
<String>["DeviceOrientation.portraitUp"],
)]));
}); });
test('setApplicationSwitcherDescription control test', () async { test('setApplicationSwitcherDescription control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await SystemChrome.setApplicationSwitcherDescription( await SystemChrome.setApplicationSwitcherDescription(
const ApplicationSwitcherDescription(label: 'Example label', primaryColor: 0xFF00FF00) const ApplicationSwitcherDescription(label: 'Example label', primaryColor: 0xFF00FF00)
); );
expect(log, equals(<String>['{"method":"SystemChrome.setApplicationSwitcherDescription","args":[{"label":"Example label","primaryColor":4278255360}]}'])); expect(log, equals(<MethodCall>[new MethodCall(
'SystemChrome.setApplicationSwitcherDescription',
<String, dynamic>{"label":"Example label","primaryColor":4278255360}
)]));
}); });
test('setEnabledSystemUIOverlays control test', () async { test('setEnabledSystemUIOverlays control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[SystemUiOverlay.top]); await SystemChrome.setEnabledSystemUIOverlays(<SystemUiOverlay>[SystemUiOverlay.top]);
expect(log, equals(<String>['{"method":"SystemChrome.setEnabledSystemUIOverlays","args":[["SystemUiOverlay.top"]]}'])); expect(log, equals(<MethodCall>[new MethodCall(
'SystemChrome.setEnabledSystemUIOverlays',
<String>["SystemUiOverlay.top"],
)]));
}); });
} }
...@@ -7,14 +7,14 @@ import 'package:test/test.dart'; ...@@ -7,14 +7,14 @@ import 'package:test/test.dart';
void main() { void main() {
test('System navigator control test', () async { test('System navigator control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await SystemNavigator.pop(); await SystemNavigator.pop();
expect(log, equals(<String>['{"method":"SystemNavigator.pop","args":[]}'])); expect(log, equals(<MethodCall>[new MethodCall('SystemNavigator.pop')]));
}); });
} }
...@@ -7,14 +7,14 @@ import 'package:test/test.dart'; ...@@ -7,14 +7,14 @@ import 'package:test/test.dart';
void main() { void main() {
test('System sound control test', () async { test('System sound control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await SystemSound.play(SystemSoundType.click); await SystemSound.play(SystemSoundType.click);
expect(log, equals(<String>['{"method":"SystemSound.play","args":["SystemSoundType.click"]}'])); expect(log, equals(<MethodCall>[new MethodCall('SystemSound.play', "SystemSoundType.click")]));
}); });
} }
...@@ -7,14 +7,14 @@ import 'package:test/test.dart'; ...@@ -7,14 +7,14 @@ import 'package:test/test.dart';
void main() { void main() {
test('URL launcher control test', () async { test('URL launcher control test', () async {
final List<String> log = <String>[]; final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(message); log.add(methodCall);
}); });
await UrlLauncher.launch('http://example.com/'); await UrlLauncher.launch('http://example.com/');
expect(log, equals(<String>['{"method":"UrlLauncher.launch","args":["http://example.com/"]}'])); expect(log, equals(<MethodCall>[new MethodCall('UrlLauncher.launch', 'http://example.com/')]));
}); });
} }
...@@ -15,14 +15,12 @@ class MockClipboard { ...@@ -15,14 +15,12 @@ class MockClipboard {
'text': null 'text': null
}; };
Future<dynamic> handleJSONMessage(dynamic message) async { Future<dynamic> handleMethodCall(MethodCall methodCall) async {
final String method = message['method']; switch (methodCall.method) {
final List<dynamic> args= message['args'];
switch (method) {
case 'Clipboard.getData': case 'Clipboard.getData':
return _clipboardData; return _clipboardData;
case 'Clipboard.setData': case 'Clipboard.setData':
_clipboardData = args[0]; _clipboardData = methodCall.arguments;
break; break;
} }
} }
...@@ -40,7 +38,7 @@ Widget overlay(Widget child) { ...@@ -40,7 +38,7 @@ Widget overlay(Widget child) {
void main() { void main() {
final MockClipboard mockClipboard = new MockClipboard(); final MockClipboard mockClipboard = new MockClipboard();
PlatformMessages.setMockJSONMessageHandler('flutter/platform', mockClipboard.handleJSONMessage); SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
const String kThreeLines = const String kThreeLines =
'First line of text is here abcdef ghijkl mnopqrst. ' + 'First line of text is here abcdef ghijkl mnopqrst. ' +
......
...@@ -2,18 +2,15 @@ ...@@ -2,18 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void sendFakeKeyEvent(Map<String, dynamic> data) { void sendFakeKeyEvent(Map<String, dynamic> data) {
final String message = JSON.encode(data);
final Uint8List encoded = UTF8.encoder.convert(message);
PlatformMessages.handlePlatformMessage( PlatformMessages.handlePlatformMessage(
'flutter/keyevent', encoded.buffer.asByteData(), (_) {}); SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(_) {});
} }
void main() { void main() {
......
...@@ -3,8 +3,6 @@ ...@@ -3,8 +3,6 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
...@@ -23,34 +21,35 @@ const String _kTextInputClientChannel = 'flutter/textinputclient'; ...@@ -23,34 +21,35 @@ const String _kTextInputClientChannel = 'flutter/textinputclient';
/// popup keyboard and initializing its text. /// popup keyboard and initializing its text.
class TestTextInput { class TestTextInput {
void register() { void register() {
PlatformMessages.setMockJSONMessageHandler('flutter/textinput', handleJSONMessage); SystemChannels.textInput.setMockMethodCallHandler(handleTextInputCall);
} }
int _client = 0; int _client = 0;
Map<String, dynamic> editingState; Map<String, dynamic> editingState;
Future<dynamic> handleJSONMessage(dynamic message) async { Future<dynamic> handleTextInputCall(MethodCall methodCall) async {
final String method = message['method']; switch (methodCall.method) {
final List<dynamic> args= message['args'];
switch (method) {
case 'TextInput.setClient': case 'TextInput.setClient':
_client = args[0]; _client = methodCall.arguments[0];
break; break;
case 'TextInput.setEditingState': case 'TextInput.setEditingState':
editingState = args[0]; editingState = methodCall.arguments;
break; break;
} }
} }
void updateEditingState(TextEditingState state) { void updateEditingState(TextEditingState state) {
expect(_client, isNonZero); expect(_client, isNonZero);
final String message = JSON.encode(<String, dynamic>{
'method': 'TextInputClient.updateEditingState',
'args': <dynamic>[_client, state.toJSON()],
});
final Uint8List encoded = UTF8.encoder.convert(message);
PlatformMessages.handlePlatformMessage( PlatformMessages.handlePlatformMessage(
_kTextInputClientChannel, encoded.buffer.asByteData(), (_) {}); SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall(
new MethodCall(
'TextInputClient.updateEditingState',
<dynamic>[_client, state.toJSON()],
),
),
(_) {},
);
} }
void enterText(String text) { void enterText(String text) {
......
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