Skip to content

Commit

Permalink
Make standard codecs extensible (#15414)
Browse files Browse the repository at this point in the history
  • Loading branch information
mravn-google committed Mar 16, 2018
1 parent 2d5ebd2 commit 340c680
Show file tree
Hide file tree
Showing 8 changed files with 314 additions and 74 deletions.
Expand Up @@ -4,7 +4,9 @@

package com.yourcompany.channels;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.Date;

import android.os.Bundle;

Expand All @@ -20,9 +22,9 @@ protected void onCreate(Bundle savedInstanceState) {
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) {
Expand Down Expand Up @@ -135,3 +137,49 @@ public void notImplemented() {
});
}
}

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 + "]";
}
}
81 changes: 79 additions & 2 deletions dev/integration_tests/channels/ios/Runner/AppDelegate.m
Expand Up @@ -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 {
Expand All @@ -13,6 +89,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
FlutterViewController *flutterController =
(FlutterViewController *)self.window.rootViewController;

ExtendedReaderWriter* extendedReaderWriter = [ExtendedReaderWriter new];
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"binary-msg"
binaryMessenger:flutterController
Expand All @@ -28,15 +105,15 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
[self setupMessagingHandshakeOnChannel:
[FlutterBasicMessageChannel messageChannelWithName:@"std-msg"
binaryMessenger:flutterController
codec:[FlutterStandardMessageCodec sharedInstance]]];
codec:[FlutterStandardMessageCodec codecWithReaderWriter:extendedReaderWriter]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"json-method"
binaryMessenger:flutterController
codec:[FlutterJSONMethodCodec sharedInstance]]];
[self setupMethodCallSuccessHandshakeOnChannel:
[FlutterMethodChannel methodChannelWithName:@"std-method"
binaryMessenger:flutterController
codec:[FlutterStandardMethodCodec sharedInstance]]];
codec:[FlutterStandardMethodCodec codecWithReaderWriter:extendedReaderWriter]]];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

Expand Down
12 changes: 11 additions & 1 deletion dev/integration_tests/channels/lib/main.dart
Expand Up @@ -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() {
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -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(),
Expand Down
38 changes: 36 additions & 2 deletions dev/integration_tests/channels/lib/src/basic_messaging.dart
Expand Up @@ -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>(
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
19 changes: 13 additions & 6 deletions dev/integration_tests/channels/lib/src/method_calls.dart
Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down
14 changes: 14 additions & 0 deletions dev/integration_tests/channels/lib/src/pair.dart
@@ -0,0 +1,14 @@
// 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]';
}
8 changes: 8 additions & 0 deletions dev/integration_tests/channels/lib/src/test_step.dart
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}

0 comments on commit 340c680

Please sign in to comment.