Unverified Commit 340c6803 authored by Mikkel Nygaard Ravn's avatar Mikkel Nygaard Ravn Committed by GitHub

Make standard codecs extensible (#15414)

parent 2d5ebd2a
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
package com.yourcompany.channels; package com.yourcompany.channels;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Date;
import android.os.Bundle; import android.os.Bundle;
...@@ -20,9 +22,9 @@ public class MainActivity extends FlutterActivity { ...@@ -20,9 +22,9 @@ public class MainActivity extends FlutterActivity {
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "json-msg", JSONMessageCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "json-msg", JSONMessageCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "std-msg", StandardMessageCodec.INSTANCE)); setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "std-msg", ExtendedStandardMessageCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "json-method", JSONMethodCodec.INSTANCE)); setupMethodHandshake(new MethodChannel(getFlutterView(), "json-method", JSONMethodCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", StandardMethodCodec.INSTANCE)); setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", new StandardMethodCodec(ExtendedStandardMessageCodec.INSTANCE)));
} }
private <T> void setupMessageHandshake(final BasicMessageChannel<T> channel) { private <T> void setupMessageHandshake(final BasicMessageChannel<T> channel) {
...@@ -135,3 +137,49 @@ public class MainActivity extends FlutterActivity { ...@@ -135,3 +137,49 @@ public class MainActivity extends FlutterActivity {
}); });
} }
} }
final class ExtendedStandardMessageCodec extends StandardMessageCodec {
public static final ExtendedStandardMessageCodec INSTANCE = new ExtendedStandardMessageCodec();
private static final byte DATE = (byte) 128;
private static final byte PAIR = (byte) 129;
@Override
protected void writeValue(ByteArrayOutputStream stream, Object value) {
if (value instanceof Date) {
stream.write(DATE);
writeLong(stream, ((Date) value).getTime());
} else if (value instanceof Pair) {
stream.write(PAIR);
writeValue(stream, ((Pair) value).left);
writeValue(stream, ((Pair) value).right);
} else {
super.writeValue(stream, value);
}
}
@Override
protected Object readValueOfType(byte type, ByteBuffer buffer) {
switch (type) {
case DATE:
return new Date(buffer.getLong());
case PAIR:
return new Pair(readValue(buffer), readValue(buffer));
default: return super.readValueOfType(type, buffer);
}
}
}
final class Pair {
public final Object left;
public final Object right;
public Pair(Object left, Object right) {
this.left = left;
this.right = right;
}
@Override
public String toString() {
return "Pair[" + left + ", " + right + "]";
}
}
...@@ -5,6 +5,82 @@ ...@@ -5,6 +5,82 @@
#include "AppDelegate.h" #include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h" #include "GeneratedPluginRegistrant.h"
@interface Pair : NSObject
@property(atomic, readonly, strong, nullable) NSObject* left;
@property(atomic, readonly, strong, nullable) NSObject* right;
- (instancetype)initWithLeft:(NSObject*)first right:(NSObject*)right;
@end
@implementation Pair
- (instancetype)initWithLeft:(NSObject*)left right:(NSObject*)right {
self = [super init];
_left = left;
_right = right;
return self;
}
@end
const UInt8 DATE = 128;
const UInt8 PAIR = 129;
@interface ExtendedWriter : FlutterStandardWriter
- (void)writeValue:(id)value;
@end
@implementation ExtendedWriter
- (void)writeValue:(id)value {
if ([value isKindOfClass:[NSDate class]]) {
[self writeByte:DATE];
NSDate* date = value;
NSTimeInterval time = date.timeIntervalSince1970;
SInt64 ms = (SInt64) (time * 1000.0);
[self writeBytes:&ms length:8];
} else if ([value isKindOfClass:[Pair class]]) {
Pair* pair = value;
[self writeByte:PAIR];
[self writeValue:pair.left];
[self writeValue:pair.right];
} else {
[super writeValue:value];
}
}
@end
@interface ExtendedReader : FlutterStandardReader
- (id)readValueOfType:(UInt8)type;
@end
@implementation ExtendedReader
- (id)readValueOfType:(UInt8)type {
switch (type) {
case DATE: {
SInt64 value;
[self readBytes:&value length:8];
NSTimeInterval time = [NSNumber numberWithLong:value].doubleValue / 1000.0;
return [NSDate dateWithTimeIntervalSince1970:time];
}
case PAIR: {
return [[Pair alloc] initWithLeft:[self readValue] right:[self readValue]];
}
default: return [super readValueOfType:type];
}
}
@end
@interface ExtendedReaderWriter : FlutterStandardReaderWriter
- (FlutterStandardWriter*)writerWithData:(NSMutableData*)data;
- (FlutterStandardReader*)readerWithData:(NSData*)data;
@end
@implementation ExtendedReaderWriter
- (FlutterStandardWriter*)writerWithData:(NSMutableData*)data {
return [[ExtendedWriter alloc] initWithData:data];
}
- (FlutterStandardReader*)readerWithData:(NSData*)data {
return [[ExtendedReader alloc] initWithData:data];
}
@end
@implementation AppDelegate @implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...@@ -13,6 +89,7 @@ ...@@ -13,6 +89,7 @@
FlutterViewController *flutterController = FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController; (FlutterViewController *)self.window.rootViewController;
ExtendedReaderWriter* extendedReaderWriter = [ExtendedReaderWriter new];
[self setupMessagingHandshakeOnChannel: [self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"binary-msg" [FlutterBasicMessageChannel messageChannelWithName:@"binary-msg"
binaryMessenger:flutterController binaryMessenger:flutterController
...@@ -28,7 +105,7 @@ ...@@ -28,7 +105,7 @@
[self setupMessagingHandshakeOnChannel: [self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"std-msg" [FlutterBasicMessageChannel messageChannelWithName:@"std-msg"
binaryMessenger:flutterController binaryMessenger:flutterController
codec:[FlutterStandardMessageCodec sharedInstance]]]; codec:[FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter]]];
[self setupMethodCallSuccessHandshakeOnChannel: [self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"json-method" [FlutterMethodChannel methodChannelWithName:@"json-method"
binaryMessenger:flutterController binaryMessenger:flutterController
...@@ -36,7 +113,7 @@ ...@@ -36,7 +113,7 @@
[self setupMethodCallSuccessHandshakeOnChannel: [self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"std-method" [FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec sharedInstance]]]; codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
return [super application:application didFinishLaunchingWithOptions:launchOptions]; return [super application:application didFinishLaunchingWithOptions:launchOptions];
} }
......
...@@ -10,6 +10,7 @@ import 'package:flutter_driver/driver_extension.dart'; ...@@ -10,6 +10,7 @@ import 'package:flutter_driver/driver_extension.dart';
import 'src/basic_messaging.dart'; import 'src/basic_messaging.dart';
import 'src/method_calls.dart'; import 'src/method_calls.dart';
import 'src/pair.dart';
import 'src/test_step.dart'; import 'src/test_step.dart';
void main() { void main() {
...@@ -23,6 +24,7 @@ class TestApp extends StatefulWidget { ...@@ -23,6 +24,7 @@ class TestApp extends StatefulWidget {
} }
class _TestAppState extends State<TestApp> { class _TestAppState extends State<TestApp> {
static final dynamic anUnknownValue = new DateTime.fromMillisecondsSinceEpoch(1520777802314);
static final List<dynamic> aList = <dynamic>[ static final List<dynamic> aList = <dynamic>[
false, false,
0, 0,
...@@ -39,7 +41,7 @@ class _TestAppState extends State<TestApp> { ...@@ -39,7 +41,7 @@ class _TestAppState extends State<TestApp> {
'd': 'hello', 'd': 'hello',
'e': <dynamic>[ 'e': <dynamic>[
<String, dynamic>{'key': 42} <String, dynamic>{'key': 42}
] ],
}; };
static final Uint8List someUint8s = new Uint8List.fromList(<int>[ static final Uint8List someUint8s = new Uint8List.fromList(<int>[
0xBA, 0xBA,
...@@ -69,6 +71,10 @@ class _TestAppState extends State<TestApp> { ...@@ -69,6 +71,10 @@ class _TestAppState extends State<TestApp> {
double.maxFinite, double.maxFinite,
double.infinity, double.infinity,
]); ]);
static final dynamic aCompoundUnknownValue = <dynamic>[
anUnknownValue,
new Pair(anUnknownValue, aList),
];
static final List<TestStep> steps = <TestStep>[ static final List<TestStep> steps = <TestStep>[
() => methodCallJsonSuccessHandshake(null), () => methodCallJsonSuccessHandshake(null),
() => methodCallJsonSuccessHandshake(true), () => methodCallJsonSuccessHandshake(true),
...@@ -83,6 +89,8 @@ class _TestAppState extends State<TestApp> { ...@@ -83,6 +89,8 @@ class _TestAppState extends State<TestApp> {
() => methodCallStandardSuccessHandshake('world'), () => methodCallStandardSuccessHandshake('world'),
() => methodCallStandardSuccessHandshake(aList), () => methodCallStandardSuccessHandshake(aList),
() => methodCallStandardSuccessHandshake(aMap), () => methodCallStandardSuccessHandshake(aMap),
() => methodCallStandardSuccessHandshake(anUnknownValue),
() => methodCallStandardSuccessHandshake(aCompoundUnknownValue),
() => methodCallJsonErrorHandshake(null), () => methodCallJsonErrorHandshake(null),
() => methodCallJsonErrorHandshake('world'), () => methodCallJsonErrorHandshake('world'),
() => methodCallStandardErrorHandshake(null), () => methodCallStandardErrorHandshake(null),
...@@ -138,6 +146,8 @@ class _TestAppState extends State<TestApp> { ...@@ -138,6 +146,8 @@ class _TestAppState extends State<TestApp> {
() => basicStandardHandshake(<String, dynamic>{}), () => basicStandardHandshake(<String, dynamic>{}),
() => basicStandardHandshake(<dynamic, dynamic>{7: true, false: -7}), () => basicStandardHandshake(<dynamic, dynamic>{7: true, false: -7}),
() => basicStandardHandshake(aMap), () => basicStandardHandshake(aMap),
() => basicStandardHandshake(anUnknownValue),
() => basicStandardHandshake(aCompoundUnknownValue),
() => basicBinaryMessageToUnknownChannel(), () => basicBinaryMessageToUnknownChannel(),
() => basicStringMessageToUnknownChannel(), () => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(), () => basicJsonMessageToUnknownChannel(),
......
...@@ -5,8 +5,42 @@ ...@@ -5,8 +5,42 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'pair.dart';
import 'test_step.dart'; import 'test_step.dart';
class ExtendedStandardMessageCodec extends StandardMessageCodec {
const ExtendedStandardMessageCodec();
static const int _kDateTime = 128;
static const int _kPair = 129;
@override
void writeValue(WriteBuffer buffer, dynamic value) {
if (value is DateTime) {
buffer.putUint8(_kDateTime);
buffer.putInt64(value.millisecondsSinceEpoch);
} else if (value is Pair) {
buffer.putUint8(_kPair);
writeValue(buffer, value.left);
writeValue(buffer, value.right);
} else {
super.writeValue(buffer, value);
}
}
@override
dynamic readValueOfType(int type, ReadBuffer buffer) {
switch (type) {
case _kDateTime:
return new DateTime.fromMillisecondsSinceEpoch(buffer.getInt64());
case _kPair:
return new Pair(readValue(buffer), readValue(buffer));
default: return super.readValueOfType(type, buffer);
}
}
}
Future<TestStepResult> basicBinaryHandshake(ByteData message) async { Future<TestStepResult> basicBinaryHandshake(ByteData message) async {
const BasicMessageChannel<ByteData> channel = const BasicMessageChannel<ByteData> channel =
const BasicMessageChannel<ByteData>( const BasicMessageChannel<ByteData>(
...@@ -38,7 +72,7 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async { ...@@ -38,7 +72,7 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async {
const BasicMessageChannel<dynamic> channel = const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>( const BasicMessageChannel<dynamic>(
'std-msg', 'std-msg',
const StandardMessageCodec(), const ExtendedStandardMessageCodec(),
); );
return _basicMessageHandshake<dynamic>( return _basicMessageHandshake<dynamic>(
'Standard >${toString(message)}<', channel, message); 'Standard >${toString(message)}<', channel, message);
...@@ -74,7 +108,7 @@ Future<TestStepResult> basicStandardMessageToUnknownChannel() async { ...@@ -74,7 +108,7 @@ Future<TestStepResult> basicStandardMessageToUnknownChannel() async {
const BasicMessageChannel<dynamic> channel = const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>( const BasicMessageChannel<dynamic>(
'std-unknown', 'std-unknown',
const StandardMessageCodec(), const ExtendedStandardMessageCodec(),
); );
return _basicMessageToUnknownChannel<dynamic>('Standard', channel); return _basicMessageToUnknownChannel<dynamic>('Standard', channel);
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'basic_messaging.dart';
import 'test_step.dart'; import 'test_step.dart';
Future<TestStepResult> methodCallJsonSuccessHandshake(dynamic payload) async { Future<TestStepResult> methodCallJsonSuccessHandshake(dynamic payload) async {
...@@ -27,22 +28,28 @@ Future<TestStepResult> methodCallJsonNotImplementedHandshake() async { ...@@ -27,22 +28,28 @@ Future<TestStepResult> methodCallJsonNotImplementedHandshake() async {
Future<TestStepResult> methodCallStandardSuccessHandshake( Future<TestStepResult> methodCallStandardSuccessHandshake(
dynamic payload) async { dynamic payload) async {
const MethodChannel channel = const MethodChannel channel = const MethodChannel(
const MethodChannel('std-method', const StandardMethodCodec()); 'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallSuccessHandshake( return _methodCallSuccessHandshake(
'Standard success($payload)', channel, payload); 'Standard success($payload)', channel, payload);
} }
Future<TestStepResult> methodCallStandardErrorHandshake(dynamic payload) async { Future<TestStepResult> methodCallStandardErrorHandshake(dynamic payload) async {
const MethodChannel channel = const MethodChannel channel = const MethodChannel(
const MethodChannel('std-method', const StandardMethodCodec()); 'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallErrorHandshake( return _methodCallErrorHandshake(
'Standard error($payload)', channel, payload); 'Standard error($payload)', channel, payload);
} }
Future<TestStepResult> methodCallStandardNotImplementedHandshake() async { Future<TestStepResult> methodCallStandardNotImplementedHandshake() async {
const MethodChannel channel = const MethodChannel channel = const MethodChannel(
const MethodChannel('std-method', const StandardMethodCodec()); 'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallNotImplementedHandshake( return _methodCallNotImplementedHandshake(
'Standard notImplemented()', channel); 'Standard notImplemented()', channel);
} }
......
// Copyright 2018 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.
/// A pair of values. Used for testing custom codecs.
class Pair {
final dynamic left;
final dynamic right;
Pair(this.left, this.right);
@override
String toString() => 'Pair[$left, $right]';
}
...@@ -7,6 +7,8 @@ import 'dart:typed_data'; ...@@ -7,6 +7,8 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'pair.dart';
enum TestStatus { ok, pending, failed, complete } enum TestStatus { ok, pending, failed, complete }
typedef Future<TestStepResult> TestStep(); typedef Future<TestStepResult> TestStep();
...@@ -147,6 +149,8 @@ bool _deepEquals(dynamic a, dynamic b) { ...@@ -147,6 +149,8 @@ bool _deepEquals(dynamic a, dynamic b) {
return b is List && _deepEqualsList(a, b); return b is List && _deepEqualsList(a, b);
if (a is Map) if (a is Map)
return b is Map && _deepEqualsMap(a, b); return b is Map && _deepEqualsMap(a, b);
if (a is Pair)
return b is Pair && _deepEqualsPair(a, b);
return false; return false;
} }
...@@ -176,3 +180,7 @@ bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) { ...@@ -176,3 +180,7 @@ bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
} }
return true; return true;
} }
bool _deepEqualsPair(Pair a, Pair b) {
return _deepEquals(a.left, b.left) && _deepEquals(a.right, b.right);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment