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

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

Lines changed: 46 additions & 4 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"
@@ -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

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: 39 additions & 8 deletions
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

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 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
//

0 commit comments

Comments
 (0)