-
Notifications
You must be signed in to change notification settings - Fork 28.7k
PlatformChannel concept added to support interop #8394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@@ -10,7 +10,7 @@ | |||
|
|||
<application android:name="io.flutter.app.FlutterApplication" android:label="@string/app_name" > | |||
<activity | |||
android:name=".ExampleActivity" | |||
android:name="com.example.flutter.ExampleActivity" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we're using com.example.platformservices
on line 3 and com.example.flutter
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. The original manifest package was wrong, and I was unaware of the . prefix shorthand. Fixed.
) | ||
) | ||
); | ||
return new Material(child: new Center(child: new Column( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you add trailing commas everywhere there are permitted and run dartfmt, you'll get code that matches https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo in this example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
_latitude = reply['latitude'].toDouble(); | ||
_longitude = reply['longitude'].toDouble(); | ||
_locationRequest = | ||
new PlatformChannel('geo').invokeMethod('getLocation', 'network'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
/// * [String]s | ||
/// * [List]s of supported values | ||
/// * [Map]s from strings to supported values | ||
static const MessageCodec json = const _JSONCodec(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not make _JSONCodec a public class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/// * [Uint8List]s, [Int32List]s, [Int64List]s, [Float64List]s | ||
/// * [List]s of supported values | ||
/// * [Map]s from supported values to supported values | ||
static const MessageCodec standard = const _StandardCodec(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here.
The way you've structured this code, these two are special children. It would be better to structure things in a way that they're not special and someone could use a different codec (e.g., protobufs) easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/// Encodes the specified method call in binary. | ||
/// | ||
/// The [name] of the method must be non-null. The [arguments] may be `null`. | ||
ByteData encodeMethodCall(String name, dynamic arguments); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As in the Java code review, I think we should disentangle the methodcall-based and message-based codecs. If necessary, we can have separate PlatformChannel
classes to drive them. For example, we could have a PlatformMessageChannel
and a PlatformMethodChannel
(or some other better naming scheme.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/// populate a [PlatformException]. | ||
/// | ||
/// All operations throw [FormatException], if conversion fails. | ||
abstract class MessageCodec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd break this file up into three separate files:
- One file that has the abstract classes for MessageCodec and MethodCodec.
- Another file that defines concrete implementations of these codecs for "standard" and "JSON". This separation will prevent these codecs from being special children because (2) will be able to import (1) but (1) should not import (2).
- A third file that contains the PlatformChannel class that has the pretty front-ends.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
} | ||
} | ||
|
||
class _WriteBuffer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd move this out into foundation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
void putInt32(int value) { | ||
_fourBytes.setInt32(0, value); | ||
buffer.addAll(_fourBytesAsList); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really more efficient than doing the bit smashing manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just get a ByteData view of buffer.buffer
and use setInt32
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct bit-smashing applied for ints.
For doubles, it seems I would need to create a new ByteData view instance of buffer.buffer for each double added, because the buffer is dynamically extended. Retained original approach for now.
} | ||
} | ||
|
||
class _StandardCodec implements MessageCodec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As requested in the Java review, please document this protocol. Here are examples where we've documented similar protocols:
https://github.com/flutter/engine/blob/master/lib/ui/text.dart#L184
https://github.com/flutter/engine/blob/master/lib/ui/painting.dart#L310
Doesn't have to be anything fancy. Just enough documentation so that someone could write, for example, a C# implementation of the protocol without having to reverse engineer the other implementations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
] | ||
) | ||
) | ||
new FutureBuilder<dynamic>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice.
setState(() { | ||
_latitude = reply['latitude'].toDouble(); | ||
_longitude = reply['longitude'].toDouble(); | ||
_locationRequest = new PlatformMethodChannel('geo').invokeMethod( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it recommend to make a new channel for each request? If so, I wonder if we can make it const constructible. No action needed. Just wondering aloud.
@@ -0,0 +1,204 @@ | |||
// Copyright 2015 The Chromium Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2017
if (Endianness.HOST_ENDIAN == Endianness.BIG_ENDIAN) { | ||
_buffer.addAll(list.buffer.asUint8List(list.offsetInBytes, 4 * list.length)); | ||
} else { | ||
for (final int value in list) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't usually put final
here but we probably should.
|
||
|
||
/// Thrown to indicate that a platform interaction resulted in an error. | ||
class PlatformException implements Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why have a PlatformException that's different from FlutterException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. These are errors reported from the other side of the channel. Would you be willing to add a bit more to the dartdoc explaining that? I thought these were errors in the mechanism itself rather than errors from the platform-specific implementation on the other side.
/// A message encoding/decoding mechanism. | ||
/// | ||
/// Both operations throw [FormatException], if conversion fails. | ||
abstract class MessageCodec<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful
// * Strings are encoded using their UTF-8 representation. First the length | ||
// of that in bytes is encoded using the expanding format, then follows the | ||
// UTF-8 encoding itself. | ||
// * Uint8Lists, Int32Lists, Int64Lists, and Float64Lists are encoded by first |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Document alignment and byte ordering.
// type byte (Lists are assumed to be heterogeneous). | ||
// * Maps are encoded by first encoding their length in the expanding format, | ||
// then follows the recursive encoding of each key/value pair, including the | ||
// type byte for both (Maps are assumed to be heterogeneous). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect. Thank you.
ByteData encodeMessage(dynamic message) { | ||
if (message == null) { | ||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -0,0 +1,198 @@ | |||
// Copyright 2016 The Chromium Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2017
PlatformMethodChannel(this.name, [this.codec = const StandardMethodCodec()]) { | ||
assert(name != null); | ||
assert(codec != null); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it could be const constructible except for the asserts.
onListen: () async { | ||
PlatformMessages.setBinaryMessageHandler( | ||
name, | ||
(ByteData reply) async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'd usually combine this line and the previous one.
controller.addError(e); | ||
} | ||
}, onCancel: () async { | ||
PlatformMessages.setBinaryMessageHandler(name, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be indented to match line 181, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Argh - the indentation is completely garbled in this class! Appears my IntelliJ does that on copy/paste. I moved nicely formatted code from PlatformMessages...
T get requireData { | ||
if (hasData) | ||
return data; | ||
else if (hasError) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see it explicitly in the style guide, but generally we skip the else
after return or throw statements. (Also applies to line 216
If I were writing this patch, I'd smoke test each type of channel against a mock implementation. (I wouldn't try to hit every type of encoded message, but I'd just makes sure messages and errors can get through as expected.) This looks great. Just some trivial nits. |
Thanks a lot Adam. Smoke test is in the works. Could I get you to take a
look at the Android side too: flutter/engine#3446
Mikkel
…On Wed, Mar 1, 2017 at 8:53 AM, Adam Barth ***@***.***> wrote:
If I were writing this patch, I'd smoke test each type of channel against
a mock implementation. (I wouldn't try to hit every type of encoded
message, but I'd just makes sure messages and errors can get through as
expected.)
This looks great. Just some trivial nits.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#8394 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AV33XQ6jOpfecEW8OKv5aj1qJYew1iw9ks5rhSPtgaJpZM4MLYYl>
.
--
Mikkel Nygaard Ravn
Software Engineer
|
ByteData _eightBytes; | ||
Uint8List _eightBytesAsList; | ||
|
||
WriteBuffer() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: here and in the class below, please move the constructor to above the fields. We put constructors first so that someone quickly glancing at the code can quickly tell if the class has an implicit constructor or explicit constructors.
} | ||
|
||
static void _writeSize(WriteBuffer buffer, int value) { | ||
assert(0 <= value && value < 0xffffffff); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not <= 0xffffffff
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's so that you can expand the format later to store even bigger ints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
your decoder just reads 0xffffffff as that, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expanding the format further was not the intention. I just had myself confused, that's all. Fix upcoming.
buffer.putUint8(_kLargeInt); | ||
final List<int> hex = UTF8.encoder.convert(value.toRadixString(16)); | ||
_writeSize(buffer, hex.length); | ||
buffer.putUint8List(hex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha, that escalated fast. :-P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. It's a temporary solution. Make it work, make it right, make it fast...
list = data.buffer.asInt64List(data.offsetInBytes + position, length); | ||
} else { | ||
final ByteData invertedData = new ByteData(8 * length); | ||
for (int i = 0; i < length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no braces around single-line blocks
|
||
void _alignTo(int alignment) { | ||
final int mod = position % alignment; | ||
if (mod != 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: similar here and elsewhere
@@ -1,4 +1,4 @@ | |||
// Copyright 2016 The Chromium Authors. All rights reserved. | |||
// Copyright 2017 The Chromium Authors. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: (don't fix this, but for future knowledge): don't change the copyright dates.
@@ -104,6 +103,8 @@ class PlatformMessages { | |||
/// | |||
/// Returns a [Future] which completes to the received response, decoded as a | |||
/// UTF-8 string, or to an error, if the decoding fails. | |||
/// | |||
/// Deprecated, use [PlatformMessageChannel.send] instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark the deprecated methods using @deprecated
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(or just remove them)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's the plan. Just wanted to land this PR first, then I'll remove uses of these methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent.
This is great. Thanks so much for refactoring all this into this much saner mechanism. |
Matches flutter/engine#3446 on flutter/engine that introduces a FlutterChannel on the platform side.
Unit tests pending.
Ready for design review.