diff --git a/packages/flutter/lib/src/services/message_codec.dart b/packages/flutter/lib/src/services/message_codec.dart index 063fa75a80..394f6a65f0 100644 --- a/packages/flutter/lib/src/services/message_codec.dart +++ b/packages/flutter/lib/src/services/message_codec.dart @@ -29,7 +29,7 @@ abstract class MessageCodec { /// Decodes the specified [message] from binary. /// /// Returns null if the message is null. - T decodeMessage(ByteData? message); + T? decodeMessage(ByteData? message); } /// An command object representing the invocation of a named method. @@ -73,16 +73,22 @@ abstract class MethodCodec { /// /// Throws [PlatformException], if [envelope] represents an error, otherwise /// returns the enveloped result. + /// + /// The type returned from [decodeEnvelope] is `dynamic` (not `Object?`), + /// which means *no type checking is performed on its return value*. It is + /// strongly recommended that the return value be immediately cast to a known + /// type to prevent runtime errors due to typos that the type checker could + /// otherwise catch. dynamic decodeEnvelope(ByteData envelope); /// Encodes a successful [result] into a binary envelope. - ByteData encodeSuccessEnvelope(dynamic result); + ByteData encodeSuccessEnvelope(Object? 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}); + ByteData encodeErrorEnvelope({ required String code, String? message, Object? details}); } @@ -117,7 +123,7 @@ class PlatformException implements Exception { final String? message; /// Error details, possibly null. - final dynamic details; + final Object? details; /// Native stacktrace for the error, possibly null. /// This is strictly for native platform stacktrace. diff --git a/packages/flutter/lib/src/services/message_codecs.dart b/packages/flutter/lib/src/services/message_codecs.dart index 92f9d722e8..cb5e4ac118 100644 --- a/packages/flutter/lib/src/services/message_codecs.dart +++ b/packages/flutter/lib/src/services/message_codecs.dart @@ -17,7 +17,7 @@ import 'message_codec.dart'; /// When sending outgoing messages from Android, be sure to use direct `ByteBuffer` /// as opposed to indirect. The `wrap()` API provides indirect buffers by default /// and you will get empty `ByteData` objects in Dart. -class BinaryCodec implements MessageCodec { +class BinaryCodec implements MessageCodec { /// Creates a [MessageCodec] with unencoded binary messages represented using /// [ByteData]. const BinaryCodec(); @@ -33,7 +33,7 @@ class BinaryCodec implements MessageCodec { /// /// On Android, messages will be represented using `java.util.String`. /// On iOS, messages will be represented using `NSString`. -class StringCodec implements MessageCodec { +class StringCodec implements MessageCodec { /// Creates a [MessageCodec] with UTF-8 encoded String messages. const StringCodec(); @@ -71,7 +71,13 @@ class StringCodec implements MessageCodec { /// null/nil for null, and identical to what would result from decoding a /// singleton JSON array with a Boolean, number, or string value, and then /// extracting its single element. -class JSONMessageCodec implements MessageCodec { +/// +/// The type returned from [decodeMessage] is `dynamic` (not `Object?`), which +/// means *no type checking is performed on its return value*. It is strongly +/// recommended that the return value be immediately cast to a known type to +/// prevent runtime errors due to typos that the type checker could otherwise +/// catch. +class JSONMessageCodec implements MessageCodec { // The codec serializes messages as defined by the JSON codec of the // dart:convert package. The format used must match the Android and // iOS counterparts. @@ -80,7 +86,7 @@ class JSONMessageCodec implements MessageCodec { const JSONMessageCodec(); @override - ByteData? encodeMessage(dynamic message) { + ByteData? encodeMessage(Object? message) { if (message == null) return null; return const StringCodec().encodeMessage(json.encode(message)); @@ -118,7 +124,7 @@ class JSONMethodCodec implements MethodCodec { @override ByteData encodeMethodCall(MethodCall call) { - return const JSONMessageCodec().encodeMessage({ + return const JSONMessageCodec().encodeMessage({ 'method': call.method, 'args': call.arguments, })!; @@ -126,11 +132,11 @@ class JSONMethodCodec implements MethodCodec { @override MethodCall decodeMethodCall(ByteData? methodCall) { - final dynamic decoded = const JSONMessageCodec().decodeMessage(methodCall); + final Object? decoded = const JSONMessageCodec().decodeMessage(methodCall); if (decoded is! Map) throw FormatException('Expected method call Map, got $decoded'); - final dynamic method = decoded['method']; - final dynamic arguments = decoded['args']; + final Object? method = decoded['method']; + final Object? arguments = decoded['args']; if (method is String) return MethodCall(method, arguments); throw FormatException('Invalid method call: $decoded'); @@ -138,7 +144,7 @@ class JSONMethodCodec implements MethodCodec { @override dynamic decodeEnvelope(ByteData envelope) { - final dynamic decoded = const JSONMessageCodec().decodeMessage(envelope); + final Object? decoded = const JSONMessageCodec().decodeMessage(envelope); if (decoded is! List) throw FormatException('Expected envelope List, got $decoded'); if (decoded.length == 1) @@ -165,14 +171,14 @@ class JSONMethodCodec implements MethodCodec { } @override - ByteData encodeSuccessEnvelope(dynamic result) { - return const JSONMessageCodec().encodeMessage([result])!; + ByteData encodeSuccessEnvelope(Object? result) { + return const JSONMessageCodec().encodeMessage([result])!; } @override - ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}) { + ByteData encodeErrorEnvelope({ required String code, String? message, Object? details}) { assert(code != null); - return const JSONMessageCodec().encodeMessage([code, message, details])!; + return const JSONMessageCodec().encodeMessage([code, message, details])!; } } @@ -188,9 +194,20 @@ class JSONMethodCodec implements MethodCodec { /// * [List]s of supported values /// * [Map]s from supported values to supported values /// -/// Decoded values will use `List` and `Map` +/// Decoded values will use `List` and `Map` /// irrespective of content. /// +/// The type returned from [decodeMessage] is `dynamic` (not `Object?`), which +/// means *no type checking is performed on its return value*. It is strongly +/// recommended that the return value be immediately cast to a known type to +/// prevent runtime errors due to typos that the type checker could otherwise +/// catch. +/// +/// The codec is extensible by subclasses overriding [writeValue] and +/// [readValueOfType]. +/// +/// ## Android specifics +/// /// On Android, messages are represented as follows: /// /// * null: null @@ -206,6 +223,14 @@ class JSONMethodCodec implements MethodCodec { /// * [List]\: `java.util.ArrayList` /// * [Map]\: `java.util.HashMap` /// +/// When sending a `java.math.BigInteger` from Java, it is converted into a +/// [String] with the hexadecimal representation of the integer. (The value is +/// tagged as being a big integer; subclasses of this class could be made to +/// support it natively; see the discussion at [writeValue].) This codec does +/// not support sending big integers from Dart. +/// +/// ## iOS specifics +/// /// On iOS, messages are represented as follows: /// /// * null: nil @@ -218,16 +243,7 @@ class JSONMethodCodec implements MethodCodec { /// `FlutterStandardTypedData` /// * [List]\: `NSArray` /// * [Map]\: `NSDictionary` -/// -/// When sending a `java.math.BigInteger` from Java, it is converted into a -/// [String] with the hexadecimal representation of the integer. (The value is -/// tagged as being a big integer; subclasses of this class could be made to -/// support it natively; see the discussion at [writeValue].) This codec does -/// not support sending big integers from Dart. -/// -/// The codec is extensible by subclasses overriding [writeValue] and -/// [readValueOfType]. -class StandardMessageCodec implements MessageCodec { +class StandardMessageCodec implements MessageCodec { /// Creates a [MessageCodec] using the Flutter standard binary encoding. const StandardMessageCodec(); @@ -289,7 +305,7 @@ class StandardMessageCodec implements MessageCodec { static const int _valueMap = 13; @override - ByteData? encodeMessage(dynamic message) { + ByteData? encodeMessage(Object? message) { if (message == null) return null; final WriteBuffer buffer = WriteBuffer(); @@ -302,7 +318,7 @@ class StandardMessageCodec implements MessageCodec { if (message == null) return null; final ReadBuffer buffer = ReadBuffer(message); - final dynamic result = readValue(buffer); + final Object? result = readValue(buffer); if (buffer.hasRemaining) throw const FormatException('Message corrupted'); return result; @@ -344,7 +360,7 @@ class StandardMessageCodec implements MessageCodec { /// string's length as encoded by [writeSize] followed by the string bytes. On /// Android, that would get converted to a `java.math.BigInteger` object. On /// iOS, the string representation is returned. - void writeValue(WriteBuffer buffer, dynamic value) { + void writeValue(WriteBuffer buffer, Object? value) { if (value == null) { buffer.putUint8(_valueNull); } else if (value is bool) { @@ -389,13 +405,13 @@ class StandardMessageCodec implements MessageCodec { } else if (value is List) { buffer.putUint8(_valueList); writeSize(buffer, value.length); - for (final dynamic item in value) { + for (final Object? item in value) { writeValue(buffer, item); } } else if (value is Map) { buffer.putUint8(_valueMap); writeSize(buffer, value.length); - value.forEach((dynamic key, dynamic value) { + value.forEach((Object? key, Object? value) { writeValue(buffer, key); writeValue(buffer, value); }); @@ -408,7 +424,7 @@ class StandardMessageCodec implements MessageCodec { /// /// This method is intended for use by subclasses overriding /// [readValueOfType]. - dynamic readValue(ReadBuffer buffer) { + Object? readValue(ReadBuffer buffer) { if (!buffer.hasRemaining) throw const FormatException('Message corrupted'); final int type = buffer.getUint8(); @@ -420,7 +436,7 @@ class StandardMessageCodec implements MessageCodec { /// The codec can be extended by overriding this method, calling super for /// types that the extension does not handle. See the discussion at /// [writeValue]. - dynamic readValueOfType(int type, ReadBuffer buffer) { + Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case _valueNull: return null; @@ -452,13 +468,13 @@ class StandardMessageCodec implements MessageCodec { return buffer.getFloat64List(length); case _valueList: final int length = readSize(buffer); - final List result = List.filled(length, null, growable: false); + final List result = List.filled(length, null, growable: false); for (int i = 0; i < length; i++) result[i] = readValue(buffer); return result; case _valueMap: final int length = readSize(buffer); - final Map result = {}; + final Map result = {}; for (int i = 0; i < length; i++) result[readValue(buffer)] = readValue(buffer); return result; @@ -539,8 +555,8 @@ class StandardMethodCodec implements MethodCodec { @override MethodCall decodeMethodCall(ByteData? methodCall) { final ReadBuffer buffer = ReadBuffer(methodCall!); - final dynamic method = messageCodec.readValue(buffer); - final dynamic arguments = messageCodec.readValue(buffer); + final Object? method = messageCodec.readValue(buffer); + final Object? arguments = messageCodec.readValue(buffer); if (method is String && !buffer.hasRemaining) return MethodCall(method, arguments); else @@ -548,7 +564,7 @@ class StandardMethodCodec implements MethodCodec { } @override - ByteData encodeSuccessEnvelope(dynamic result) { + ByteData encodeSuccessEnvelope(Object? result) { final WriteBuffer buffer = WriteBuffer(); buffer.putUint8(0); messageCodec.writeValue(buffer, result); @@ -556,7 +572,7 @@ class StandardMethodCodec implements MethodCodec { } @override - ByteData encodeErrorEnvelope({ required String code, String? message, dynamic details}) { + ByteData encodeErrorEnvelope({ required String code, String? message, Object? details}) { final WriteBuffer buffer = WriteBuffer(); buffer.putUint8(1); messageCodec.writeValue(buffer, code); @@ -573,10 +589,10 @@ class StandardMethodCodec implements MethodCodec { final ReadBuffer buffer = ReadBuffer(envelope); if (buffer.getUint8() == 0) return messageCodec.readValue(buffer); - final dynamic errorCode = messageCodec.readValue(buffer); - final dynamic errorMessage = messageCodec.readValue(buffer); - final dynamic errorDetails = messageCodec.readValue(buffer); - final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String : null; + final Object? errorCode = messageCodec.readValue(buffer); + final Object? errorMessage = messageCodec.readValue(buffer); + final Object? errorDetails = messageCodec.readValue(buffer); + final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String? : null; if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining) throw PlatformException(code: errorCode, message: errorMessage as String?, details: errorDetails, stacktrace: errorStacktrace); else diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart index 3f176c3929..3f9383e9f1 100644 --- a/packages/flutter/lib/src/services/platform_channel.dart +++ b/packages/flutter/lib/src/services/platform_channel.dart @@ -52,7 +52,7 @@ class BasicMessageChannel { /// /// Returns a [Future] which completes to the received response, which may /// be null. - Future send(T message) async { + Future send(T message) async { return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message))); } @@ -65,7 +65,7 @@ class BasicMessageChannel { /// /// The handler's return value is sent back to the platform plugins as a /// message reply. It may be null. - void setMessageHandler(Future Function(T message)? handler) { + void setMessageHandler(Future Function(T? message)? handler) { if (handler == null) { binaryMessenger.setMessageHandler(name, null); } else { @@ -86,7 +86,7 @@ class BasicMessageChannel { /// /// This is intended for testing. Messages intercepted in this manner are not /// sent to platform plugins. - void setMockMessageHandler(Future Function(T message)? handler) { + void setMockMessageHandler(Future Function(T? message)? handler) { if (handler == null) { binaryMessenger.setMockMessageHandler(name, null); } else { diff --git a/packages/flutter/lib/src/services/restoration.dart b/packages/flutter/lib/src/services/restoration.dart index 97b991e9df..22f9ba90d7 100644 --- a/packages/flutter/lib/src/services/restoration.dart +++ b/packages/flutter/lib/src/services/restoration.dart @@ -226,7 +226,7 @@ class RestorationManager extends ChangeNotifier { bool _isReplacing = false; Future _getRootBucketFromEngine() async { - final Map? config = await SystemChannels.restoration.invokeMethod>('get'); + final Map? config = await SystemChannels.restoration.invokeMethod>('get'); if (_pendingRootBucket == null) { // The restoration data was obtained via other means (e.g. by calling // [handleRestorationDataUpdate] while the request to the engine was @@ -237,9 +237,9 @@ class RestorationManager extends ChangeNotifier { _parseAndHandleRestorationUpdateFromEngine(config); } - void _parseAndHandleRestorationUpdateFromEngine(Map? update) { + void _parseAndHandleRestorationUpdateFromEngine(Map? update) { handleRestorationUpdateFromEngine( - enabled: update != null && update['enabled'] as bool, + enabled: update != null && update['enabled']! as bool, data: update == null ? null : update['data'] as Uint8List?, ); } @@ -305,25 +305,25 @@ class RestorationManager extends ChangeNotifier { ); } - Future _methodHandler(MethodCall call) async { + Future _methodHandler(MethodCall call) async { switch (call.method) { case 'push': - _parseAndHandleRestorationUpdateFromEngine(call.arguments as Map); + _parseAndHandleRestorationUpdateFromEngine(call.arguments as Map); break; default: throw UnimplementedError("${call.method} was invoked but isn't implemented by $runtimeType"); } } - Map? _decodeRestorationData(Uint8List? data) { + Map? _decodeRestorationData(Uint8List? data) { if (data == null) { return null; } final ByteData encoded = data.buffer.asByteData(data.offsetInBytes, data.lengthInBytes); - return const StandardMessageCodec().decodeMessage(encoded) as Map; + return const StandardMessageCodec().decodeMessage(encoded) as Map?; } - Uint8List _encodeRestorationData(Map data) { + Uint8List _encodeRestorationData(Map data) { final ByteData encoded = const StandardMessageCodec().encodeMessage(data)!; return encoded.buffer.asUint8List(encoded.offsetInBytes, encoded.lengthInBytes); } @@ -505,7 +505,7 @@ class RestorationBucket { required Object? debugOwner, }) : assert(restorationId != null), _restorationId = restorationId, - _rawData = {} { + _rawData = {} { assert(() { _debugOwner = debugOwner; return true; @@ -537,10 +537,10 @@ class RestorationBucket { /// The `manager` argument must not be null. RestorationBucket.root({ required RestorationManager manager, - required Map? rawData, + required Map? rawData, }) : assert(manager != null), _manager = manager, - _rawData = rawData ?? {}, + _rawData = rawData ?? {}, _restorationId = 'root' { assert(() { _debugOwner = manager; @@ -567,7 +567,7 @@ class RestorationBucket { assert(parent._rawChildren[restorationId] != null), _manager = parent._manager, _parent = parent, - _rawData = parent._rawChildren[restorationId] as Map, + _rawData = parent._rawChildren[restorationId]! as Map, _restorationId = restorationId { assert(() { _debugOwner = debugOwner; @@ -578,7 +578,7 @@ class RestorationBucket { static const String _childrenMapKey = 'c'; static const String _valuesMapKey = 'v'; - final Map _rawData; + final Map _rawData; /// The owner of the bucket that was provided when the bucket was claimed via /// [claimChild]. @@ -616,9 +616,9 @@ class RestorationBucket { String _restorationId; // Maps a restoration ID to the raw map representation of a child bucket. - Map get _rawChildren => _rawData.putIfAbsent(_childrenMapKey, () => {}) as Map; + Map get _rawChildren => _rawData.putIfAbsent(_childrenMapKey, () => {})! as Map; // Maps a restoration ID to a value that is stored in this bucket. - Map get _rawValues => _rawData.putIfAbsent(_valuesMapKey, () => {}) as Map; + Map get _rawValues => _rawData.putIfAbsent(_valuesMapKey, () => {})! as Map; // Get and store values. diff --git a/packages/flutter/test/services/message_codecs_testing.dart b/packages/flutter/test/services/message_codecs_testing.dart index 26e3edd298..5c561bcf50 100644 --- a/packages/flutter/test/services/message_codecs_testing.dart +++ b/packages/flutter/test/services/message_codecs_testing.dart @@ -17,13 +17,13 @@ void checkEncoding(MessageCodec codec, T message, List expectedBytes) void checkEncodeDecode(MessageCodec codec, T message) { final ByteData? encoded = codec.encodeMessage(message); - final T decoded = codec.decodeMessage(encoded); + final T? decoded = codec.decodeMessage(encoded); if (message == null) { expect(encoded, isNull); expect(decoded, isNull); } else { expect(deepEquals(message, decoded), isTrue); - final ByteData? encodedAgain = codec.encodeMessage(decoded); + final ByteData? encodedAgain = codec.encodeMessage(decoded as T); expect( encodedAgain!.buffer.asUint8List(), orderedEquals(encoded!.buffer.asUint8List()), diff --git a/packages/flutter_web_plugins/test/plugin_registry_test.dart b/packages/flutter_web_plugins/test/plugin_registry_test.dart index 7b950d7fdd..a38fb23bdc 100644 --- a/packages/flutter_web_plugins/test/plugin_registry_test.dart +++ b/packages/flutter_web_plugins/test/plugin_registry_test.dart @@ -56,7 +56,7 @@ void main() { final List loggedMessages = []; ServicesBinding.instance!.defaultBinaryMessenger .setMessageHandler('test_send', (ByteData? data) { - loggedMessages.add(codec.decodeMessage(data) as String); + loggedMessages.add(codec.decodeMessage(data)! as String); return Future.value(null); });