-
Notifications
You must be signed in to change notification settings - Fork 6k
Support EventChannel C++ plugin API for Linux/Windows #17015
Changes from 18 commits
44a638b
395df1e
c556c7a
1000496
ae81b9d
9069e4c
8e96c4f
b8fe504
251829e
4f5e051
b821d51
7c5e963
0b9446e
6168c67
6917341
f870a60
f4f8634
02b3a3e
59c7146
5222fc0
50576c6
28cc16e
0835785
cc3f913
2210369
6c942dc
fbaaee8
6c3fb5f
7f0e247
a4e0caf
05ce3b5
c830c9b
53077da
21d9f99
3768021
825ca23
3befe52
8a76fba
5f09561
4896184
41eb32f
dc68fbd
0416d5b
b36ae30
dfb1451
61df9ba
090f6bf
7905db1
f71c8a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/event_channel.h" | ||
|
|
||
| #include <memory> | ||
| #include <string> | ||
|
|
||
| #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/binary_messenger.h" | ||
| #include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/standard_method_codec.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace flutter { | ||
|
|
||
| namespace { | ||
|
|
||
| class TestBinaryMessenger : public BinaryMessenger { | ||
| public: | ||
| void Send(const std::string& channel, | ||
| const uint8_t* message, | ||
| const size_t message_size) const override {} | ||
|
|
||
| void Send(const std::string& channel, | ||
| const uint8_t* message, | ||
| const size_t message_size, | ||
| BinaryReply reply) const override {} | ||
|
|
||
| void SetMessageHandler(const std::string& channel, | ||
| BinaryMessageHandler handler) override { | ||
| last_message_handler_channel_ = channel; | ||
| last_message_handler_ = handler; | ||
| } | ||
|
|
||
| std::string last_message_handler_channel() { | ||
| return last_message_handler_channel_; | ||
| } | ||
|
|
||
| BinaryMessageHandler last_message_handler() { return last_message_handler_; } | ||
|
|
||
| private: | ||
| std::string last_message_handler_channel_; | ||
| BinaryMessageHandler last_message_handler_; | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| // Tests that SetStreamHandler sets a handler that correctly interacts with | ||
| // the binary messenger. | ||
| TEST(EventChannelTest, Registration) { | ||
| TestBinaryMessenger messenger; | ||
| const std::string channel_name("some_channel"); | ||
| const StandardMethodCodec& codec = StandardMethodCodec::GetInstance(); | ||
| EventChannel channel(&messenger, channel_name, &codec); | ||
|
|
||
| bool on_listen_called = false; | ||
| auto onListen = [&on_listen_called]( | ||
| const flutter::EncodableValue* arguments, | ||
| EventSink<flutter::EncodableValue>* event_sink) { | ||
| event_sink->Success(); | ||
| auto message = flutter::EncodableValue(flutter::EncodableMap{ | ||
| {flutter::EncodableValue("message"), | ||
| flutter::EncodableValue("Test from Event Channel")}}); | ||
| event_sink->Success(&message); | ||
| event_sink->Error("Event Channel Error Code", "Error Message", nullptr); | ||
| event_sink->EndOfStream(); | ||
| on_listen_called = true; | ||
| }; | ||
|
|
||
| bool on_cancel_called = false; | ||
| auto onCancel = | ||
| [&on_cancel_called](const flutter::EncodableValue* arguments) { | ||
| on_cancel_called = true; | ||
| }; | ||
| channel.SetStreamHandler( | ||
| flutter::StreamHandler<flutter::EncodableValue>(onListen, onCancel)); | ||
|
|
||
| EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); | ||
| EXPECT_NE(messenger.last_message_handler(), nullptr); | ||
|
|
||
| // Send a test message to trigger the handler test assertions. | ||
| MethodCall<EncodableValue> call("listen", nullptr); | ||
| auto message = codec.EncodeMethodCall(call); | ||
| messenger.last_message_handler()( | ||
| message->data(), message->size(), | ||
| [](const uint8_t* reply, const size_t reply_size) {}); | ||
| EXPECT_EQ(on_listen_called, true); | ||
|
|
||
| // FIXME: add onCancel test scenario | ||
| // EXPECT_EQ(on_cancel_called, true); | ||
| } | ||
|
|
||
| // Tests that SetStreamHandler with a null handler unregisters the handler. | ||
| TEST(EventChannelTest, Unregistration) { | ||
| TestBinaryMessenger messenger; | ||
| const std::string channel_name("some_channel"); | ||
| EventChannel<flutter::EncodableValue> channel( | ||
| &messenger, channel_name, &flutter::StandardMethodCodec::GetInstance()); | ||
|
|
||
| auto onListen = [](const flutter::EncodableValue* arguments, | ||
| EventSink<flutter::EncodableValue>* event_sink) {}; | ||
| auto onCancel = [](const flutter::EncodableValue* arguments) {}; | ||
| channel.SetStreamHandler( | ||
| flutter::StreamHandler<flutter::EncodableValue>(onListen, onCancel)); | ||
| EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); | ||
| EXPECT_NE(messenger.last_message_handler(), nullptr); | ||
|
|
||
| // channel.SetStreamHandler(std::nullopt); | ||
| // EXPECT_EQ(messenger.last_message_handler_channel(), channel_name); | ||
| // EXPECT_EQ(messenger.last_message_handler(), nullptr); | ||
| } | ||
|
|
||
stuartmorgan-g marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } // namespace flutter | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_ | ||
| #define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_ | ||
|
|
||
| #include <iostream> | ||
| #include <string> | ||
|
|
||
| #include "binary_messenger.h" | ||
| #include "engine_method_result.h" | ||
| #include "event_sink.h" | ||
| #include "event_stream_handler.h" | ||
|
|
||
| static constexpr char kOnListenMethod[] = "listen"; | ||
|
||
| static constexpr char kOnCancelMethod[] = "cancel"; | ||
|
|
||
| namespace flutter { | ||
|
|
||
| // A named channel for communicating with the Flutter application using | ||
| // asynchronous event streams. Incoming requests for event stream setup are | ||
| // decoded from binary on receipt, and C++ responses and events are encoded into | ||
| // binary before being transmitted back to Flutter. The MethodCodec used must be | ||
| // compatible with the one used by the Flutter application. This can be achieved | ||
| // by creating an EventChannel | ||
| // ("https://docs.flutter.io/flutter/services/EventChannel-class.html") | ||
| // counterpart of this channel on the Dart side. The C++ type of stream | ||
| // configuration arguments, events, and error details is Object, but only values | ||
| // supported by the specified MethodCodec can be used. | ||
| // | ||
| // The logical identity of the channel is given by its name. Identically named | ||
| // channels will interfere with each other's communication. | ||
| template <typename T> | ||
| class EventChannel { | ||
| public: | ||
| // Creates an instance that sends and receives event handler on the channel | ||
| // named |name|, encoded with |codec| and dispatched via |messenger|. | ||
| EventChannel(BinaryMessenger* messenger, | ||
| const std::string& name, | ||
| const MethodCodec<T>* codec) | ||
| : messenger_(messenger), name_(name), codec_(codec) {} | ||
| ~EventChannel() = default; | ||
|
|
||
| // Prevent copying. | ||
| EventChannel(EventChannel const&) = delete; | ||
| EventChannel& operator=(EventChannel const&) = delete; | ||
|
|
||
| // Registers a stream handler on this channel. | ||
| // If no handler has been registered, any incoming stream setup requests will | ||
| // be handled silently by providing an empty stream. | ||
| void SetStreamHandler(const StreamHandler<T>& handler) const { | ||
| // TODO: The following is required when nullptr | ||
|
||
| // can be passed as an argument. | ||
| // if (!handler) { /* <= available for more than C++17 */ | ||
| // messenger_->SetMessageHandler(name_, nullptr); | ||
|
||
| // return; | ||
| // } | ||
|
|
||
| const auto* codec = codec_; | ||
|
||
| const std::string channel_name = name_; | ||
| const auto* messenger = messenger_; | ||
| EventSinkImplementation* current_sink = current_sink_; | ||
| BinaryMessageHandler binary_handler = [handler, codec, channel_name, | ||
| current_sink, messenger]( | ||
| const uint8_t* message, | ||
| const size_t message_size, | ||
| BinaryReply reply) mutable { | ||
| std::unique_ptr<MethodCall<T>> method_call = | ||
| codec->DecodeMethodCall(message, message_size); | ||
| if (!method_call) { | ||
| std::cerr << "Unable to construct method call from message on channel " | ||
| << channel_name << std::endl; | ||
| return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You aren't calling
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added reply. |
||
| } | ||
|
|
||
| const std::string& method = method_call->method_name(); | ||
| if (method.compare(kOnListenMethod) == 0) { | ||
| if (current_sink) { | ||
| std::cerr << "Failed to cancel existing stream: " << channel_name | ||
| << std::endl; | ||
| handler.onCancel(method_call->arguments()); | ||
| delete current_sink; | ||
|
||
| current_sink = nullptr; | ||
| } | ||
|
|
||
| current_sink = | ||
| new EventSinkImplementation(messenger, channel_name, codec); | ||
| handler.onListen(method_call->arguments(), | ||
| static_cast<EventSink<T>*>(current_sink)); | ||
|
|
||
| { | ||
| auto result = codec->EncodeSuccessEnvelope(); | ||
| uint8_t* buffer = new uint8_t[result->size()]; | ||
|
||
| std::copy(result->begin(), result->end(), buffer); | ||
| reply(buffer, result->size()); | ||
| delete[] buffer; | ||
| } | ||
| } else if (method.compare(kOnCancelMethod) == 0) { | ||
| if (current_sink) { | ||
| handler.onCancel(method_call->arguments()); | ||
|
|
||
| auto result = codec->EncodeSuccessEnvelope(); | ||
| uint8_t* buffer = new uint8_t[result->size()]; | ||
|
||
| std::copy(result->begin(), result->end(), buffer); | ||
| reply(buffer, result->size()); | ||
| delete current_sink; | ||
| current_sink = nullptr; | ||
| } else { | ||
| auto result = codec->EncodeErrorEnvelope( | ||
| "error", "No active stream to cancel", nullptr); | ||
| uint8_t* buffer = new uint8_t[result->size()]; | ||
| std::copy(result->begin(), result->end(), buffer); | ||
| reply(buffer, result->size()); | ||
| delete[] buffer; | ||
| } | ||
| } else { | ||
| std::cerr | ||
|
||
| << "Unknown event channel method call from message on channel: " | ||
| << channel_name << std::endl; | ||
| reply(nullptr, 0); | ||
| if (current_sink) { | ||
| delete current_sink; | ||
|
||
| current_sink = nullptr; | ||
| } | ||
| } | ||
| }; | ||
| messenger_->SetMessageHandler(name_, std::move(binary_handler)); | ||
| } | ||
|
|
||
| private: | ||
| class EventSinkImplementation : public EventSink<T> { | ||
| public: | ||
| // Creates an instance that EventSink send event on the channel | ||
| // named |name|, encoded with |codec| and dispatched via |messenger|. | ||
| EventSinkImplementation(const BinaryMessenger* messenger, | ||
| const std::string& name, | ||
| const MethodCodec<T>* codec) | ||
| : messenger_(messenger), name_(name), codec_(codec) {} | ||
| ~EventSinkImplementation() = default; | ||
|
|
||
| // Prevent copying. | ||
| EventSinkImplementation(EventSinkImplementation const&) = delete; | ||
| EventSinkImplementation& operator=(EventSinkImplementation const&) = delete; | ||
|
|
||
| private: | ||
| const BinaryMessenger* messenger_; | ||
| const std::string name_; | ||
| const MethodCodec<T>* codec_; | ||
|
|
||
| protected: | ||
| void SuccessInternal(T* event = nullptr) override { | ||
| auto result = codec_->EncodeSuccessEnvelope(event); | ||
| uint8_t* buffer = new uint8_t[result->size()]; | ||
| std::copy(result->begin(), result->end(), buffer); | ||
| messenger_->Send(name_, buffer, result->size()); | ||
| delete[] buffer; | ||
| } | ||
|
|
||
| void ErrorInternal(const std::string& error_code, | ||
| const std::string& error_message, | ||
| T* error_details) override { | ||
| auto result = | ||
| codec_->EncodeErrorEnvelope(error_code, error_message, error_details); | ||
| uint8_t* buffer = new uint8_t[result->size()]; | ||
| std::copy(result->begin(), result->end(), buffer); | ||
| messenger_->Send(name_, buffer, result->size()); | ||
| delete[] buffer; | ||
| } | ||
|
|
||
| void EndOfStreamInternal() override { messenger_->Send(name_, nullptr, 0); } | ||
| }; | ||
|
|
||
| BinaryMessenger* messenger_; | ||
| std::string name_; | ||
| const MethodCodec<T>* codec_; | ||
| EventSinkImplementation* current_sink_ = nullptr; | ||
|
||
| }; | ||
|
|
||
| } // namespace flutter | ||
|
|
||
| #endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_CHANNEL_H_ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| #ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_SINK_H_ | ||
| #define FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_SINK_H_ | ||
|
|
||
| namespace flutter { | ||
|
|
||
| // Event callback. Supports dual use: Producers of events to be sent to Flutter | ||
|
||
| // act as clients of this interface for sending events. Consumers of events sent | ||
| // from Flutter implement this interface for handling received events. | ||
| template <typename T> | ||
| class EventSink { | ||
| public: | ||
| EventSink() = default; | ||
| virtual ~EventSink() = default; | ||
|
|
||
| // Prevent copying. | ||
| EventSink(EventSink const&) = delete; | ||
| EventSink& operator=(EventSink const&) = delete; | ||
|
|
||
| // Consumes end of stream. Ensuing calls to Success(T) or | ||
| // Error(String, String, Object)}, if any, are ignored. | ||
| void EndOfStream() { EndOfStreamInternal(); } | ||
|
||
|
|
||
| // Consumes a successful event. | ||
| void Success(T* event = nullptr) { SuccessInternal(event); } | ||
|
|
||
| // Consumes an error event. | ||
| void Error(const std::string& error_code, | ||
| const std::string& error_message = "", | ||
| T* error_details = nullptr) { | ||
| ErrorInternal(error_code, error_message, error_details); | ||
| } | ||
|
|
||
| protected: | ||
| // Implementation of the public interface, to be provided by subclasses. | ||
| virtual void EndOfStreamInternal() = 0; | ||
|
||
|
|
||
| // Implementation of the public interface, to be provided by subclasses. | ||
| virtual void SuccessInternal(T* event = nullptr) = 0; | ||
|
|
||
| // Implementation of the public interface, to be provided by subclasses. | ||
| virtual void ErrorInternal(const std::string& error_code, | ||
| const std::string& error_message, | ||
| T* error_details) = 0; | ||
| }; | ||
|
|
||
| } // namespace flutter | ||
|
|
||
| #endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_CLIENT_WRAPPER_INCLUDE_FLUTTER_EVENT_SINK_H_ | ||
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.
Please finish implementing the tests; there is commented-out code in both test 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.
I fixed unit test code.