From 83af6f48d6d180cf76bf7f6715cd015ac0bf2674 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Thu, 10 Jan 2019 13:21:03 -0800 Subject: [PATCH] Adds a type parameter to invokeMethod (and additional utility methods) (#26303) --- .../android_semantics_testing/lib/main.dart | 2 +- .../android_views/lib/main.dart | 20 ++--- .../channels/lib/src/method_calls.dart | 6 +- .../external_ui/lib/main.dart | 8 +- dev/integration_tests/flavors/lib/main.dart | 2 +- .../flutter_gallery/test/live_smoketest.dart | 4 +- .../flutter/lib/src/services/clipboard.dart | 2 +- .../lib/src/services/haptic_feedback.dart | 10 +-- .../lib/src/services/platform_channel.dart | 79 ++++++++++++++++--- .../lib/src/services/platform_views.dart | 12 +-- .../lib/src/services/system_chrome.dart | 10 +-- .../lib/src/services/system_navigator.dart | 2 +- .../lib/src/services/system_sound.dart | 2 +- .../flutter/lib/src/services/text_input.dart | 10 +-- .../test/services/platform_channel_test.dart | 42 +++++++++- 15 files changed, 149 insertions(+), 62 deletions(-) diff --git a/dev/integration_tests/android_semantics_testing/lib/main.dart b/dev/integration_tests/android_semantics_testing/lib/main.dart index d0f52854ae..0cecafaa02 100644 --- a/dev/integration_tests/android_semantics_testing/lib/main.dart +++ b/dev/integration_tests/android_semantics_testing/lib/main.dart @@ -26,7 +26,7 @@ Future dataHandler(String message) async { final Completer completer = Completer(); final int id = int.tryParse(message.split('#')[1]) ?? 0; Future completeSemantics([Object _]) async { - final dynamic result = await kSemanticsChannel.invokeMethod('getSemanticsNode', { + final dynamic result = await kSemanticsChannel.invokeMethod('getSemanticsNode', { 'id': id, }); completer.complete(json.encode(result)); diff --git a/dev/integration_tests/android_views/lib/main.dart b/dev/integration_tests/android_views/lib/main.dart index a9e79faf26..64250171f0 100644 --- a/dev/integration_tests/android_views/lib/main.dart +++ b/dev/integration_tests/android_views/lib/main.dart @@ -125,15 +125,15 @@ class PlatformViewState extends State { .cast>() .map>((Map e) =>e.cast()) .toList(); - await channel.invokeMethod('pipeFlutterViewEvents'); - await viewChannel.invokeMethod('pipeTouchEvents'); + await channel.invokeMethod('pipeFlutterViewEvents'); + await viewChannel.invokeMethod('pipeTouchEvents'); print('replaying ${recordedEvents.length} motion events'); for (Map event in recordedEvents.reversed) { - await channel.invokeMethod('synthesizeEvent', event); + await channel.invokeMethod('synthesizeEvent', event); } - await channel.invokeMethod('stopFlutterViewEvents'); - await viewChannel.invokeMethod('stopTouchEvents'); + await channel.invokeMethod('stopFlutterViewEvents'); + await viewChannel.invokeMethod('stopTouchEvents'); if (flutterViewEvents.length != embeddedViewEvents.length) return 'Synthesized ${flutterViewEvents.length} events but the embedded view received ${embeddedViewEvents.length} events'; @@ -160,7 +160,7 @@ class PlatformViewState extends State { } Future saveRecordedEvents(ByteData data, BuildContext context) async { - if (!await channel.invokeMethod('getStoragePermission')) { + if (!await channel.invokeMethod('getStoragePermission')) { showMessage( context, 'External storage permissions are required to save events'); return; @@ -190,11 +190,11 @@ class PlatformViewState extends State { } void listenToFlutterViewEvents() { - channel.invokeMethod('pipeFlutterViewEvents'); - viewChannel.invokeMethod('pipeTouchEvents'); + channel.invokeMethod('pipeFlutterViewEvents'); + viewChannel.invokeMethod('pipeTouchEvents'); Timer(const Duration(seconds: 3), () { - channel.invokeMethod('stopFlutterViewEvents'); - viewChannel.invokeMethod('stopTouchEvents'); + channel.invokeMethod('stopFlutterViewEvents'); + viewChannel.invokeMethod('stopTouchEvents'); }); } diff --git a/dev/integration_tests/channels/lib/src/method_calls.dart b/dev/integration_tests/channels/lib/src/method_calls.dart index 5b81044108..bdbd98c699 100644 --- a/dev/integration_tests/channels/lib/src/method_calls.dart +++ b/dev/integration_tests/channels/lib/src/method_calls.dart @@ -67,7 +67,7 @@ Future _methodCallSuccessHandshake( dynamic result = nothing; dynamic error = nothing; try { - result = await channel.invokeMethod('success', arguments); + result = await channel.invokeMethod('success', arguments); } catch (e) { error = e; } @@ -95,7 +95,7 @@ Future _methodCallErrorHandshake( dynamic errorDetails = nothing; dynamic error = nothing; try { - error = await channel.invokeMethod('error', arguments); + error = await channel.invokeMethod('error', arguments); } on PlatformException catch (e) { errorDetails = e.details; } catch (e) { @@ -123,7 +123,7 @@ Future _methodCallNotImplementedHandshake( dynamic result = nothing; dynamic error = nothing; try { - error = await channel.invokeMethod('notImplemented'); + error = await channel.invokeMethod('notImplemented'); } on MissingPluginException { result = null; } catch (e) { diff --git a/dev/integration_tests/external_ui/lib/main.dart b/dev/integration_tests/external_ui/lib/main.dart index 454083a4bf..166760359c 100644 --- a/dev/integration_tests/external_ui/lib/main.dart +++ b/dev/integration_tests/external_ui/lib/main.dart @@ -47,11 +47,11 @@ Widget builds: $_widgetBuilds'''; _summary = 'Producing texture frames at .5x speed...'; _state = FrameState.slow; _icon = Icons.stop; - channel.invokeMethod('start', _flutterFrameRate ~/ 2); + channel.invokeMethod('start', _flutterFrameRate ~/ 2); break; case FrameState.slow: debugPrint('Stopping .5x speed test...'); - await channel.invokeMethod('stop'); + await channel.invokeMethod('stop'); await _summarizeStats(); _icon = Icons.fast_forward; _state = FrameState.afterSlow; @@ -62,11 +62,11 @@ Widget builds: $_widgetBuilds'''; _summary = 'Producing texture frames at 2x speed...'; _state = FrameState.fast; _icon = Icons.stop; - channel.invokeMethod('start', (_flutterFrameRate * 2).toInt()); + channel.invokeMethod('start', (_flutterFrameRate * 2).toInt()); break; case FrameState.fast: debugPrint('Stopping 2x speed test...'); - await channel.invokeMethod('stop'); + await channel.invokeMethod('stop'); await _summarizeStats(); _state = FrameState.afterFast; _icon = Icons.replay; diff --git a/dev/integration_tests/flavors/lib/main.dart b/dev/integration_tests/flavors/lib/main.dart index b838200623..f0018a3ca9 100644 --- a/dev/integration_tests/flavors/lib/main.dart +++ b/dev/integration_tests/flavors/lib/main.dart @@ -18,7 +18,7 @@ class _FlavorState extends State { @override void initState() { super.initState(); - const MethodChannel('flavor').invokeMethod('getFlavor').then((Object flavor) { + const MethodChannel('flavor').invokeMethod('getFlavor').then((String flavor) { setState(() { _flavor = flavor; }); diff --git a/examples/flutter_gallery/test/live_smoketest.dart b/examples/flutter_gallery/test/live_smoketest.dart index 8f2977c806..a3c6952d8f 100644 --- a/examples/flutter_gallery/test/live_smoketest.dart +++ b/examples/flutter_gallery/test/live_smoketest.dart @@ -82,10 +82,10 @@ Future main() async { await controller.tap(find.byTooltip('Back')); } print('Finished successfully!'); - _kTestChannel.invokeMethod('success'); + _kTestChannel.invokeMethod('success'); } catch (error, stack) { print('Caught error: $error\n$stack'); - _kTestChannel.invokeMethod('failure'); + _kTestChannel.invokeMethod('failure'); } } diff --git a/packages/flutter/lib/src/services/clipboard.dart b/packages/flutter/lib/src/services/clipboard.dart index aba3761a85..f1e2d5134b 100644 --- a/packages/flutter/lib/src/services/clipboard.dart +++ b/packages/flutter/lib/src/services/clipboard.dart @@ -34,7 +34,7 @@ class Clipboard { /// Stores the given clipboard data on the clipboard. static Future setData(ClipboardData data) async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'Clipboard.setData', { 'text': data.text, diff --git a/packages/flutter/lib/src/services/haptic_feedback.dart b/packages/flutter/lib/src/services/haptic_feedback.dart index f2759aeba4..8b7c64ac8c 100644 --- a/packages/flutter/lib/src/services/haptic_feedback.dart +++ b/packages/flutter/lib/src/services/haptic_feedback.dart @@ -21,7 +21,7 @@ class HapticFeedback { /// On Android, this uses the platform haptic feedback API to simulate a /// response to a long press (`HapticFeedbackConstants.LONG_PRESS`). static Future vibrate() async { - await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate'); + await SystemChannels.platform.invokeMethod('HapticFeedback.vibrate'); } /// Provides a haptic feedback corresponding a collision impact with a light mass. @@ -32,7 +32,7 @@ class HapticFeedback { /// /// On Android, this uses `HapticFeedbackConstants.VIRTUAL_KEY`. static Future lightImpact() async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'HapticFeedback.vibrate', 'HapticFeedbackType.lightImpact', ); @@ -46,7 +46,7 @@ class HapticFeedback { /// /// On Android, this uses `HapticFeedbackConstants.KEYBOARD_TAP`. static Future mediumImpact() async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'HapticFeedback.vibrate', 'HapticFeedbackType.mediumImpact', ); @@ -61,7 +61,7 @@ class HapticFeedback { /// On Android, this uses `HapticFeedbackConstants.CONTEXT_CLICK` on API levels /// 23 and above. This call has no effects on Android API levels below 23. static Future heavyImpact() async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'HapticFeedback.vibrate', 'HapticFeedbackType.heavyImpact', ); @@ -74,7 +74,7 @@ class HapticFeedback { /// /// On Android, this uses `HapticFeedbackConstants.CLOCK_TICK`. static Future selectionClick() async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'HapticFeedback.vibrate', 'HapticFeedbackType.selectionClick', ); diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart index ff2b8f8bd0..bf80a47576 100644 --- a/packages/flutter/lib/src/services/platform_channel.dart +++ b/packages/flutter/lib/src/services/platform_channel.dart @@ -128,6 +128,12 @@ class MethodChannel { /// result. The values supported by the default codec and their platform-specific /// counterparts are documented with [StandardMessageCodec]. /// + /// The generic argument `T` of the method can be inferred by the surrounding + /// context, or provided explicitly. If it does not match the returned type of + /// the channel, a [TypeError] will be thrown at runtime. `T` cannot be a class + /// with generics other than `dynamic`. For example, `Map` + /// is not supported but `Map` or `Map` is. + /// /// Returns a [Future] which completes to one of the following: /// /// * a result (possibly null), on successful invocation; @@ -149,10 +155,9 @@ class MethodChannel { /// static const MethodChannel _channel = MethodChannel('music'); /// /// static Future isLicensed() async { - /// // invokeMethod returns a Future, and we cannot pass that for - /// // a Future, hence the indirection. - /// final bool result = await _channel.invokeMethod('isLicensed'); - /// return result; + /// // invokeMethod returns a Future which can be inferred as bool + /// // in this context. + /// return _channel.invokeMethod('isLicensed'); /// } /// /// static Future> songs() async { @@ -160,6 +165,7 @@ class MethodChannel { /// // List with Map entries. Post-processing /// // code thus cannot assume e.g. List> even though /// // the actual values involved would support such a typed container. + /// // The correct type cannot be inferred with any value of `T`. /// final List songs = await _channel.invokeMethod('getSongs'); /// return songs.map(Song.fromJson).toList(); /// } @@ -168,7 +174,7 @@ class MethodChannel { /// // Errors occurring on the platform side cause invokeMethod to throw /// // PlatformExceptions. /// try { - /// await _channel.invokeMethod('play', { + /// return _channel.invokeMethod('play', { /// 'song': song.id, /// 'volume': volume, /// }); @@ -275,21 +281,54 @@ class MethodChannel { /// /// See also: /// + /// * [invokeListMethod], for automatically returning typed lists. + /// * [invokeMapMethod], for automatically returning typed maps. /// * [StandardMessageCodec] which defines the payload values supported by /// [StandardMethodCodec]. /// * [JSONMessageCodec] which defines the payload values supported by /// [JSONMethodCodec]. /// * /// for how to access method call arguments on Android. - Future invokeMethod(String method, [dynamic arguments]) async { + @optionalTypeArgs + Future invokeMethod(String method, [dynamic arguments]) async { assert(method != null); - final dynamic result = await BinaryMessages.send( + final ByteData result = await BinaryMessages.send( name, codec.encodeMethodCall(MethodCall(method, arguments)), ); - if (result == null) + if (result == null) { throw MissingPluginException('No implementation found for method $method on channel $name'); - return codec.decodeEnvelope(result); + } + final T typedResult = codec.decodeEnvelope(result); + return typedResult; + } + + /// An implementation of [invokeMethod] that can return typed lists. + /// + /// Dart generics are reified, meaning that an untyped List + /// cannot masquerade as a List. Since invokeMethod can only return + /// dynamic maps, we instead create a new typed list using [List.cast]. + /// + /// See also: + /// + /// * [invokeMethod], which this call delegates to. + Future> invokeListMethod(String method, [dynamic arguments]) async { + final List result = await invokeMethod>(method, arguments); + return result.cast(); + } + + /// An implementation of [invokeMethod] that can return typed maps. + /// + /// Dart generics are reified, meaning that an untyped Map + /// cannot masquerade as a Map. Since invokeMethod can only return + /// dynamic maps, we instead create a new typed map using [Map.cast]. + /// + /// See also: + /// + /// * [invokeMethod], which this call delegates to. + Future> invokeMapMethod(String method, [dynamic arguments]) async { + final Map result = await invokeMethod>(method, arguments); + return result.cast(); } /// Sets a callback for receiving method calls on this channel. @@ -366,13 +405,27 @@ class OptionalMethodChannel extends MethodChannel { : super(name, codec); @override - Future invokeMethod(String method, [dynamic arguments]) async { + Future invokeMethod(String method, [dynamic arguments]) async { try { - return await super.invokeMethod(method, arguments); + final T result = await super.invokeMethod(method, arguments); + return result; } on MissingPluginException { return null; } } + + @override + Future> invokeListMethod(String method, [dynamic arguments]) async { + final List result = await invokeMethod>(method, arguments); + return result.cast(); + } + + @override + Future> invokeMapMethod(String method, [dynamic arguments]) async { + final Map result = await invokeMethod>(method, arguments); + return result.cast(); + } + } /// A named channel for communicating with platform plugins using event streams. @@ -434,7 +487,7 @@ class EventChannel { return null; }); try { - await methodChannel.invokeMethod('listen', arguments); + await methodChannel.invokeMethod('listen', arguments); } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, @@ -446,7 +499,7 @@ class EventChannel { }, onCancel: () async { BinaryMessages.setMessageHandler(name, null); try { - await methodChannel.invokeMethod('cancel', arguments); + await methodChannel.invokeMethod('cancel', arguments); } catch (exception, stack) { FlutterError.reportError(FlutterErrorDetails( exception: exception, diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart index 99985b8d33..37dd8be14f 100644 --- a/packages/flutter/lib/src/services/platform_views.dart +++ b/packages/flutter/lib/src/services/platform_views.dart @@ -129,7 +129,7 @@ class PlatformViewsService { paramsByteData.lengthInBytes, ); } - await SystemChannels.platform_views.invokeMethod('create', args); + await SystemChannels.platform_views.invokeMethod('create', args); return UiKitViewController._(id, layoutDirection); } } @@ -485,7 +485,7 @@ class AndroidViewController { /// disposed. Future dispose() async { if (_state == _AndroidViewState.creating || _state == _AndroidViewState.created) - await SystemChannels.platform_views.invokeMethod('dispose', id); + await SystemChannels.platform_views.invokeMethod('dispose', id); _state = _AndroidViewState.disposed; } @@ -504,7 +504,7 @@ class AndroidViewController { if (_state == _AndroidViewState.waitingForSize) return _create(size); - await SystemChannels.platform_views.invokeMethod('resize', { + await SystemChannels.platform_views.invokeMethod('resize', { 'id': id, 'width': size.width, 'height': size.height, @@ -526,7 +526,7 @@ class AndroidViewController { if (_state == _AndroidViewState.waitingForSize) return; - await SystemChannels.platform_views.invokeMethod('setDirection', { + await SystemChannels.platform_views.invokeMethod('setDirection', { 'id': id, 'direction': _getAndroidDirection(layoutDirection), }); @@ -550,7 +550,7 @@ class AndroidViewController { /// See documentation of [MotionEvent.obtain](https://developer.android.com/reference/android/view/MotionEvent.html#obtain(long,%20long,%20int,%20float,%20float,%20float,%20float,%20int,%20float,%20float,%20int,%20int)) /// for description of the parameters. Future sendMotionEvent(AndroidMotionEvent event) async { - await SystemChannels.platform_views.invokeMethod( + await SystemChannels.platform_views.invokeMethod( 'touch', event._asList(id), ); @@ -649,6 +649,6 @@ class UiKitViewController { /// disposed. Future dispose() async { _debugDisposed = true; - await SystemChannels.platform_views.invokeMethod('dispose', id); + await SystemChannels.platform_views.invokeMethod('dispose', id); } } diff --git a/packages/flutter/lib/src/services/system_chrome.dart b/packages/flutter/lib/src/services/system_chrome.dart index 1bafea4388..6aeb99fa81 100644 --- a/packages/flutter/lib/src/services/system_chrome.dart +++ b/packages/flutter/lib/src/services/system_chrome.dart @@ -238,7 +238,7 @@ class SystemChrome { /// The empty list causes the application to defer to the operating system /// default. static Future setPreferredOrientations(List orientations) async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'SystemChrome.setPreferredOrientations', _stringify(orientations), ); @@ -250,7 +250,7 @@ class SystemChrome { /// Any part of the description that is unsupported on the current platform /// will be ignored. static Future setApplicationSwitcherDescription(ApplicationSwitcherDescription description) async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'SystemChrome.setApplicationSwitcherDescription', { 'label': description.label, @@ -282,7 +282,7 @@ class SystemChrome { /// or calling this again. Otherwise, the original UI overlay settings will be /// automatically restored only when the application loses and regains focus. static Future setEnabledSystemUIOverlays(List overlays) async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'SystemChrome.setEnabledSystemUIOverlays', _stringify(overlays), ); @@ -298,7 +298,7 @@ class SystemChrome { /// On Android, the system UI cannot be changed until 1 second after the previous /// change. This is to prevent malware from permanently hiding navigation buttons. static Future restoreSystemUIOverlays() async { - await SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'SystemChrome.restoreSystemUIOverlays', null, ); @@ -349,7 +349,7 @@ class SystemChrome { scheduleMicrotask(() { assert(_pendingStyle != null); if (_pendingStyle != _latestStyle) { - SystemChannels.platform.invokeMethod( + SystemChannels.platform.invokeMethod( 'SystemChrome.setSystemUIOverlayStyle', _pendingStyle._toMap(), ); diff --git a/packages/flutter/lib/src/services/system_navigator.dart b/packages/flutter/lib/src/services/system_navigator.dart index a93867335f..5922f8f984 100644 --- a/packages/flutter/lib/src/services/system_navigator.dart +++ b/packages/flutter/lib/src/services/system_navigator.dart @@ -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 SystemChannels.platform.invokeMethod('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 528964a795..805fe1f01b 100644 --- a/packages/flutter/lib/src/services/system_sound.dart +++ b/packages/flutter/lib/src/services/system_sound.dart @@ -20,7 +20,7 @@ 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 SystemChannels.platform.invokeMethod( + await SystemChannels.platform.invokeMethod( 'SystemSound.play', type.toString(), ); diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 2afcd18198..20b36d2ddd 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -626,13 +626,13 @@ class TextInputConnection { /// Requests that the text input control become visible. void show() { assert(attached); - SystemChannels.textInput.invokeMethod('TextInput.show'); + SystemChannels.textInput.invokeMethod('TextInput.show'); } /// Requests that the text input control change its internal state to match the given state. void setEditingState(TextEditingValue value) { assert(attached); - SystemChannels.textInput.invokeMethod( + SystemChannels.textInput.invokeMethod( 'TextInput.setEditingState', value.toJSON(), ); @@ -644,7 +644,7 @@ class TextInputConnection { /// other client attaches to it within this animation frame. void close() { if (attached) { - SystemChannels.textInput.invokeMethod('TextInput.clearClient'); + SystemChannels.textInput.invokeMethod('TextInput.clearClient'); _clientHandler .._currentConnection = null .._scheduleHide(); @@ -749,7 +749,7 @@ class _TextInputClientHandler { scheduleMicrotask(() { _hidePending = false; if (_currentConnection == null) - SystemChannels.textInput.invokeMethod('TextInput.hide'); + SystemChannels.textInput.invokeMethod('TextInput.hide'); }); } } @@ -802,7 +802,7 @@ class TextInput { assert(_debugEnsureInputActionWorksOnPlatform(configuration.inputAction)); final TextInputConnection connection = TextInputConnection._(client); _clientHandler._currentConnection = connection; - SystemChannels.textInput.invokeMethod( + SystemChannels.textInput.invokeMethod( 'TextInput.setClient', [ connection._id, configuration.toJson() ], ); diff --git a/packages/flutter/test/services/platform_channel_test.dart b/packages/flutter/test/services/platform_channel_test.dart index 572454b0db..c9d218b2f8 100644 --- a/packages/flutter/test/services/platform_channel_test.dart +++ b/packages/flutter/test/services/platform_channel_test.dart @@ -43,15 +43,49 @@ void main() { 'ch7', (ByteData message) async { final Map methodCall = jsonMessage.decodeMessage(message); - if (methodCall['method'] == 'sayHello') + if (methodCall['method'] == 'sayHello') { return jsonMessage.encodeMessage(['${methodCall['args']} world']); - else + } else { return jsonMessage.encodeMessage(['unknown', null, null]); + } }, ); final String result = await channel.invokeMethod('sayHello', 'hello'); expect(result, equals('hello world')); }); + test('can invoke list method and get result', () async { + BinaryMessages.setMockMessageHandler( + 'ch7', + (ByteData message) async { + final Map methodCall = jsonMessage.decodeMessage(message); + if (methodCall['method'] == 'sayHello') { + return jsonMessage.encodeMessage([['${methodCall['args']}', 'world']]); + } else { + return jsonMessage.encodeMessage(['unknown', null, null]); + } + }, + ); + expect(channel.invokeMethod>('sayHello', 'hello'), throwsA(isInstanceOf())); + expect(await channel.invokeListMethod('sayHello', 'hello'), ['hello', 'world']); + }); + + + test('can invoke map method and get result', () async { + BinaryMessages.setMockMessageHandler( + 'ch7', + (ByteData message) async { + final Map methodCall = jsonMessage.decodeMessage(message); + if (methodCall['method'] == 'sayHello') { + return jsonMessage.encodeMessage([{'${methodCall['args']}': 'world'}]); + } else { + return jsonMessage.encodeMessage(['unknown', null, null]); + } + }, + ); + expect(channel.invokeMethod>('sayHello', 'hello'), throwsA(isInstanceOf())); + expect(await channel.invokeMapMethod('sayHello', 'hello'), {'hello': 'world'}); + }); + test('can invoke method and get error', () async { BinaryMessages.setMockMessageHandler( 'ch7', @@ -64,7 +98,7 @@ void main() { }, ); try { - await channel.invokeMethod('sayHello', 'hello'); + await channel.invokeMethod('sayHello', 'hello'); fail('Exception expected'); } on PlatformException catch (e) { expect(e.code, equals('bad')); @@ -80,7 +114,7 @@ void main() { (ByteData message) async => null, ); try { - await channel.invokeMethod('sayHello', 'hello'); + await channel.invokeMethod('sayHello', 'hello'); fail('Exception expected'); } on MissingPluginException catch (e) { expect(e.message, contains('sayHello'));