Skip to content
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

Feature/callback messages #220

Merged
merged 4 commits into from
Aug 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions embedder/embedder.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package embedder
// #include "embedder.h"
// FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine, FlutterProjectArgs * Args,
// const char *const * vmArgs, int nVmAgrs);
// FlutterEngineResult
// createMessageResponseHandle(FlutterEngine engine, void *user_data,
// FlutterPlatformMessageResponseHandle **reply);
// char** makeCharArray(int size);
// void setArrayString(char **a, char *s, int n);
// const int32_t kFlutterSemanticsNodeIdBatchEnd = -1;
// const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1;
import "C"
import (
"errors"
"fmt"
"runtime"
"runtime/debug"
"sync"
"unsafe"
Expand Down Expand Up @@ -257,7 +262,6 @@ type PlatformMessage struct {
Message []byte

// ResponseHandle is only set when receiving a platform message.
// https://github.com/flutter/flutter/issues/18852
ResponseHandle PlatformMessageResponseHandle
}

Expand Down Expand Up @@ -357,8 +361,40 @@ func (flu *FlutterEngine) MarkExternalTextureFrameAvailable(textureID int64) Res
return (Result)(res)
}

// FlutterEngineGetCurrentTime gets the current time in nanoseconds from the
// clock used by the flutter engine.
// DataCallback is a function called when a PlatformMessage response send back
// to the embedder.
type DataCallback func(binaryReply []byte)

// CreatePlatformMessageResponseHandle creates a platform message response
// handle that allows the embedder to set a native callback for a response to a
// message.
func (flu *FlutterEngine) CreatePlatformMessageResponseHandle(callback DataCallback) (PlatformMessageResponseHandle, error) {
var responseHandle *C.FlutterPlatformMessageResponseHandle

callbackPointer := uintptr(unsafe.Pointer(&callback))
defer func() {
runtime.KeepAlive(callbackPointer)
}()

res := C.createMessageResponseHandle(flu.Engine, unsafe.Pointer(&callbackPointer), &responseHandle)
if (Result)(res) != ResultSuccess {
return 0, errors.New("failed to create a response handle")
}
return PlatformMessageResponseHandle(unsafe.Pointer(responseHandle)), nil
}

// ReleasePlatformMessageResponseHandle collects a platform message response
// handle.
func (flu *FlutterEngine) ReleasePlatformMessageResponseHandle(responseHandle PlatformMessageResponseHandle) {
cResponseHandle := (*C.FlutterPlatformMessageResponseHandle)(unsafe.Pointer(responseHandle))
res := C.FlutterPlatformMessageReleaseResponseHandle(flu.Engine, cResponseHandle)
if (Result)(res) != ResultSuccess {
fmt.Printf("go-flutter: failed to collect platform response message handle")
}
}

// FlutterEngineGetCurrentTime gets the current time in nanoseconds from the clock used by the flutter
// engine.
func FlutterEngineGetCurrentTime() uint64 {
return uint64(C.FlutterEngineGetCurrentTime())
}
}
11 changes: 11 additions & 0 deletions embedder/embedder_helper.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ bool proxy_runs_task_on_current_thread_callback(void *user_data);
void proxy_post_task_callback(FlutterTask task, uint64_t target_time_nanos,
void *user_data);

void proxy_desktop_binary_reply(const uint8_t *data, size_t data_size,
void *user_data);

// C helper
FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine,
FlutterProjectArgs *Args,
Expand Down Expand Up @@ -62,3 +65,11 @@ FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine,
char **makeCharArray(int size) { return calloc(sizeof(char *), size); }

void setArrayString(char **a, char *s, int n) { a[n] = s; }

