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;
import android.app.Instrumentation;
import android.graphics.Bitmap;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
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 android.support.test.InstrumentationRegistry;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
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)
public class ExampleInstrumentedTest {
@Rule
public ActivityTestRule<ExampleActivity> activityRule =
new ActivityTestRule<>(ExampleActivity.class);
new ActivityTestRule<>(ExampleActivity.class);
@Test
public void testFlutterMessage() {
final Instrumentation instr = InstrumentationRegistry.getInstrumentation();
final JSONObject message = new JSONObject();
final int RANDOM_MIN = 1;
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 AtomicInteger random = new AtomicInteger();
......@@ -46,17 +41,18 @@ public class ExampleInstrumentedTest {
instr.runOnMainSync(new Runnable() {
public void run() {
final FlutterView flutterView = (FlutterView) activityRule.getActivity().findViewById(
R.id.flutter_view);
flutterView.sendToFlutter("getRandom", message.toString(), new FlutterView.MessageReplyCallback() {
public void onReply(String json) {
try {
JSONObject reply = new JSONObject(json);
random.set(reply.getInt("value"));
} catch (JSONException e) {
fail(e.getMessage());
} finally {
latch.countDown();
}
R.id.flutter_view);
final FlutterMethodChannel randomChannel = new FlutterMethodChannel(flutterView, "random");
randomChannel.invokeMethod("getRandom", Arrays.asList(RANDOM_MIN, RANDOM_MAX), new FlutterMethodChannel.Response() {
@Override
public void success(Object o) {
random.set(((Number) o).intValue());
latch.countDown();
}
@Override
public void error(String code, String message, Object details) {
}
});
}
......@@ -78,7 +74,7 @@ public class ExampleInstrumentedTest {
instr.runOnMainSync(new Runnable() {
public void run() {
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
// is paused in the test environment.
......@@ -105,13 +101,23 @@ public class ExampleInstrumentedTest {
// Waits on a FlutterView until it is able to produce a bitmap.
private class BitmapPoller {
private final int delayMsec = 1000;
private int triesPending;
private int waitMsec;
private FlutterView flutterView;
private Bitmap bitmap;
private CountDownLatch latch = new CountDownLatch(1);
private final int delayMsec = 1000;
private Runnable checkBitmap = new Runnable() {
public void run() {
bitmap = flutterView.getBitmap();
triesPending--;
if (bitmap != null || triesPending == 0) {
latch.countDown();
} else {
flutterView.postDelayed(checkBitmap, delayMsec);
}
}
};
BitmapPoller(int tries) {
triesPending = tries;
......@@ -127,17 +133,5 @@ public class ExampleInstrumentedTest {
latch.await(waitMsec, TimeUnit.MILLISECONDS);
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;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
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.FlutterView;
import java.io.File;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
public class ExampleActivity extends Activity {
private static final String TAG = "ExampleActivity";
private FlutterView flutterView;
private FlutterMethodChannel randomChannel;
@Override
public void onCreate(Bundle savedInstanceState) {
......@@ -39,20 +37,24 @@ public class ExampleActivity extends Activity {
flutterView = (FlutterView) findViewById(R.id.flutter_view);
flutterView.runFromBundle(FlutterMain.findAppBundlePath(getApplicationContext()), null);
flutterView.addOnMessageListener("getLocation",
new FlutterView.OnMessageListener() {
@Override
public String onMessage(FlutterView view, String message) {
return onGetLocation(message);
FlutterMethodChannel locationChannel = new FlutterMethodChannel(flutterView, "location");
randomChannel = new FlutterMethodChannel(flutterView, "random");
locationChannel.setMethodCallHandler(new FlutterMethodChannel.MethodCallHandler() {
@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);
getRandom.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendGetRandom();
}
public void onClick(View v) { getRandom(); }
});
}
......@@ -76,79 +78,44 @@ public class ExampleActivity extends Activity {
flutterView.onPostResume();
}
private void sendGetRandom() {
JSONObject message = new JSONObject();
try {
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 getRandom() {
randomChannel.invokeMethod("getRandom", Arrays.asList(1, 1000), new FlutterMethodChannel.Response() {
TextView textView = (TextView) findViewById(R.id.random_value);
private void onRandomReply(String json) {
double value;
try {
JSONObject reply = new JSONObject(json);
value = reply.getDouble("value");
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return;
}
@Override
public void success(Object result) {
textView.setText(result.toString());
}
TextView randomValue = (TextView) findViewById(R.id.random_value);
randomValue.setText(Double.toString(value));
@Override
public void error(String code, String message, Object details) {
textView.setText("Error: " + message);
}
});
}
private String onGetLocation(String json) {
String provider;
try {
JSONObject message = new JSONObject(json);
provider = message.getString("provider");
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return null;
}
private void getLocation(String provider, FlutterMethodChannel.Response response) {
String locationProvider;
if (provider.equals("network")) {
locationProvider = LocationManager.NETWORK_PROVIDER;
} else if (provider.equals("gps")) {
locationProvider = LocationManager.GPS_PROVIDER;
} else {
return null;
response.error("unknown provider", "Unknown location provider: " + provider, null);
return;
}
String permission = "android.permission.ACCESS_FINE_LOCATION";
Location location = null;
if (checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) {
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
location = locationManager.getLastKnownLocation(locationProvider);
}
JSONObject reply = new JSONObject();
try {
Location location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
reply.put("latitude", location.getLatitude());
reply.put("longitude", location.getLongitude());
response.success(Arrays.asList(location.getLatitude(), location.getLongitude()));
} else {
reply.put("latitude", 0);
reply.put("longitude", 0);
response.error("location unavailable", "Location is not available", null);
}
} catch (JSONException e) {
Log.e(TAG, "JSON exception", e);
return null;
} else {
response.error("access error", "Location permissions not granted", null);
}
return reply.toString();
}
}
......@@ -13,7 +13,6 @@
9740EEB41CF90195004384FC /* Flutter.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Flutter.xcconfig */; };
9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; };
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 */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
......@@ -42,8 +41,6 @@
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>"; };
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>"; };
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; };
......@@ -105,8 +102,6 @@
97C146F11CF9000F007C117D /* Supporting Files */,
97A38A331CFDEC680099F1B4 /* AppDelegate.h */,
97A38A341CFDEC880099F1B4 /* AppDelegate.m */,
977505171CFDF21E00BC28DA /* LocationProvider.h */,
977505181CFDF23500BC28DA /* LocationProvider.m */,
);
path = Runner;
sourceTree = "<group>";
......
......@@ -5,24 +5,34 @@
#import "AppDelegate.h"
#import <Flutter/Flutter.h>
#import "LocationProvider.h"
#import <CoreLocation/CoreLocation.h>
@implementation AppDelegate {
LocationProvider* _locationProvider;
CLLocationManager* _locationManager;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
FlutterDartProject* project = [[FlutterDartProject alloc] initFromDefaultSourceForConfiguration];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
FlutterViewController* flutterController = [[FlutterViewController alloc] initWithProject:project
nibName:nil
bundle:nil];
_locationProvider = [[LocationProvider alloc] init];
[flutterController addMessageListener:_locationProvider];
self.window.rootViewController = flutterController;
[self.window makeKeyAndVisible];
return YES;
- (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
FlutterViewController* controller =
(FlutterViewController*)self.window.rootViewController;
FlutterMethodChannel* locationChannel = [FlutterMethodChannel
methodChannelNamed:@"location"
binaryMessenger:controller
codec:[FlutterStandardMethodCodec sharedInstance]];
[locationChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResultReceiver result) {
if ([@"getLocation" isEqualToString:call.method]) {
if (_locationManager == nil) {
_locationManager = [[CLLocationManager alloc] init];
[_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
<?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">
<?xml version="1.0" encoding="UTF-8"?>
<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>
<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>
<scenes>
<!--View Controller-->
......@@ -14,9 +18,9 @@
<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"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<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>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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">
<?xml version="1.0" encoding="UTF-8"?>
<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>
<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>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
<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"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<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>
</viewController>
<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';
final Random random = new Random();
Future<dynamic> handleGetRandom(Map<String, dynamic> message) async {
final double min = message['min'].toDouble();
final double max = message['max'].toDouble();
final PlatformMethodChannel randomChannel = new PlatformMethodChannel('random');
return <String, double>{
'value': (random.nextDouble() * (max - min)) + min
};
Future<dynamic> handleGetRandom(MethodCall call) async {
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 {
......@@ -25,8 +26,8 @@ class HelloServices extends StatefulWidget {
}
class _HelloServicesState extends State<HelloServices> {
double _latitude;
double _longitude;
static PlatformMethodChannel locationChannel = new PlatformMethodChannel('location');
String _location = 'Press button to get location';
@override
Widget build(BuildContext context) {
......@@ -38,32 +39,37 @@ class _HelloServicesState extends State<HelloServices> {
new Text('Hello from Flutter!'),
new RaisedButton(
child: new Text('Get Location'),
onPressed: _getLocation
onPressed: _getLocation,
),
new Text('Latitude: $_latitude, Longitude: $_longitude'),
]
)
)
new Text(_location),
],
),
),
);
}
Future<Null> _getLocation() async {
final Map<String, String> message = <String, String>{'provider': 'network'};
final Map<String, dynamic> reply = await PlatformMessages.sendJSON('getLocation', message);
String location;
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,
// we want to discard the reply rather than calling setState to update our
// non-existent appearance.
if (!mounted)
return;
if (!mounted) return;
setState(() {
_latitude = reply['latitude'].toDouble();
_longitude = reply['longitude'].toDouble();
_location = location;
});
}
}
void main() {
runApp(new HelloServices());
PlatformMessages.setJSONMessageHandler('getRandom', handleGetRandom);
randomChannel.setMethodCallHandler(handleGetRandom);
}
......@@ -26,6 +26,7 @@ export 'src/services/path_provider.dart';
export 'src/services/platform_channel.dart';
export 'src/services/platform_messages.dart';
export 'src/services/raw_keyboard.dart';
export 'src/services/system_channels.dart';
export 'src/services/system_chrome.dart';
export 'src/services/system_navigator.dart';
export 'src/services/system_sound.dart';
......
......@@ -4,7 +4,7 @@
import 'dart:async';
import 'platform_messages.dart';
import 'system_channels.dart';
/// Data stored on the system clipboard.
///
......@@ -18,8 +18,6 @@ class ClipboardData {
final String text;
}
const String _kChannelName = 'flutter/platform';
/// Utility methods for interacting with the system's clipboard.
class Clipboard {
Clipboard._();
......@@ -33,12 +31,11 @@ class Clipboard {
/// Stores the given clipboard data on the clipboard.
static Future<Null> setData(ClipboardData data) async {
await PlatformMessages.invokeMethod(
_kChannelName,
await SystemChannels.platform.invokeMethod(
'Clipboard.setData',
<Map<String, dynamic>>[<String, dynamic>{
<String, dynamic>{
'text': data.text,
}],
},
);
}
......@@ -50,10 +47,9 @@ class Clipboard {
/// Returns a future which completes to null if the data could not be
/// obtained, and to a [ClipboardData] object if it could.
static Future<ClipboardData> getData(String format) async {
final Map<String, dynamic> result = await PlatformMessages.invokeMethod(
_kChannelName,
final Map<String, dynamic> result = await SystemChannels.platform.invokeMethod(
'Clipboard.getData',
<String>[format]
format,
);
if (result == null)
return null;
......
......@@ -4,7 +4,7 @@
import 'dart:async';
import 'platform_messages.dart';
import 'system_channels.dart';
/// Allows access to the haptic feedback interface on the device.
///
......@@ -21,6 +21,6 @@ class HapticFeedback {
/// On Android, this uses the platform haptic feedback API to simulates a
/// short tap on a virtual keyboard.
static Future<Null> vibrate() async {
await PlatformMessages.invokeMethod('flutter/platform', 'HapticFeedback.vibrate');
await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate');
}
}
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui' show hashValues;
import 'package:meta/meta.dart';
......@@ -26,6 +27,68 @@ abstract class MessageCodec<T> {
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.
///
/// Result envelopes are binary messages with enough structure that the codec can
......@@ -43,15 +106,26 @@ abstract class MessageCodec<T> {
/// * [PlatformMethodChannel], which use [MethodCodec]s for communication
/// between Flutter and platform plugins.
abstract class MethodCodec {
/// Encodes the specified method call in binary.
///
/// The [name] of the method must be non-null. The [arguments] may be `null`.
ByteData encodeMethodCall(String name, dynamic arguments);
/// Encodes the specified [methodCall] in binary.
ByteData encodeMethodCall(MethodCall methodCall);
/// Decodes the specified [methodCall] from binary.
MethodCall decodeMethodCall(ByteData methodCall);
/// 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);
/// 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';
import 'dart:typed_data';
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:meta/meta.dart';
import 'message_codec.dart';
......@@ -86,8 +87,8 @@ class JSONMethodCodec implements MethodCodec {
//
// * Individual values are serialized as defined by the JSON codec of the
// dart:convert package.
// * Method calls are serialized as two-element lists with the method name
// string as first element and the method call arguments as the second.
// * Method calls are serialized as two-element maps, with the method name
// keyed by 'method' and the arguments keyed by 'args'.
// * Reply envelopes are serialized as either:
// * one-element lists containing the successful result as its single
// element, or
......@@ -99,9 +100,23 @@ class JSONMethodCodec implements MethodCodec {
const JSONMethodCodec();
@override
ByteData encodeMethodCall(String name, dynamic arguments) {
assert(name != null);
return const JSONMessageCodec().encodeMessage(<dynamic>[name, arguments]);
ByteData encodeMethodCall(MethodCall call) {
return const JSONMessageCodec().encodeMessage(<String, dynamic>{
'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
......@@ -119,7 +134,18 @@ class JSONMethodCodec implements MethodCodec {
message: decoded[1],
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 {
const StandardMethodCodec();
@override
ByteData encodeMethodCall(String name, dynamic arguments) {
assert(name != null);
ByteData encodeMethodCall(MethodCall call) {
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();
StandardMessageCodec._writeValue(buffer, name);
StandardMessageCodec._writeValue(buffer, arguments);
buffer.putUint8(1);
StandardMessageCodec._writeValue(buffer, code);
StandardMessageCodec._writeValue(buffer, message);
StandardMessageCodec._writeValue(buffer, details);
return buffer.done();
}
......@@ -412,7 +469,7 @@ class StandardMethodCodec implements MethodCodec {
final dynamic errorCode = StandardMessageCodec._readValue(buffer);
final dynamic errorMessage = 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);
else
throw const FormatException('Invalid envelope');
......
......@@ -5,9 +5,7 @@
import 'dart:async';
import 'dart:io';
import 'platform_messages.dart';
const String _kChannelName = 'flutter/platform';
import 'system_channels.dart';
/// Returns commonly used locations on the filesystem.
class PathProvider {
......@@ -24,11 +22,10 @@ class PathProvider {
///
/// On Android, this uses the `getCacheDir` API on the context.
static Future<Directory> getTemporaryDirectory() async {
final Map<String, dynamic> result = await PlatformMessages.invokeMethod(
_kChannelName, 'PathProvider.getTemporaryDirectory');
if (result == null)
final String path = await SystemChannels.platform.invokeMethod('PathProvider.getTemporaryDirectory');
if (path == 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
......@@ -39,10 +36,9 @@ class PathProvider {
///
/// On Android, this returns the AppData directory.
static Future<Directory> getApplicationDocumentsDirectory() async {
final Map<String, dynamic> result = await PlatformMessages.invokeMethod(
_kChannelName, 'PathProvider.getApplicationDocumentsDirectory');
if (result == null)
final String path = await SystemChannels.platform.invokeMethod('PathProvider.getApplicationDocumentsDirectory');
if (path == null)
return null;
return new Directory(result['path']);
return new Directory(path);
}
}
......@@ -25,6 +25,8 @@ import 'platform_messages.dart';
/// 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.
///
/// See: <https://flutter.io/platform-services/>
class PlatformMessageChannel<T> {
/// Creates a [PlatformMessageChannel] with the specified [name] and [codec].
///
......@@ -51,14 +53,19 @@ class PlatformMessageChannel<T> {
/// channel.
///
/// 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
/// plugins as a response.
void setMessageHandler(Future<T> handler(T message)) {
PlatformMessages.setBinaryMessageHandler(name, (ByteData message) async {
return codec.encodeMessage(await handler(codec.decodeMessage(message)));
});
if (handler == null) {
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.
......@@ -94,6 +101,8 @@ class PlatformMessageChannel<T> {
///
/// The identity of the channel is given by its name, so other uses of that name
/// with may interfere with this channel's communication.
///
/// See: <https://flutter.io/platform-services/>
class PlatformMethodChannel {
/// Creates a [PlatformMethodChannel] with the specified [name].
///
......@@ -120,10 +129,73 @@ class PlatformMethodChannel {
assert(method != null);
return codec.decodeEnvelope(await PlatformMessages.sendBinary(
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.
///
/// Returns a broadcast [Stream] which emits events to listeners as follows:
......
......@@ -3,34 +3,22 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui' as ui;
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);
/// 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/>
class PlatformMessages {
......@@ -97,91 +85,19 @@ class PlatformMessages {
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
/// given channel, without decoding them.
///
/// 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.
static void setBinaryMessageHandler(String channel, Future<ByteData> handler(ByteData message)) {
_handlers[channel] = handler;
}
/// Set a callback for receiving messages from the platform plugins on the
/// 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)));
});
if (handler == null)
_handlers.remove(channel);
else
_handlers[channel] = handler;
}
/// Set a mock callback for intercepting messages from the `send*` methods on
......@@ -201,52 +117,4 @@ class PlatformMessages {
else
_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';
import 'package:flutter/foundation.dart';
import 'platform_messages.dart';
import 'system_channels.dart';
/// Base class for platform specific key event data.
///
......@@ -182,7 +182,7 @@ RawKeyEvent _toRawKeyEvent(Map<String, dynamic> message) {
/// * [RawKeyUpEvent]
class RawKeyboard {
RawKeyboard._() {
PlatformMessages.setJSONMessageHandler('flutter/keyevent', _handleKeyEvent);
SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent);
}
/// 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 @@
import 'dart:async';
import 'platform_messages.dart';
import 'system_channels.dart';
/// Specifies a particular device orientation.
///
......@@ -87,8 +87,6 @@ enum SystemUiOverlayStyle {
dark,
}
const String _kChannelName = 'flutter/platform';
List<String> _stringify(List<dynamic> list) {
final List<String> result = <String>[];
for (dynamic item in list)
......@@ -107,10 +105,9 @@ class SystemChrome {
/// The `orientation` argument is a list of [DeviceOrientation] enum values.
/// The empty list is synonymous with having all options enabled.
static Future<Null> setPreferredOrientations(List<DeviceOrientation> orientations) async {
await PlatformMessages.invokeMethod(
_kChannelName,
await SystemChannels.platform.invokeMethod(
'SystemChrome.setPreferredOrientations',
<List<String>>[ _stringify(orientations) ],
_stringify(orientations),
);
}
......@@ -120,13 +117,12 @@ class SystemChrome {
/// Any part of the description that is unsupported on the current platform
/// will be ignored.
static Future<Null> setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async {
await PlatformMessages.invokeMethod(
_kChannelName,
await SystemChannels.platform.invokeMethod(
'SystemChrome.setApplicationSwitcherDescription',
<Map<String, dynamic>>[<String, dynamic>{
<String, dynamic>{
'label': description.label,
'primaryColor': description.primaryColor,
}],
},
);
}
......@@ -139,10 +135,9 @@ class SystemChrome {
/// If a particular overlay is unsupported on the platform, enabling or
/// disabling that overlay will be ignored.
static Future<Null> setEnabledSystemUIOverlays(List<SystemUiOverlay> overlays) async {
await PlatformMessages.invokeMethod(
_kChannelName,
await SystemChannels.platform.invokeMethod(
'SystemChrome.setEnabledSystemUIOverlays',
<List<String>>[ _stringify(overlays) ],
_stringify(overlays),
);
}
......@@ -175,10 +170,9 @@ class SystemChrome {
scheduleMicrotask(() {
assert(_pendingStyle != null);
if (_pendingStyle != _latestStyle) {
PlatformMessages.invokeMethod(
_kChannelName,
SystemChannels.platform.invokeMethod(
'SystemChrome.setSystemUIOverlayStyle',
<String>[ _pendingStyle.toString() ],
_pendingStyle.toString(),
);
_latestStyle = _pendingStyle;
}
......
......@@ -4,7 +4,7 @@
import 'dart:async';
import 'platform_messages.dart';
import 'system_channels.dart';
/// Controls specific aspects of the system navigation stack.
class SystemNavigator {
......@@ -20,6 +20,6 @@ class SystemNavigator {
/// the latter may cause the underlying platform to act as if the application
/// had crashed.
static Future<Null> pop() async {
await PlatformMessages.invokeMethod('flutter/platform', 'SystemNavigator.pop');
await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
}
......@@ -4,7 +4,7 @@
import 'dart:async';
import 'platform_messages.dart';
import 'system_channels.dart';
/// A sound provided by the system.
enum SystemSoundType {
......@@ -20,10 +20,9 @@ class SystemSound {
/// Play the specified system sound. If that sound is not present on the
/// system, the call is ignored.
static Future<Null> play(SystemSoundType type) async {
await PlatformMessages.invokeMethod(
'flutter/platform',
await SystemChannels.platform.invokeMethod(
'SystemSound.play',
<String>[ type.toString() ],
type.toString(),
);
}
}
......@@ -7,7 +7,8 @@ import 'dart:ui' show TextAffinity;
import 'package:flutter/foundation.dart';
import 'platform_messages.dart';
import 'system_channels.dart';
import 'message_codec.dart';
export 'dart:ui' show TextAffinity;
......@@ -172,8 +173,6 @@ abstract class TextInputClient {
void performAction(TextInputAction action);
}
const String _kChannelName = 'flutter/textinput';
/// A interface for interacting with a text input control.
///
/// See also:
......@@ -195,16 +194,15 @@ class TextInputConnection {
/// Requests that the text input control become visible.
void show() {
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.
void setEditingState(TextEditingState state) {
assert(attached);
PlatformMessages.invokeMethod(
_kChannelName,
SystemChannels.textInput.invokeMethod(
'TextInput.setEditingState',
<dynamic>[ state.toJSON() ],
state.toJSON(),
);
}
......@@ -214,7 +212,7 @@ class TextInputConnection {
/// other client attaches to it within this animation frame.
void close() {
if (attached) {
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.clearClient');
SystemChannels.textInput.invokeMethod('TextInput.clearClient');
_clientHandler
.._currentConnection = null
.._scheduleHide();
......@@ -233,16 +231,16 @@ TextInputAction _toTextInputAction(String action) {
class _TextInputClientHandler {
_TextInputClientHandler() {
PlatformMessages.setJSONMessageHandler('flutter/textinputclient', _handleMessage);
SystemChannels.textInput.setMethodCallHandler(_handleTextInputInvocation);
}
TextInputConnection _currentConnection;
Future<Null> _handleMessage(dynamic message) async {
Future<dynamic> _handleTextInputInvocation(MethodCall methodCall) async {
if (_currentConnection == null)
return;
final String method = message['method'];
final List<dynamic> args = message['args'];
final String method = methodCall.method;
final List<dynamic> args = methodCall.arguments;
final int client = args[0];
// The incoming message was for a different client.
if (client != _currentConnection._id)
......@@ -270,7 +268,7 @@ class _TextInputClientHandler {
scheduleMicrotask(() {
_hidePending = false;
if (_currentConnection == null)
PlatformMessages.invokeMethod(_kChannelName, 'TextInput.hide');
SystemChannels.textInput.invokeMethod('TextInput.hide');
});
}
}
......@@ -296,8 +294,7 @@ class TextInput {
assert(configuration != null);
final TextInputConnection connection = new TextInputConnection._(client);
_clientHandler._currentConnection = connection;
PlatformMessages.invokeMethod(
_kChannelName,
SystemChannels.textInput.invokeMethod(
'TextInput.setClient',
<dynamic>[ connection._id, configuration.toJSON() ],
);
......
......@@ -4,7 +4,7 @@
import 'dart:async';
import 'platform_messages.dart';
import 'system_channels.dart';
/// Allows applications to delegate responsbility of handling certain URLs to
/// the underlying platform.
......@@ -14,10 +14,9 @@ class UrlLauncher {
/// Parse the specified URL string and delegate handling of the same to the
/// underlying platform.
static Future<Null> launch(String urlString) async {
await PlatformMessages.invokeMethod(
'flutter/platform',
await SystemChannels.platform.invokeMethod(
'UrlLauncher.launch',
<String>[ urlString ],
urlString,
);
}
}
......@@ -61,9 +61,9 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
_instance = this;
buildOwner.onBuildScheduled = _handleBuildScheduled;
ui.window.onLocaleChanged = handleLocaleChanged;
PlatformMessages.setJSONMessageHandler('flutter/navigation', _handleNavigationMessage);
PlatformMessages.setStringMessageHandler('flutter/lifecycle', _handleLifecycleMessage);
PlatformMessages.setJSONMessageHandler('flutter/system', _handleSystemMessage);
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
SystemChannels.system.setMessageHandler(_handleSystemMessage);
}
/// The current [WidgetsBinding], if one has been created.
......@@ -189,9 +189,8 @@ abstract class WidgetsBinding extends BindingBase implements GestureBinding, Ren
SystemNavigator.pop();
}
Future<dynamic> _handleNavigationMessage(Map<String, dynamic> message) async {
final String method = message['method'];
if (method == 'popRoute')
Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) async {
if (methodCall.method == 'popRoute')
handlePopRoute();
// TODO(abarth): Handle 'pushRoute'.
}
......
......@@ -275,8 +275,8 @@ void main() {
int hapticFeedbackCount;
setUpAll(() {
PlatformMessages.setMockJSONMessageHandler('flutter/platform', (dynamic message) {
if (message['method'] == "HapticFeedback.vibrate")
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == "HapticFeedback.vibrate")
hapticFeedbackCount++;
});
});
......
......@@ -118,8 +118,8 @@ void main() {
int hapticFeedbackCount;
setUpAll(() {
PlatformMessages.setMockJSONMessageHandler('flutter/platform', (dynamic message) {
if (message['method'] == "HapticFeedback.vibrate")
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) {
if (methodCall.method == "HapticFeedback.vibrate")
hapticFeedbackCount++;
});
});
......
......@@ -7,14 +7,14 @@ import 'package:test/test.dart';
void main() {
test('Haptic feedback control test', () async {
final List<String> log = <String>[];
final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
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';
void main() {
test('Path provider control test', () async {
final List<String> log = <String>[];
final List<MethodCall> log = <MethodCall>[];
String response;
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
return response;
});
Directory directory = await PathProvider.getTemporaryDirectory();
expect(log, equals(<String>['{"method":"PathProvider.getTemporaryDirectory","args":[]}']));
expect(log, equals(<MethodCall>[new MethodCall('PathProvider.getTemporaryDirectory')]));
expect(directory, isNull);
log.clear();
directory = await PathProvider.getApplicationDocumentsDirectory();
expect(log, equals(<String>['{"method":"PathProvider.getApplicationDocumentsDirectory","args":[]}']));
expect(log, equals(<MethodCall>[new MethodCall('PathProvider.getApplicationDocumentsDirectory')]));
expect(directory, isNull);
final String fakePath = "/foo/bar/baz";
response = '{"path":"$fakePath"}';
response = fakePath;
directory = await PathProvider.getTemporaryDirectory();
expect(directory.path, equals(fakePath));
......
......@@ -42,9 +42,9 @@ void main() {
PlatformMessages.setMockBinaryMessageHandler(
'ch',
(ByteData message) async {
final List<dynamic> methodCall = jsonMessage.decodeMessage(message);
if (methodCall[0] == 'sayHello')
return jsonMessage.encodeMessage(<dynamic>['${methodCall[1]} world']);
final Map<dynamic, dynamic> methodCall = jsonMessage.decodeMessage(message);
if (methodCall['method'] == 'sayHello')
return jsonMessage.encodeMessage(<dynamic>['${methodCall['args']} world']);
else
return jsonMessage.encodeMessage(<dynamic>['unknown', null, null]);
},
......@@ -84,14 +84,14 @@ void main() {
PlatformMessages.setMockBinaryMessageHandler(
'ch',
(ByteData message) async {
final List<dynamic> methodCall = jsonMessage.decodeMessage(message);
if (methodCall[0] == 'listen') {
final String argument = methodCall[1];
final Map<dynamic, dynamic> methodCall = jsonMessage.decodeMessage(message);
if (methodCall['method'] == 'listen') {
final String argument = methodCall['args'];
emitEvent(jsonMessage.encodeMessage(<dynamic>[argument + '1']));
emitEvent(jsonMessage.encodeMessage(<dynamic>[argument + '2']));
emitEvent(null);
return jsonMessage.encodeMessage(<dynamic>[null]);
} else if (methodCall[0] == 'cancel') {
} else if (methodCall['method'] == 'cancel') {
cancelled = true;
return jsonMessage.encodeMessage(<dynamic>[null]);
} else {
......
......@@ -2,39 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:test/test.dart';
void main() {
test('Mock string message handler control test', () async {
final List<String> log = <String>[];
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>[];
test('Mock binary message handler control test', () async {
final List<ByteData> log = <ByteData>[];
PlatformMessages.setMockJSONMessageHandler('test2', (dynamic message) async {
PlatformMessages.setMockBinaryMessageHandler('test1', (ByteData message) async {
log.add(message);
});
await PlatformMessages.sendString('test2', '{"hello": "world"}');
expect(log, equals(<Map<String, String>>[<String, String>{'hello': 'world'}]));
final ByteData message = new ByteData(2)..setUint16(0, 0xABCD);
await PlatformMessages.sendBinary('test1', message);
expect(log, equals(<ByteData>[message]));
log.clear();
PlatformMessages.setMockStringMessageHandler('test2', null);
await PlatformMessages.sendString('test2', '{"fail": "message"}');
PlatformMessages.setMockBinaryMessageHandler('test1', null);
await PlatformMessages.sendBinary('test1', message);
expect(log, isEmpty);
});
}
......@@ -21,42 +21,51 @@ void main() {
});
test('setPreferredOrientations control test', () async {
final List<String> log = <String>[];
final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
await SystemChrome.setPreferredOrientations(<DeviceOrientation>[
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 {
final List<String> log = <String>[];
final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
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 {
final List<String> log = <String>[];
final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
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';
void main() {
test('System navigator control test', () async {
final List<String> log = <String>[];
final List<MethodCall> log = <MethodCall>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
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';
void main() {
test('System sound control test', () async {
final List<String> log = <String>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
final List<MethodCall> log = <MethodCall>[];
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
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';
void main() {
test('URL launcher control test', () async {
final List<String> log = <String>[];
PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async {
log.add(message);
final List<MethodCall> log = <MethodCall>[];
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
log.add(methodCall);
});
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 {
'text': null
};
Future<dynamic> handleJSONMessage(dynamic message) async {
final String method = message['method'];
final List<dynamic> args= message['args'];
switch (method) {
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'Clipboard.getData':
return _clipboardData;
case 'Clipboard.setData':
_clipboardData = args[0];
_clipboardData = methodCall.arguments;
break;
}
}
......@@ -40,7 +38,7 @@ Widget overlay(Widget child) {
void main() {
final MockClipboard mockClipboard = new MockClipboard();
PlatformMessages.setMockJSONMessageHandler('flutter/platform', mockClipboard.handleJSONMessage);
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
const String kThreeLines =
'First line of text is here abcdef ghijkl mnopqrst. ' +
......
......@@ -2,18 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void sendFakeKeyEvent(Map<String, dynamic> data) {
final String message = JSON.encode(data);
final Uint8List encoded = UTF8.encoder.convert(message);
PlatformMessages.handlePlatformMessage(
'flutter/keyevent', encoded.buffer.asByteData(), (_) {});
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(_) {});
}
void main() {
......
......@@ -3,8 +3,6 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart';
......@@ -23,34 +21,35 @@ const String _kTextInputClientChannel = 'flutter/textinputclient';
/// popup keyboard and initializing its text.
class TestTextInput {
void register() {
PlatformMessages.setMockJSONMessageHandler('flutter/textinput', handleJSONMessage);
SystemChannels.textInput.setMockMethodCallHandler(handleTextInputCall);
}
int _client = 0;
Map<String, dynamic> editingState;
Future<dynamic> handleJSONMessage(dynamic message) async {
final String method = message['method'];
final List<dynamic> args= message['args'];
switch (method) {
Future<dynamic> handleTextInputCall(MethodCall methodCall) async {
switch (methodCall.method) {
case 'TextInput.setClient':
_client = args[0];
_client = methodCall.arguments[0];
break;
case 'TextInput.setEditingState':
editingState = args[0];
editingState = methodCall.arguments;
break;
}
}
void updateEditingState(TextEditingState state) {
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(
_kTextInputClientChannel, encoded.buffer.asByteData(), (_) {});
SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall(
new MethodCall(
'TextInputClient.updateEditingState',
<dynamic>[_client, state.toJSON()],
),
),
(_) {},
);
}
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