Skip to content

Commit be33505

Browse files
committed
Feature/callback messages (#220)
1 parent f7b8240 commit be33505

15 files changed

+181
-41
lines changed

application.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func (a *Application) Run() error {
104104
glfw.WindowHint(glfw.ContextVersionMinor, 1)
105105
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
106106
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
107-
107+
108108
if a.config.windowInitialLocations.xpos != 0 {
109109
// To create the window at a specific position, make it initially invisible
110110
// using the Visible window hint, set its position and then show it.

embedder/embedder.go

+46-4
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ package embedder
33
// #include "embedder.h"
44
// FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine, FlutterProjectArgs * Args,
55
// const char *const * vmArgs, int nVmAgrs);
6+
// FlutterEngineResult
7+
// createMessageResponseHandle(FlutterEngine engine, void *user_data,
8+
// FlutterPlatformMessageResponseHandle **reply);
69
// char** makeCharArray(int size);
710
// void setArrayString(char **a, char *s, int n);
811
// const int32_t kFlutterSemanticsNodeIdBatchEnd = -1;
912
// const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1;
1013
import "C"
1114
import (
15+
"errors"
1216
"fmt"
17+
"runtime"
1318
"runtime/debug"
1419
"sync"
1520
"unsafe"
@@ -256,8 +261,11 @@ type PlatformMessage struct {
256261
Channel string
257262
Message []byte
258263

259-
// ResponseHandle is only set when receiving a platform message.
260-
// https://github.com/flutter/flutter/issues/18852
264+
// ResponseHandle is set on some recieved platform message. All
265+
// PlatformMessage recieved with this attribute must send a response with
266+
// `SendPlatformMessageResponse`.
267+
// ResponseHandle can also be created from the embedder side when a
268+
// platform(golang) message needs native callback.
261269
ResponseHandle PlatformMessageResponseHandle
262270
}
263271

@@ -357,8 +365,42 @@ func (flu *FlutterEngine) MarkExternalTextureFrameAvailable(textureID int64) Res
357365
return (Result)(res)
358366
}
359367

360-
// FlutterEngineGetCurrentTime gets the current time in nanoseconds from the
361-
// clock used by the flutter engine.
368+
// DataCallback is a function called when a PlatformMessage response send back
369+
// to the embedder.
370+
type DataCallback func(binaryReply []byte)
371+
372+
// CreatePlatformMessageResponseHandle creates a platform message response
373+
// handle that allows the embedder to set a native callback for a response to a
374+
// message.
375+
// Must be collected via `ReleasePlatformMessageResponseHandle` after the call
376+
// to `SendPlatformMessage`.
377+
func (flu *FlutterEngine) CreatePlatformMessageResponseHandle(callback DataCallback) (PlatformMessageResponseHandle, error) {
378+
var responseHandle *C.FlutterPlatformMessageResponseHandle
379+
380+
callbackPointer := uintptr(unsafe.Pointer(&callback))
381+
defer func() {
382+
runtime.KeepAlive(callbackPointer)
383+
}()
384+
385+
res := C.createMessageResponseHandle(flu.Engine, unsafe.Pointer(&callbackPointer), &responseHandle)
386+
if (Result)(res) != ResultSuccess {
387+
return 0, errors.New("failed to create a response handle")
388+
}
389+
return PlatformMessageResponseHandle(unsafe.Pointer(responseHandle)), nil
390+
}
391+
392+
// ReleasePlatformMessageResponseHandle collects a platform message response
393+
// handle.
394+
func (flu *FlutterEngine) ReleasePlatformMessageResponseHandle(responseHandle PlatformMessageResponseHandle) {
395+
cResponseHandle := (*C.FlutterPlatformMessageResponseHandle)(unsafe.Pointer(responseHandle))
396+
res := C.FlutterPlatformMessageReleaseResponseHandle(flu.Engine, cResponseHandle)
397+
if (Result)(res) != ResultSuccess {
398+
fmt.Printf("go-flutter: failed to collect platform response message handle")
399+
}
400+
}
401+
402+
// FlutterEngineGetCurrentTime gets the current time in nanoseconds from the clock used by the flutter
403+
// engine.
362404
func FlutterEngineGetCurrentTime() uint64 {
363405
return uint64(C.FlutterEngineGetCurrentTime())
364406
}

embedder/embedder_helper.c

+11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ bool proxy_runs_task_on_current_thread_callback(void *user_data);
2121
void proxy_post_task_callback(FlutterTask task, uint64_t target_time_nanos,
2222
void *user_data);
2323

24+
void proxy_desktop_binary_reply(const uint8_t *data, size_t data_size,
25+
void *user_data);
26+
2427
// C helper
2528
FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine,
2629
FlutterProjectArgs *Args,
@@ -62,3 +65,11 @@ FlutterEngineResult runFlutter(void *user_data, FlutterEngine *engine,
6265
char **makeCharArray(int size) { return calloc(sizeof(char *), size); }
6366

6467
void setArrayString(char **a, char *s, int n) { a[n] = s; }
68+
69+
FlutterEngineResult
70+
createMessageResponseHandle(FlutterEngine engine, void *user_data,
71+
FlutterPlatformMessageResponseHandle **reply) {
72+
73+
return FlutterPlatformMessageCreateResponseHandle(
74+
engine, proxy_desktop_binary_reply, user_data, reply);
75+
}

embedder/embedder_proxy.go

+7
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,10 @@ func proxy_post_task_callback(task C.FlutterTask, targetTimeNanos C.uint64_t, us
9191
flutterEngine := (*FlutterEngine)(unsafe.Pointer(flutterEnginePointer))
9292
flutterEngine.TaskRunnerPostTask(task, uint64(targetTimeNanos))
9393
}
94+
95+
//export proxy_desktop_binary_reply
96+
func proxy_desktop_binary_reply(data *C.uint8_t, dataSize C.size_t, userData unsafe.Pointer) {
97+
callbackPointer := *(*uintptr)(userData)
98+
handler := *(*DataCallback)(unsafe.Pointer(callbackPointer))
99+
handler(C.GoBytes(unsafe.Pointer(data), C.int(dataSize)))
100+
}

key-events.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (p *keyeventPlugin) sendKeyEvent(window *glfw.Window, key glfw.Key, scancod
8181
ScanCode: scancode,
8282
Modifiers: int(mods),
8383
}
84-
_, err := p.keyEventChannel.Send(event)
84+
err := p.keyEventChannel.Send(event)
8585
if err != nil {
8686
fmt.Printf("go-flutter: Failed to send raw_keyboard event %v: %v\n", event, err)
8787
}

lifecycle.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (p *lifecyclePlugin) glfwIconifyCallback(w *glfw.Window, iconified bool) {
3333
case false:
3434
state = "AppLifecycleState.resumed"
3535
}
36-
_, err := p.channel.Send(state)
36+
err := p.channel.Send(state)
3737
if err != nil {
3838
fmt.Printf("go-flutter: Failed to send lifecycle event %s: %v\n", state, err)
3939
}

messenger.go

+39-8
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,25 @@ func newMessenger(engine *embedder.FlutterEngine) *messenger {
3030
}
3131
}
3232

33-
// Send pushes a binary message on a channel to the Flutter side. Replies are
34-
// not supported yet (https://github.com/flutter/flutter/issues/18852). This
35-
// means that currently, binaryReply will be nil on success.
33+
// Send pushes a binary message on a channel to the Flutter side and wait for a
34+
// reply.
35+
// NOTE: If no value are returned by the flutter handler, the function will
36+
// wait forever. In case you don't want to wait for reply, use SendNoReply.
3637
func (m *messenger) Send(channel string, binaryMessage []byte) (binaryReply []byte, err error) {
38+
reply := make(chan []byte)
39+
defer close(reply)
40+
responseHandle, err := m.engine.CreatePlatformMessageResponseHandle(func(binaryMessage []byte) {
41+
reply <- binaryMessage
42+
})
43+
if err != nil {
44+
return nil, err
45+
}
46+
defer m.engine.ReleasePlatformMessageResponseHandle(responseHandle)
47+
3748
msg := &embedder.PlatformMessage{
38-
Channel: channel,
39-
Message: binaryMessage,
49+
Channel: channel,
50+
Message: binaryMessage,
51+
ResponseHandle: responseHandle,
4052
}
4153
res := m.engine.SendPlatformMessage(msg)
4254
if err != nil {
@@ -48,9 +60,28 @@ func (m *messenger) Send(channel string, binaryMessage []byte) (binaryReply []by
4860
return nil, errors.New("failed to send message")
4961
}
5062

51-
// NOTE: Response from engine is not yet supported by embedder.
52-
// https://github.com/flutter/flutter/issues/18852
53-
return nil, nil
63+
// wait for a reply and return
64+
return <-reply, nil
65+
}
66+
67+
// SendNoReply pushes a binary message on a channel to the Flutter side without
68+
// expecting replies.
69+
func (m *messenger) SendNoReply(channel string, binaryMessage []byte) (err error) {
70+
msg := &embedder.PlatformMessage{
71+
Channel: channel,
72+
Message: binaryMessage,
73+
}
74+
res := m.engine.SendPlatformMessage(msg)
75+
if err != nil {
76+
if ferr, ok := err.(*plugin.FlutterError); ok {
77+
return ferr
78+
}
79+
}
80+
if res != embedder.ResultSuccess {
81+
return errors.New("failed to send message")
82+
}
83+
84+
return nil
5485
}
5586

5687
// SetChannelHandler satisfies plugin.BinaryMessenger

plugin/basic-message-channel.go

+22-5
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,28 @@ func NewBasicMessageChannel(messenger BinaryMessenger, channelName string, codec
5353
return b
5454
}
5555

56-
// Send encodes and sends the specified message to the Flutter application and
57-
// returns the reply, or an error. Results from the Flutter side are not yet
58-
// implemented in the embedder. Until then, InvokeMethod will always return nil
59-
// as reult. https://github.com/flutter/flutter/issues/18852
60-
func (b *BasicMessageChannel) Send(message interface{}) (reply interface{}, err error) {
56+
// Send encodes and sends the specified message to the Flutter application
57+
// without waiting for a reply.
58+
func (b *BasicMessageChannel) Send(message interface{}) error {
59+
encodedMessage, err := b.codec.EncodeMessage(message)
60+
if err != nil {
61+
return errors.Wrap(err, "failed to encode outgoing message")
62+
}
63+
err = b.messenger.SendNoReply(b.channelName, encodedMessage)
64+
if err != nil {
65+
return errors.Wrap(err, "failed to send outgoing message")
66+
}
67+
return nil
68+
}
69+
70+
// SendWithReply encodes and sends the specified message to the Flutter
71+
// application and returns the reply, or an error.
72+
//
73+
// NOTE: If no value are returned by the handler setted in the
74+
// setMessageHandler flutter method, the function will wait forever. In case
75+
// you don't want to wait for reply, use Send or launch the
76+
// function in a goroutine.
77+
func (b *BasicMessageChannel) SendWithReply(message interface{}) (reply interface{}, err error) {
6178
encodedMessage, err := b.codec.EncodeMessage(message)
6279
if err != nil {
6380
return nil, errors.Wrap(err, "failed to encode outgoing message")

plugin/basic-message-channel_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func TestBasicMethodChannelStringCodecSend(t *testing.T) {
3131
return nil
3232
})
3333
channel := NewBasicMessageChannel(messenger, "ch", codec)
34-
reply, err := channel.Send("hello")
34+
reply, err := channel.SendWithReply("hello")
3535
if err != nil {
3636
t.Fatal(err)
3737
}
@@ -100,7 +100,7 @@ func TestBasicMethodChannelBinaryCodecSend(t *testing.T) {
100100
return nil
101101
})
102102
channel := NewBasicMessageChannel(messenger, "ch", codec)
103-
reply, err := channel.Send([]byte{0x01})
103+
reply, err := channel.SendWithReply([]byte{0x01})
104104
if err != nil {
105105
t.Fatal(err)
106106
}
@@ -160,7 +160,7 @@ func TestBasicMethodChannelNilMockHandler(t *testing.T) {
160160
messenger := NewTestingBinaryMessenger()
161161
messenger.MockSetChannelHandler("ch", nil)
162162
channel := NewBasicMessageChannel(messenger, "ch", codec)
163-
reply, err := channel.Send("hello")
163+
reply, err := channel.SendWithReply("hello")
164164
Nil(t, reply)
165165
NotNil(t, err)
166166
Equal(t, "failed to send outgoing message: no handler set", err.Error())
@@ -170,7 +170,7 @@ func TestBasicMethodChannelEncodeFail(t *testing.T) {
170170
codec := StringCodec{}
171171
messenger := NewTestingBinaryMessenger()
172172
channel := NewBasicMessageChannel(messenger, "ch", codec)
173-
reply, err := channel.Send(int(42)) // invalid value
173+
reply, err := channel.SendWithReply(int(42)) // invalid value
174174
Nil(t, reply)
175175
NotNil(t, err)
176176
Equal(t, "failed to encode outgoing message: invalid type provided to message codec: expected message to be of type string", err.Error())

plugin/binary-messenger.go

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ type BinaryMessenger interface {
55
// Send sends a binary message to the Flutter application.
66
Send(channel string, binaryMessage []byte) (binaryReply []byte, err error)
77

8+
// SendNoReply sends a binary message to the Flutter application without
9+
// expecting a reply.
10+
SendNoReply(channel string, binaryMessage []byte) (err error)
11+
812
// SetChannelHandler registers a handler to be invoked when the Flutter
913
// application sends a message to its host platform on given channel.
1014
//

plugin/event-sink.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ func (es *EventSink) Success(event interface{}) {
3636
if err != nil {
3737
fmt.Printf("go-flutter: failed to encode success envelope for event channel '%s', error: %v\n", es.eventChannel.channelName, err)
3838
}
39-
es.eventChannel.messenger.Send(es.eventChannel.channelName, binaryMsg)
39+
err = es.eventChannel.messenger.SendNoReply(es.eventChannel.channelName, binaryMsg)
40+
if err != nil {
41+
fmt.Printf("go-flutter: failed to send Success message on event channel '%s', error: %v\n", es.eventChannel.channelName, err)
42+
}
4043
}
4144

4245
// Error consumes an error event.
@@ -51,7 +54,10 @@ func (es *EventSink) Error(errorCode string, errorMessage string, errorDetails i
5154
if err != nil {
5255
fmt.Printf("go-flutter: failed to encode success envelope for event channel '%s', error: %v\n", es.eventChannel.channelName, err)
5356
}
54-
es.eventChannel.messenger.Send(es.eventChannel.channelName, binaryMsg)
57+
err = es.eventChannel.messenger.SendNoReply(es.eventChannel.channelName, binaryMsg)
58+
if err != nil {
59+
fmt.Printf("go-flutter: failed to send Error message on event channel '%s', error: %v\n", es.eventChannel.channelName, err)
60+
}
5561
}
5662

5763
// EndOfStream consumes end of stream.
@@ -63,5 +69,8 @@ func (es *EventSink) EndOfStream() {
6369
}
6470
es.hasEnded = true
6571

66-
es.eventChannel.messenger.Send(es.eventChannel.channelName, nil)
72+
err := es.eventChannel.messenger.SendNoReply(es.eventChannel.channelName, nil)
73+
if err != nil {
74+
fmt.Printf("go-flutter: failed to send EndOfStream message on event channel '%s', error: %v\n", es.eventChannel.channelName, err)
75+
}
6776
}

plugin/helper_test.go

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ func NewTestingBinaryMessenger() *TestingBinaryMessenger {
2929

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

32+
func (t *TestingBinaryMessenger) SendNoReply(channel string, message []byte) (err error) {
33+
_, err = t.Send(channel, message)
34+
return err
35+
}
36+
3237
// Send sends the bytes onto the given channel.
3338
// In this testing implementation of a BinaryMessenger, the handler for the
3439
// channel may be set using MockSetMessageHandler

plugin/method-channel.go

+25-11
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,31 @@ func NewMethodChannel(messenger BinaryMessenger, channelName string, methodCodec
4040
return mc
4141
}
4242

43-
// InvokeMethod sends a methodcall to the binary messenger and waits for a
44-
// result. Results from the Flutter side are not yet implemented in the
45-
// embedder. Until then, InvokeMethod will always return nil as result.
46-
// https://github.com/flutter/flutter/issues/18852
47-
func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) (result interface{}, err error) {
43+
// InvokeMethod sends a methodcall to the binary messenger without waiting for
44+
// a reply.
45+
func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) error {
46+
encodedMessage, err := m.methodCodec.EncodeMethodCall(MethodCall{
47+
Method: name,
48+
Arguments: arguments,
49+
})
50+
if err != nil {
51+
return errors.Wrap(err, "failed to encode methodcall")
52+
}
53+
err = m.messenger.SendNoReply(m.channelName, encodedMessage)
54+
if err != nil {
55+
return errors.Wrap(err, "failed to send methodcall")
56+
}
57+
return nil
58+
}
59+
60+
// InvokeMethodWithReply sends a methodcall to the binary messenger and wait
61+
// for a reply.
62+
//
63+
// NOTE: If no value are returned by the handler setted in the
64+
// setMethodCallHandler flutter method, the function will wait forever. In case
65+
// you don't want to wait for reply, use InvokeMethod or launch the
66+
// function in a goroutine.
67+
func (m *MethodChannel) InvokeMethodWithReply(name string, arguments interface{}) (result interface{}, err error) {
4868
encodedMessage, err := m.methodCodec.EncodeMethodCall(MethodCall{
4969
Method: name,
5070
Arguments: arguments,
@@ -56,12 +76,6 @@ func (m *MethodChannel) InvokeMethod(name string, arguments interface{}) (result
5676
if err != nil {
5777
return nil, errors.Wrap(err, "failed to send methodcall")
5878
}
59-
// TODO(GeertJohan): InvokeMethod may not return any JSON. In Java this is
60-
// handled by not having a callback handler, which means no response is
61-
// expected and response is never unmarshalled. We should perhaps define
62-
// InvokeMethod(..) and InovkeMethodNoResponse(..) to avoid errors when no
63-
// response is given.
64-
// https://github.com/go-flutter-desktop/go-flutter/issues/141
6579
result, err = m.methodCodec.DecodeEnvelope(encodedReply)
6680
if err != nil {
6781
return nil, err

plugin/method-channel_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ func TestMethodChannelJSONInvoke(t *testing.T) {
2929
r.Send(binaryReply)
3030
return nil
3131
})
32-
result, err := channel.InvokeMethod("sayHello", "hello")
32+
result, err := channel.InvokeMethodWithReply("sayHello", "hello")
3333
Nil(t, err)
3434
Equal(t, json.RawMessage(`"hello world"`), result)
3535

36-
result, err = channel.InvokeMethod("invalidMethod", "")
36+
result, err = channel.InvokeMethodWithReply("invalidMethod", "")
3737
Nil(t, result)
3838
expectedError := FlutterError{
3939
Code: "unknown",

0 commit comments

Comments
 (0)