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 @@
package com.yourcompany.channels;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.Date;
import android.os.Bundle;
......@@ -20,9 +22,9 @@ public class MainActivity extends FlutterActivity {
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "binary-msg", BinaryCodec.INSTANCE));
setupMessageHandshake(new BasicMessageChannel<>(getFlutterView(), "string-msg", StringCodec.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(), "std-method", StandardMethodCodec.INSTANCE));
setupMethodHandshake(new MethodChannel(getFlutterView(), "std-method", new StandardMethodCodec(ExtendedStandardMessageCodec.INSTANCE)));
}
private <T> void setupMessageHandshake(final BasicMessageChannel<T> channel) {
......@@ -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 @@
#include "AppDelegate.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
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
......@@ -13,6 +89,7 @@
FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController;
ExtendedReaderWriter* extendedReaderWriter = [ExtendedReaderWriter new];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"binary-msg"
binaryMessenger:flutterController
......@@ -28,7 +105,7 @@
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"std-msg"
binaryMessenger:flutterController
codec:[FlutterStandardMessageCodec sharedInstance]]];
codec:[FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"json-method"
binaryMessenger:flutterController
......@@ -36,7 +113,7 @@
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec sharedInstance]]];
codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
......
......@@ -10,6 +10,7 @@ import 'package:flutter_driver/driver_extension.dart';
import 'src/basic_messaging.dart';
import 'src/method_calls.dart';
import 'src/pair.dart';
import 'src/test_step.dart';
void main() {
......@@ -23,6 +24,7 @@ class TestApp extends StatefulWidget {
}
class _TestAppState extends State<TestApp> {
static final dynamic anUnknownValue = new DateTime.fromMillisecondsSinceEpoch(1520777802314);
static final List<dynamic> aList = <dynamic>[
false,
0,
......@@ -39,7 +41,7 @@ class _TestAppState extends State<TestApp> {
'd': 'hello',
'e': <dynamic>[
<String, dynamic>{'key': 42}
]
],
};
static final Uint8List someUint8s = new Uint8List.fromList(<int>[
0xBA,
......@@ -69,6 +71,10 @@ class _TestAppState extends State<TestApp> {
double.maxFinite,
double.infinity,
]);
static final dynamic aCompoundUnknownValue = <dynamic>[
anUnknownValue,
new Pair(anUnknownValue, aList),
];
static final List<TestStep> steps = <TestStep>[
() => methodCallJsonSuccessHandshake(null),
() => methodCallJsonSuccessHandshake(true),
......@@ -83,6 +89,8 @@ class _TestAppState extends State<TestApp> {
() => methodCallStandardSuccessHandshake('world'),
() => methodCallStandardSuccessHandshake(aList),
() => methodCallStandardSuccessHandshake(aMap),
() => methodCallStandardSuccessHandshake(anUnknownValue),
() => methodCallStandardSuccessHandshake(aCompoundUnknownValue),
() => methodCallJsonErrorHandshake(null),
() => methodCallJsonErrorHandshake('world'),
() => methodCallStandardErrorHandshake(null),
......@@ -138,6 +146,8 @@ class _TestAppState extends State<TestApp> {
() => basicStandardHandshake(<String, dynamic>{}),
() => basicStandardHandshake(<dynamic, dynamic>{7: true, false: -7}),
() => basicStandardHandshake(aMap),
() => basicStandardHandshake(anUnknownValue),
() => basicStandardHandshake(aCompoundUnknownValue),
() => basicBinaryMessageToUnknownChannel(),
() => basicStringMessageToUnknownChannel(),
() => basicJsonMessageToUnknownChannel(),
......
......@@ -5,8 +5,42 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'pair.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 {
const BasicMessageChannel<ByteData> channel =
const BasicMessageChannel<ByteData>(
......@@ -38,7 +72,7 @@ Future<TestStepResult> basicStandardHandshake(dynamic message) async {
const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>(
'std-msg',
const StandardMessageCodec(),
const ExtendedStandardMessageCodec(),
);
return _basicMessageHandshake<dynamic>(
'Standard >${toString(message)}<', channel, message);
......@@ -74,7 +108,7 @@ Future<TestStepResult> basicStandardMessageToUnknownChannel() async {
const BasicMessageChannel<dynamic> channel =
const BasicMessageChannel<dynamic>(
'std-unknown',
const StandardMessageCodec(),
const ExtendedStandardMessageCodec(),
);
return _basicMessageToUnknownChannel<dynamic>('Standard', channel);
}
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'basic_messaging.dart';
import 'test_step.dart';
Future<TestStepResult> methodCallJsonSuccessHandshake(dynamic payload) async {
......@@ -27,22 +28,28 @@ Future<TestStepResult> methodCallJsonNotImplementedHandshake() async {
Future<TestStepResult> methodCallStandardSuccessHandshake(
dynamic payload) async {
const MethodChannel channel =
const MethodChannel('std-method', const StandardMethodCodec());
const MethodChannel channel = const MethodChannel(
'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallSuccessHandshake(
'Standard success($payload)', channel, payload);
}
Future<TestStepResult> methodCallStandardErrorHandshake(dynamic payload) async {
const MethodChannel channel =
const MethodChannel('std-method', const StandardMethodCodec());
const MethodChannel channel = const MethodChannel(
'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallErrorHandshake(
'Standard error($payload)', channel, payload);
}
Future<TestStepResult> methodCallStandardNotImplementedHandshake() async {
const MethodChannel channel =
const MethodChannel('std-method', const StandardMethodCodec());
const MethodChannel channel = const MethodChannel(
'std-method',
const StandardMethodCodec(const ExtendedStandardMessageCodec()),
);
return _methodCallNotImplementedHandshake(
'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';
import 'package:flutter/material.dart';
import 'pair.dart';
enum TestStatus { ok, pending, failed, complete }
typedef Future<TestStepResult> TestStep();
......@@ -147,6 +149,8 @@ bool _deepEquals(dynamic a, dynamic b) {
return b is List && _deepEqualsList(a, b);
if (a is Map)
return b is Map && _deepEqualsMap(a, b);
if (a is Pair)
return b is Pair && _deepEqualsPair(a, b);
return false;
}
......@@ -176,3 +180,7 @@ bool _deepEqualsMap(Map<dynamic, dynamic> a, Map<dynamic, dynamic> b) {
}
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