Skip to content

Commit 75aafca

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

14 files changed

+174
-40
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/event-sink.go

Lines changed: 12 additions & 3 deletions
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
}

0 commit comments

Comments
 (0)