Skip to content

Commit 6fb18b7

Browse files
committed
Feature/callback messages (#220)
1 parent 0d39b44 commit 6fb18b7

13 files changed

+162
-37
lines changed

embedder/embedder.go

Lines changed: 39 additions & 3 deletions
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"
@@ -257,7 +262,6 @@ type PlatformMessage struct {
257262
Message []byte
258263

259264
// ResponseHandle is only set when receiving a platform message.
260-
// https://github.com/flutter/flutter/issues/18852
261265
ResponseHandle PlatformMessageResponseHandle
262266
}
263267

@@ -357,8 +361,40 @@ func (flu *FlutterEngine) MarkExternalTextureFrameAvailable(textureID int64) Res
357361
return (Result)(res)
358362
}
359363

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

embedder/embedder_helper.c

Lines changed: 11 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package flutter
22

33
import (
4-
"errors"
54
"fmt"
65
"sync"
76

87
"github.com/go-flutter-desktop/go-flutter/embedder"
98
"github.com/go-flutter-desktop/go-flutter/internal/tasker"
109
"github.com/go-flutter-desktop/go-flutter/plugin"
10+
"github.com/pkg/errors"
1111
)
1212

1313
type messenger struct {
@@ -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

Lines changed: 22 additions & 5 deletions
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

Lines changed: 4 additions & 4 deletions
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

Lines changed: 4 additions & 0 deletions
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 replies.
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/helper_test.go

Lines changed: 5 additions & 0 deletions
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

Lines changed: 25 additions & 11 deletions
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. and waits for a result.
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

Lines changed: 2 additions & 2 deletions
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",

textinput.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (p *textinputPlugin) glfwKeyCallback(window *glfw.Window, key glfw.Key, sca
117117

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

0 commit comments

Comments
 (0)