FlutterEngineResult
createMessageResponseHandle(FlutterEngine engine, void *user_data,
FlutterPlatformMessageResponseHandle **reply) {

return FlutterPlatformMessageCreateResponseHandle(
engine, proxy_desktop_binary_reply, user_data, reply);
}
7 changes: 7 additions & 0 deletions embedder/embedder_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,10 @@ func proxy_post_task_callback(task C.FlutterTask, targetTimeNanos C.uint64_t, us
flutterEngine := (*FlutterEngine)(unsafe.Pointer(flutterEnginePointer))
flutterEngine.TaskRunnerPostTask(task, uint64(targetTimeNanos))
}

//export proxy_desktop_binary_reply
func proxy_desktop_binary_reply(data *C.uint8_t, dataSize C.size_t, userData unsafe.Pointer) {
callbackPointer := *(*uintptr)(userData)
handler := *(*DataCallback)(unsafe.Pointer(callbackPointer))
handler(C.GoBytes(unsafe.Pointer(data), C.int(dataSize)))
}
2 changes: 1 addition & 1 deletion key-events.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (p *keyeventPlugin) sendKeyEvent(window *glfw.Window, key glfw.Key, scancod
ScanCode: scancode,
Modifiers: int(mods),
}
_, err := p.keyEventChannel.Send(event)
err := p.keyEventChannel.Send(event)
if err != nil {
fmt.Printf("go-flutter: Failed to send raw_keyboard event %v: %v\n", event, err)
}
Expand Down
2 changes: 1 addition & 1 deletion lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (p *lifecyclePlugin) glfwIconifyCallback(w *glfw.Window, iconified bool) {
case false:
state = "AppLifecycleState.resumed"
}
_, err := p.channel.Send(state)
err := p.channel.Send(state)
if err != nil {
fmt.Printf("go-flutter: Failed to send lifecycle event %s: %v\n", state, err)
}
Expand Down
49 changes: 40 additions & 9 deletions messenger.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package flutter

import (
"errors"
"fmt"
"sync"

"github.com/go-flutter-desktop/go-flutter/embedder"
"github.com/go-flutter-desktop/go-flutter/internal/tasker"
"github.com/go-flutter-desktop/go-flutter/plugin"
"github.com/pkg/errors"
)

type messenger struct {
Expand All @@ -30,13 +30,25 @@ func newMessenger(engine *embedder.FlutterEngine) *messenger {
}
}

