diff --git a/bin/internal/engine.version b/bin/internal/engine.version index e3f73c871f..82d21ba1aa 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -4a5a32466958dab49b9940e4528ee6d523f4a5ac +c4edec741704ace2c4edeff57ac348435cd1f898 diff --git a/examples/hello_services/android/app/src/androidTest/java/com/example/flutter/ExampleInstrumentedTest.java b/examples/hello_services/android/app/src/androidTest/java/com/example/flutter/ExampleInstrumentedTest.java index 6d9cb4cde4..5b66006e9b 100644 --- a/examples/hello_services/android/app/src/androidTest/java/com/example/flutter/ExampleInstrumentedTest.java +++ b/examples/hello_services/android/app/src/androidTest/java/com/example/flutter/ExampleInstrumentedTest.java @@ -1,44 +1,39 @@ 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 android.app.Instrumentation; -import android.support.test.InstrumentationRegistry; -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 java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +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 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); - } - } - }; } } diff --git a/examples/hello_services/android/app/src/main/java/com/example/flutter/ExampleActivity.java b/examples/hello_services/android/app/src/main/java/com/example/flutter/ExampleActivity.java index a7d627aa19..e9518aa4b1 100644 --- a/examples/hello_services/android/app/src/main/java/com/example/flutter/ExampleActivity.java +++ b/examples/hello_services/android/app/src/main/java/com/example/flutter/ExampleActivity.java @@ -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; - } + private void getRandom() { + randomChannel.invokeMethod("getRandom", Arrays.asList(1, 1000), new FlutterMethodChannel.Response() { + TextView textView = (TextView) findViewById(R.id.random_value); - flutterView.sendToFlutter("getRandom", message.toString(), - new FlutterView.MessageReplyCallback() { - @Override - public void onReply(String json) { - onRandomReply(json); - } - }); + @Override + public void success(Object result) { + textView.setText(result.toString()); + } + + @Override + public void error(String code, String message, Object details) { + textView.setText("Error: " + message); + } + }); } - 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; - } - - TextView randomValue = (TextView) findViewById(R.id.random_value); - randomValue.setText(Double.toString(value)); - } - - 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(); } } diff --git a/examples/hello_services/ios/Runner.xcodeproj/project.pbxproj b/examples/hello_services/ios/Runner.xcodeproj/project.pbxproj index 673f0f6b55..ebacdce07a 100644 --- a/examples/hello_services/ios/Runner.xcodeproj/project.pbxproj +++ b/examples/hello_services/ios/Runner.xcodeproj/project.pbxproj @@ -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 = ""; }; 9740EEB81CF902C7004384FC /* app.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = app.dylib; path = Flutter/app.dylib; sourceTree = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; - 977505171CFDF21E00BC28DA /* LocationProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocationProvider.h; sourceTree = ""; }; - 977505181CFDF23500BC28DA /* LocationProvider.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LocationProvider.m; sourceTree = ""; }; 97A38A331CFDEC680099F1B4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 97A38A341CFDEC880099F1B4 /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 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 = ""; diff --git a/examples/hello_services/ios/Runner/AppDelegate.m b/examples/hello_services/ios/Runner/AppDelegate.m index bf8e1915c5..3e8abee3d8 100644 --- a/examples/hello_services/ios/Runner/AppDelegate.m +++ b/examples/hello_services/ios/Runner/AppDelegate.m @@ -5,24 +5,34 @@ #import "AppDelegate.h" #import -#import "LocationProvider.h" +#import @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 diff --git a/examples/hello_services/ios/Runner/Base.lproj/LaunchScreen.storyboard b/examples/hello_services/ios/Runner/Base.lproj/LaunchScreen.storyboard index ebf48f6039..1709aef99d 100644 --- a/examples/hello_services/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/examples/hello_services/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,12 @@ - - + + + + + - + + @@ -14,9 +18,9 @@ - + - + diff --git a/examples/hello_services/ios/Runner/Base.lproj/Main.storyboard b/examples/hello_services/ios/Runner/Base.lproj/Main.storyboard index f56d2f3bb5..2801f0cba3 100644 --- a/examples/hello_services/ios/Runner/Base.lproj/Main.storyboard +++ b/examples/hello_services/ios/Runner/Base.lproj/Main.storyboard @@ -1,21 +1,26 @@ - - + + + + + - + + + - + - + - + diff --git a/examples/hello_services/ios/Runner/LocationProvider.h b/examples/hello_services/ios/Runner/LocationProvider.h deleted file mode 100644 index e646fdf4fc..0000000000 --- a/examples/hello_services/ios/Runner/LocationProvider.h +++ /dev/null @@ -1,9 +0,0 @@ -// 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 - -@interface LocationProvider : NSObject - -@end diff --git a/examples/hello_services/ios/Runner/LocationProvider.m b/examples/hello_services/ios/Runner/LocationProvider.m deleted file mode 100644 index e6e0cc7b62..0000000000 --- a/examples/hello_services/ios/Runner/LocationProvider.m +++ /dev/null @@ -1,39 +0,0 @@ -// 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 - -@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 diff --git a/examples/hello_services/lib/main.dart b/examples/hello_services/lib/main.dart index a2b98ecea8..ddec1e96ce 100644 --- a/examples/hello_services/lib/main.dart +++ b/examples/hello_services/lib/main.dart @@ -10,13 +10,14 @@ import 'package:flutter/services.dart'; final Random random = new Random(); -Future handleGetRandom(Map message) async { - final double min = message['min'].toDouble(); - final double max = message['max'].toDouble(); +final PlatformMethodChannel randomChannel = new PlatformMethodChannel('random'); - return { - 'value': (random.nextDouble() * (max - min)) + min - }; +Future 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 { - 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 { 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 _getLocation() async { - final Map message = {'provider': 'network'}; - final Map reply = await PlatformMessages.sendJSON('getLocation', message); + String location; + try { + final List 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); } diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index cbbbefe2b2..c6be68779b 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -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'; diff --git a/packages/flutter/lib/src/services/clipboard.dart b/packages/flutter/lib/src/services/clipboard.dart index 7b9dc2406f..d578e33b84 100644 --- a/packages/flutter/lib/src/services/clipboard.dart +++ b/packages/flutter/lib/src/services/clipboard.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 setData(ClipboardData data) async { - await PlatformMessages.invokeMethod( - _kChannelName, + await SystemChannels.platform.invokeMethod( 'Clipboard.setData', - >[{ + { '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 getData(String format) async { - final Map result = await PlatformMessages.invokeMethod( - _kChannelName, + final Map result = await SystemChannels.platform.invokeMethod( 'Clipboard.getData', - [format] + format, ); if (result == null) return null; diff --git a/packages/flutter/lib/src/services/haptic_feedback.dart b/packages/flutter/lib/src/services/haptic_feedback.dart index 1a1a968b43..d15eda9709 100644 --- a/packages/flutter/lib/src/services/haptic_feedback.dart +++ b/packages/flutter/lib/src/services/haptic_feedback.dart @@ -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 vibrate() async { - await PlatformMessages.invokeMethod('flutter/platform', 'HapticFeedback.vibrate'); + await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate'); } } diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index 2cdb2b2158..92b5653567 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -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 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 a, List 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 a, Map 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 { /// * [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}); } diff --git a/packages/flutter/lib/src/services/message_codecs.dart b/packages/flutter/lib/src/services/message_codecs.dart index 8e1e2be186..983edc856d 100644 --- a/packages/flutter/lib/src/services/message_codecs.dart +++ b/packages/flutter/lib/src/services/message_codecs.dart @@ -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([name, arguments]); + ByteData encodeMethodCall(MethodCall call) { + return const JSONMessageCodec().encodeMessage({ + '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([result]); + } + + @override + ByteData encodeErrorEnvelope({@required String code, String message, dynamic details}) { + assert(code != null); + return const JSONMessageCodec().encodeMessage([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, name); - StandardMessageCodec._writeValue(buffer, arguments); + 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(); + 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'); diff --git a/packages/flutter/lib/src/services/path_provider.dart b/packages/flutter/lib/src/services/path_provider.dart index 913a83b5ab..891bbfeebf 100644 --- a/packages/flutter/lib/src/services/path_provider.dart +++ b/packages/flutter/lib/src/services/path_provider.dart @@ -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 getTemporaryDirectory() async { - final Map 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 getApplicationDocumentsDirectory() async { - final Map 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); } } diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart index 8289ca8008..03dcfe0648 100644 --- a/packages/flutter/lib/src/services/platform_channel.dart +++ b/packages/flutter/lib/src/services/platform_channel.dart @@ -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: class PlatformMessageChannel { /// Creates a [PlatformMessageChannel] with the specified [name] and [codec]. /// @@ -51,14 +53,19 @@ class PlatformMessageChannel { /// 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 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 { /// /// 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: 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 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 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: diff --git a/packages/flutter/lib/src/services/platform_messages.dart b/packages/flutter/lib/src/services/platform_messages.dart index f7351309dd..10a1bc5836 100644 --- a/packages/flutter/lib/src/services/platform_messages.dart +++ b/packages/flutter/lib/src/services/platform_messages.dart @@ -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 _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: 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 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 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 invokeMethod(String channel, String method, [ List args = const [] ]) { - return sendJSON(channel, { - '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 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 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 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 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 handler(dynamic message)) { - if (handler == null) { - setMockStringMessageHandler(channel, null); - } else { - setMockStringMessageHandler(channel, (String message) async { - return _encodeJSON(await handler(_decodeJSON(message))); - }); - } - } } diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index abb5b33ca0..da799a8858 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart @@ -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 message) { /// * [RawKeyUpEvent] class RawKeyboard { RawKeyboard._() { - PlatformMessages.setJSONMessageHandler('flutter/keyevent', _handleKeyEvent); + SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent); } /// The shared instance of [RawKeyboard]. diff --git a/packages/flutter/lib/src/services/system_channels.dart b/packages/flutter/lib/src/services/system_channels.dart new file mode 100644 index 0000000000..8fe5613a35 --- /dev/null +++ b/packages/flutter/lib/src/services/system_channels.dart @@ -0,0 +1,48 @@ +// 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 keyEvent = const PlatformMessageChannel( + 'flutter/keyevent', + const JSONMessageCodec(), + ); + + /// A string [PlatformMessageChannel] for lifecycle events. + static const PlatformMessageChannel lifecycle = const PlatformMessageChannel( + 'flutter/lifecycle', + const StringCodec(), + ); + + /// A JSON [PlatformMessageChannel] for system events. + static const PlatformMessageChannel system = const PlatformMessageChannel( + 'flutter/system', + const JSONMessageCodec(), + ); + +} diff --git a/packages/flutter/lib/src/services/system_chrome.dart b/packages/flutter/lib/src/services/system_chrome.dart index 364bf56e56..1de0f87890 100644 --- a/packages/flutter/lib/src/services/system_chrome.dart +++ b/packages/flutter/lib/src/services/system_chrome.dart @@ -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 _stringify(List list) { final List result = []; 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 setPreferredOrientations(List orientations) async { - await PlatformMessages.invokeMethod( - _kChannelName, + await SystemChannels.platform.invokeMethod( 'SystemChrome.setPreferredOrientations', - >[ _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 setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async { - await PlatformMessages.invokeMethod( - _kChannelName, + await SystemChannels.platform.invokeMethod( 'SystemChrome.setApplicationSwitcherDescription', - >[{ + { '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 setEnabledSystemUIOverlays(List overlays) async { - await PlatformMessages.invokeMethod( - _kChannelName, + await SystemChannels.platform.invokeMethod( 'SystemChrome.setEnabledSystemUIOverlays', - >[ _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', - [ _pendingStyle.toString() ], + _pendingStyle.toString(), ); _latestStyle = _pendingStyle; } diff --git a/packages/flutter/lib/src/services/system_navigator.dart b/packages/flutter/lib/src/services/system_navigator.dart index a4402a6840..036601eb67 100644 --- a/packages/flutter/lib/src/services/system_navigator.dart +++ b/packages/flutter/lib/src/services/system_navigator.dart @@ -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 pop() async { - await PlatformMessages.invokeMethod('flutter/platform', 'SystemNavigator.pop'); + await SystemChannels.platform.invokeMethod('SystemNavigator.pop'); } } diff --git a/packages/flutter/lib/src/services/system_sound.dart b/packages/flutter/lib/src/services/system_sound.dart index c97c11d553..80b833ba04 100644 --- a/packages/flutter/lib/src/services/system_sound.dart +++ b/packages/flutter/lib/src/services/system_sound.dart @@ -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 play(SystemSoundType type) async { - await PlatformMessages.invokeMethod( - 'flutter/platform', + await SystemChannels.platform.invokeMethod( 'SystemSound.play', - [ type.toString() ], + type.toString(), ); } } diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 26b161fd65..05b4c8abfe 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -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', - [ 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 _handleMessage(dynamic message) async { + Future _handleTextInputInvocation(MethodCall methodCall) async { if (_currentConnection == null) return; - final String method = message['method']; - final List args = message['args']; + final String method = methodCall.method; + final List 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', [ connection._id, configuration.toJSON() ], ); diff --git a/packages/flutter/lib/src/services/url_launcher.dart b/packages/flutter/lib/src/services/url_launcher.dart index a320984cbc..75ed4ed387 100644 --- a/packages/flutter/lib/src/services/url_launcher.dart +++ b/packages/flutter/lib/src/services/url_launcher.dart @@ -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 launch(String urlString) async { - await PlatformMessages.invokeMethod( - 'flutter/platform', + await SystemChannels.platform.invokeMethod( 'UrlLauncher.launch', - [ urlString ], + urlString, ); } } diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 3252d45fb5..4ab575a111 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -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 _handleNavigationMessage(Map message) async { - final String method = message['method']; - if (method == 'popRoute') + Future _handleNavigationInvocation(MethodCall methodCall) async { + if (methodCall.method == 'popRoute') handlePopRoute(); // TODO(abarth): Handle 'pushRoute'. } diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index ba00d94561..9baaa49910 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -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++; }); }); diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 6f83e957b9..6f210fdf5a 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -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++; }); }); diff --git a/packages/flutter/test/services/haptic_feedback_test.dart b/packages/flutter/test/services/haptic_feedback_test.dart index 1acf6bef0e..f051fb49e3 100644 --- a/packages/flutter/test/services/haptic_feedback_test.dart +++ b/packages/flutter/test/services/haptic_feedback_test.dart @@ -7,14 +7,14 @@ import 'package:test/test.dart'; void main() { test('Haptic feedback control test', () async { - final List log = []; + final List log = []; - 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(['{"method":"HapticFeedback.vibrate","args":[]}'])); + expect(log, equals([new MethodCall('HapticFeedback.vibrate')])); }); } diff --git a/packages/flutter/test/services/path_provider_test.dart b/packages/flutter/test/services/path_provider_test.dart index bd5dc479b4..a31823daac 100644 --- a/packages/flutter/test/services/path_provider_test.dart +++ b/packages/flutter/test/services/path_provider_test.dart @@ -9,27 +9,27 @@ import 'package:test/test.dart'; void main() { test('Path provider control test', () async { - final List log = []; + final List log = []; 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(['{"method":"PathProvider.getTemporaryDirectory","args":[]}'])); + expect(log, equals([new MethodCall('PathProvider.getTemporaryDirectory')])); expect(directory, isNull); log.clear(); directory = await PathProvider.getApplicationDocumentsDirectory(); - expect(log, equals(['{"method":"PathProvider.getApplicationDocumentsDirectory","args":[]}'])); + expect(log, equals([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)); diff --git a/packages/flutter/test/services/platform_channel_test.dart b/packages/flutter/test/services/platform_channel_test.dart index c583c520d3..d5b0223525 100644 --- a/packages/flutter/test/services/platform_channel_test.dart +++ b/packages/flutter/test/services/platform_channel_test.dart @@ -42,9 +42,9 @@ void main() { PlatformMessages.setMockBinaryMessageHandler( 'ch', (ByteData message) async { - final List methodCall = jsonMessage.decodeMessage(message); - if (methodCall[0] == 'sayHello') - return jsonMessage.encodeMessage(['${methodCall[1]} world']); + final Map methodCall = jsonMessage.decodeMessage(message); + if (methodCall['method'] == 'sayHello') + return jsonMessage.encodeMessage(['${methodCall['args']} world']); else return jsonMessage.encodeMessage(['unknown', null, null]); }, @@ -84,14 +84,14 @@ void main() { PlatformMessages.setMockBinaryMessageHandler( 'ch', (ByteData message) async { - final List methodCall = jsonMessage.decodeMessage(message); - if (methodCall[0] == 'listen') { - final String argument = methodCall[1]; + final Map methodCall = jsonMessage.decodeMessage(message); + if (methodCall['method'] == 'listen') { + final String argument = methodCall['args']; emitEvent(jsonMessage.encodeMessage([argument + '1'])); emitEvent(jsonMessage.encodeMessage([argument + '2'])); emitEvent(null); return jsonMessage.encodeMessage([null]); - } else if (methodCall[0] == 'cancel') { + } else if (methodCall['method'] == 'cancel') { cancelled = true; return jsonMessage.encodeMessage([null]); } else { diff --git a/packages/flutter/test/services/platform_messages_test.dart b/packages/flutter/test/services/platform_messages_test.dart index 6904930ee8..a202809228 100644 --- a/packages/flutter/test/services/platform_messages_test.dart +++ b/packages/flutter/test/services/platform_messages_test.dart @@ -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 log = []; + test('Mock binary message handler control test', () async { + final List log = []; - PlatformMessages.setMockStringMessageHandler('test1', (String message) async { + PlatformMessages.setMockBinaryMessageHandler('test1', (ByteData message) async { log.add(message); }); - await PlatformMessages.sendString('test1', 'hello'); - expect(log, equals(['hello'])); + final ByteData message = new ByteData(2)..setUint16(0, 0xABCD); + await PlatformMessages.sendBinary('test1', message); + expect(log, equals([message])); log.clear(); - PlatformMessages.setMockStringMessageHandler('test1', null); - await PlatformMessages.sendString('test1', 'fail'); - expect(log, isEmpty); - }); - - test('Mock JSON message handler control test', () async { - final List log = []; - - PlatformMessages.setMockJSONMessageHandler('test2', (dynamic message) async { - log.add(message); - }); - - await PlatformMessages.sendString('test2', '{"hello": "world"}'); - expect(log, equals(>[{'hello': 'world'}])); - log.clear(); - - PlatformMessages.setMockStringMessageHandler('test2', null); - await PlatformMessages.sendString('test2', '{"fail": "message"}'); + PlatformMessages.setMockBinaryMessageHandler('test1', null); + await PlatformMessages.sendBinary('test1', message); expect(log, isEmpty); }); } diff --git a/packages/flutter/test/services/system_chrome_test.dart b/packages/flutter/test/services/system_chrome_test.dart index 06644cd3ba..16c66d8143 100644 --- a/packages/flutter/test/services/system_chrome_test.dart +++ b/packages/flutter/test/services/system_chrome_test.dart @@ -21,42 +21,51 @@ void main() { }); test('setPreferredOrientations control test', () async { - final List log = []; + final List log = []; - PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { - log.add(message); + SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); }); await SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]); - expect(log, equals(['{"method":"SystemChrome.setPreferredOrientations","args":[["DeviceOrientation.portraitUp"]]}'])); + expect(log, equals([new MethodCall( + 'SystemChrome.setPreferredOrientations', + ["DeviceOrientation.portraitUp"], + )])); }); test('setApplicationSwitcherDescription control test', () async { - final List log = []; + final List log = []; - 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(['{"method":"SystemChrome.setApplicationSwitcherDescription","args":[{"label":"Example label","primaryColor":4278255360}]}'])); + expect(log, equals([new MethodCall( + 'SystemChrome.setApplicationSwitcherDescription', + {"label":"Example label","primaryColor":4278255360} + )])); }); test('setEnabledSystemUIOverlays control test', () async { - final List log = []; + final List log = []; - PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { - log.add(message); + SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); }); await SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top]); - expect(log, equals(['{"method":"SystemChrome.setEnabledSystemUIOverlays","args":[["SystemUiOverlay.top"]]}'])); + expect(log, equals([new MethodCall( + 'SystemChrome.setEnabledSystemUIOverlays', + ["SystemUiOverlay.top"], + )])); }); } diff --git a/packages/flutter/test/services/system_navigator_test.dart b/packages/flutter/test/services/system_navigator_test.dart index 2a40e63013..828e80b983 100644 --- a/packages/flutter/test/services/system_navigator_test.dart +++ b/packages/flutter/test/services/system_navigator_test.dart @@ -7,14 +7,14 @@ import 'package:test/test.dart'; void main() { test('System navigator control test', () async { - final List log = []; + final List log = []; - 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(['{"method":"SystemNavigator.pop","args":[]}'])); + expect(log, equals([new MethodCall('SystemNavigator.pop')])); }); } diff --git a/packages/flutter/test/services/system_sound_test.dart b/packages/flutter/test/services/system_sound_test.dart index 8b9a7ec9a7..f06683919c 100644 --- a/packages/flutter/test/services/system_sound_test.dart +++ b/packages/flutter/test/services/system_sound_test.dart @@ -7,14 +7,14 @@ import 'package:test/test.dart'; void main() { test('System sound control test', () async { - final List log = []; - - PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { - log.add(message); + final List log = []; + + SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); }); - + await SystemSound.play(SystemSoundType.click); - expect(log, equals(['{"method":"SystemSound.play","args":["SystemSoundType.click"]}'])); + expect(log, equals([new MethodCall('SystemSound.play', "SystemSoundType.click")])); }); } diff --git a/packages/flutter/test/services/url_launcher_test.dart b/packages/flutter/test/services/url_launcher_test.dart index 2bd51dd432..31e4fa2eee 100644 --- a/packages/flutter/test/services/url_launcher_test.dart +++ b/packages/flutter/test/services/url_launcher_test.dart @@ -7,14 +7,14 @@ import 'package:test/test.dart'; void main() { test('URL launcher control test', () async { - final List log = []; - - PlatformMessages.setMockStringMessageHandler('flutter/platform', (String message) async { - log.add(message); + final List log = []; + + SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); }); await UrlLauncher.launch('http://example.com/'); - expect(log, equals(['{"method":"UrlLauncher.launch","args":["http://example.com/"]}'])); + expect(log, equals([new MethodCall('UrlLauncher.launch', 'http://example.com/')])); }); } diff --git a/packages/flutter/test/widgets/input_test.dart b/packages/flutter/test/widgets/input_test.dart index da79c566ee..ae6d986b66 100644 --- a/packages/flutter/test/widgets/input_test.dart +++ b/packages/flutter/test/widgets/input_test.dart @@ -15,14 +15,12 @@ class MockClipboard { 'text': null }; - Future handleJSONMessage(dynamic message) async { - final String method = message['method']; - final List args= message['args']; - switch (method) { + Future 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. ' + diff --git a/packages/flutter/test/widgets/raw_keyboard_listener_test.dart b/packages/flutter/test/widgets/raw_keyboard_listener_test.dart index dbae73a40a..a6540f24f5 100644 --- a/packages/flutter/test/widgets/raw_keyboard_listener_test.dart +++ b/packages/flutter/test/widgets/raw_keyboard_listener_test.dart @@ -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 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() { diff --git a/packages/flutter_test/lib/src/test_text_input.dart b/packages/flutter_test/lib/src/test_text_input.dart index 7c60e707d1..45cb2ab047 100644 --- a/packages/flutter_test/lib/src/test_text_input.dart +++ b/packages/flutter_test/lib/src/test_text_input.dart @@ -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 editingState; - Future handleJSONMessage(dynamic message) async { - final String method = message['method']; - final List args= message['args']; - switch (method) { + Future 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({ - 'method': 'TextInputClient.updateEditingState', - 'args': [_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', + [_client, state.toJSON()], + ), + ), + (_) {}, + ); } void enterText(String text) {