// Send pushes a binary message on a channel to the Flutter side. Replies are
// not supported yet (https://github.com/flutter/flutter/issues/18852). This
// means that currently, binaryReply will be nil on success.
// Send pushes a binary message on a channel to the Flutter side and wait for a
// reply.
// NOTE: If no value are returned by the flutter handler, the function will
// wait forever. In case you don't want to wait for reply, use SendNoReply.
func (m *messenger) Send(channel string, binaryMessage []byte) (binaryReply []byte, err error) {
reply := make(chan []byte)
defer close(reply)
responseHandle, err := m.engine.CreatePlatformMessageResponseHandle(func(binaryMessage []byte) {
reply <- binaryMessage
})
if err != nil {
return nil, err
}
defer m.engine.ReleasePlatformMessageResponseHandle(responseHandle)

msg := &embedder.PlatformMessage{
Channel: channel,
Message: binaryMessage,
Channel: channel,
Message: binaryMessage,
ResponseHandle: responseHandle,
}
res := m.engine.SendPlatformMessage(msg)
if err != nil {
Expand All @@ -48,9 +60,28 @@ func (m *messenger) Send(channel string, binaryMessage []byte) (binaryReply []by
return nil, errors.New("failed to send message")
}

// NOTE: Response from engine is not yet supported by embedder.
// https://github.com/flutter/flutter/issues/18852
return nil, nil
// wait for a reply and return
return <-reply, nil
}

// SendNoReply pushes a binary message on a channel to the Flutter side without
// expecting replies.
func (m *messenger) SendNoReply(channel string, binaryMessage []byte) (err error) {
msg := &embedder.PlatformMessage{
Channel: channel,
Message: binaryMessage,
}
res := m.engine.SendPlatformMessage(msg)
if err != nil {
if ferr, ok := err.(*plugin.FlutterError); ok {
return ferr
}
}
if res != embedder.ResultSuccess {
return errors.New("failed to send message")
}

return nil
}

// SetChannelHandler satisfies plugin.BinaryMessenger
Expand Down
27 changes: 22 additions & 5 deletions plugin/basic-message-channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,28 @@ func NewBasicMessageChannel(messenger BinaryMessenger, channelName string, codec
return b
}

// Send encodes and sends the specified message to the Flutter application and
// returns the reply, or an error. Results from the Flutter side are not yet
// implemented in the embedder. Until then, InvokeMethod will always return nil
// as reult. https://github.com/flutter/flutter/issues/18852
func (b *BasicMessageChannel) Send(message interface{}) (reply interface{}, err error) {
// Send encodes and sends the specified message to the Flutter application
// without waiting for a reply.
func (b *BasicMessageChannel) Send(message interface{}) error {
encodedMessage, err := b.codec.EncodeMessage(message)
if err != nil {
return errors.Wrap(err, "failed to encode outgoing message")
}
err = b.messenger.SendNoReply(b.channelName, encodedMessage)
if err != nil {
return errors.Wrap(err, "failed to send outgoing message")
}
return nil
}

// SendWithReply encodes and sends the specified message to the Flutter
// application and returns the reply, or an error.
//
// NOTE: If no value are returned by the handler setted in the
// setMessageHandler flutter method, the function will wait forever. In case
// you don't want to wait for reply, use Send or launch the
// function in a goroutine.
func (b *BasicMessageChannel) SendWithReply(message interface{}) (reply interface{}, err error) {
encodedMessage, err := b.codec.EncodeMessage(message)
if err != nil {
return nil, errors.Wrap(err, "failed to encode outgoing message")
Expand Down
8 changes: 4 additions & 4 deletions plugin/basic-message-channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestBasicMethodChannelStringCodecSend(t *testing.T) {
return nil
})
channel := NewBasicMessageChannel(messenger, "ch", codec)
reply, err := channel.Send("hello")
reply, err := channel.SendWithReply("hello")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -100,7 +100,7 @@ func TestBasicMethodChannelBinaryCodecSend(t *testing.T) {
return nil
})
channel := NewBasicMessageChannel(messenger, "ch", codec)
reply, err := channel.Send([]byte{0x01})
reply, err := channel.SendWithReply([]byte{0x01})
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestBasicMethodChannelNilMockHandler(t *testing.T) {
messenger := NewTestingBinaryMessenger()
messenger.MockSetChannelHandler("ch", nil)
channel := NewBasicMessageChannel(messenger, "ch", codec)
reply, err := channel.Send("hello")
reply, err := channel.SendWithReply("hello")
Nil(t, reply)
NotNil(t, err)
Equal(t, "failed to send outgoing message: no handler set", err.Error())
Expand All @@ -170,7 +170,7 @@ func TestBasicMethodChannelEncodeFail(t *testing.T) {
codec := StringCodec{}
messenger := NewTestingBinaryMessenger()
channel := NewBasicMessageChannel(messenger, "ch", codec)
reply, err := channel.Send(int(42)) // invalid value
reply, err := channel.SendWithReply(int(42)) // invalid value
Nil(t, reply)
NotNil(t, err)
Equal(t, "failed to encode outgoing message: invalid type provided to message codec: expected message to be of type string", err.Error())
Expand Down
4 changes: 4 additions & 0 deletions plugin/binary-messenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ type BinaryMessenger interface {
// Send sends a binary message to the Flutter application.
Send(channel string, binaryMessage []byte) (binaryReply []byte, err error)

// SendNoReply sends a binary message to the Flutter application without
// expecting replies.
SendNoReply(channel string, binaryMessage []byte) (err error)

// SetChannelHandler registers a handler to be invoked when the Flutter
// application sends a message to its host platform on given channel.
//
Expand Down
5 changes: 5 additions & 0 deletions plugin/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ func NewTestingBinaryMessenger() *TestingBinaryMessenger {

var _ BinaryMessenger = &TestingBinaryMessenger{} // compile-time type check

func (t *TestingBinaryMessenger) SendNoReply(channel string, message []byte) (err error) {
_, err = t.Send(channel, message)
return err
}

// Send sends the bytes onto the given channel.
// In this testing implementation of a BinaryMessenger, the handler for the
// channel may be set using MockSetMessageHandler
Expand Down
36 changes: 25 additions & 11 deletions plugin/method-channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,31 @@ func NewMethodChannel(messenger BinaryMessenger, channelName string, methodCodec
return mc
}

// InvokeMethod sends a methodcall to the binary messenger and waits for a
// result. Results from the Flutter side are not yet implemented in the
// embedder. Until then, InvokeMethod will always return nil as result.
// https://github.com/flutter/flutter/issues/18852
func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) (result interface{}, err error) {
// InvokeMethod sends a methodcall to the binary messenger without waiting for
// a reply. and waits for a result.
func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) error {
encodedMessage, err := m.methodCodec.EncodeMethodCall(MethodCall{
Method: name,
Arguments: arguments,
})
if err != nil {
return errors.Wrap(err, "failed to encode methodcall")
}
err = m.messenger.SendNoReply(m.channelName, encodedMessage)
if err != nil {
return errors.Wrap(err, "failed to send methodcall")
}
return nil
}

// InvokeMethodWithReply sends a methodcall to the binary messenger and wait
// for a reply.
//
// NOTE: If no value are returned by the handler setted in the
// setMethodCallHandler flutter method, the function will wait forever. In case
// you don't want to wait for reply, use InvokeMethod or launch the
// function in a goroutine.
func (m *MethodChannel) InvokeMethodWithReply(name string, arguments interface{}) (result interface{}, err error) {
encodedMessage, err := m.methodCodec.EncodeMethodCall(MethodCall{
Method: name,
Arguments: arguments,
Expand All @@ -56,12 +76,6 @@ func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) (result
if err != nil {
return nil, errors.Wrap(err, "failed to send methodcall")
}
// TODO(GeertJohan): InvokeMethod may not return any JSON. In Java this is
// handled by not having a callback handler, which means no response is
// expected and response is never unmarshalled. We should perhaps define
// InvokeMethod(..) and InovkeMethodNoResponse(..) to avoid errors when no
// response is given.
// https://github.com/go-flutter-desktop/go-flutter/issues/141
result, err = m.methodCodec.DecodeEnvelope(encodedReply)
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions plugin/method-channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ func TestMethodChannelJSONInvoke(t *testing.T) {
r.Send(binaryReply)
return nil
})
result, err := channel.InvokeMethod("sayHello", "hello")
result, err := channel.InvokeMethodWithReply("sayHello", "hello")
Nil(t, err)
Equal(t, json.RawMessage(`"hello world"`), result)

result, err = channel.InvokeMethod("invalidMethod", "")
result, err = channel.InvokeMethodWithReply("invalidMethod", "")
Nil(t, result)
expectedError := FlutterError{
Code: "unknown",
Expand Down
2 changes: 1 addition & 1 deletion textinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (p *textinputPlugin) glfwKeyCallback(window *glfw.Window, key glfw.Key, sca

keyboardShortcutBind := keyboardShortcutsGLFW{mod: mods}
if key == glfw.KeyEscape && action == glfw.Press {
_, err := defaultNavigationPlugin.channel.InvokeMethod("popRoute", nil)
err := defaultNavigationPlugin.channel.InvokeMethod("popRoute", nil)
if err != nil {
fmt.Printf("go-flutter: failed to pop route after escape key press: %v\n", err)
}
Expand Down