kimusan
kimusan

Reputation: 545

How do I listen for an async response while doing invokeMethod()?

I am working on a small flutter app where I use a native library for some computation. The communication is two-way between dart and java (on android) and uses methodChannels for this. I call await in_channel.invokeMethod("someJavaMethod") from dart to start the computation. This triggers an init of the native library from Java. The result from this init is coming back as an async JNI call which then triggers out_channel.invokeMethod("someDartMethod").

My plan was to bind the out_channel to a local dart broadcast stream such that I could invoke someJavaMethod and then just await myMethodStream.where((m) => m.method == "someDartMethod")...

Problem is that the "someDartMethod" can come before the "someJavaMethod" call invocation has returned.

combined code example of what I have:

    static const MethodChannel _channel_in = const 
    MethodChannel('native_lib_wrapper_out'); 
    static const MethodChannel _channel_out = const 
    MethodChannel('native_lib_wrapper_in');
    final StreamController<MethodCall> _methodStreamController = new 
    StreamController.broadcast();

    NativeLibWrapper._() {
          _channel_in.setMethodCallHandler((MethodCall call) {
              _methodStreamController.add(call);
              return;
         });
    }


    Future<Map<dynamic,dynamic>> initLib(String id, String filePath) 
      async {
        Map<dynamic,dynamic> ret;

        ret = await _channel_out.invokeMethod("initLib",  <String, 
        dynamic> { // data to be passed to the function
                    'id': id,
                    'filePath': filePath,
                  });
        print('initLib - invokeMethod done. wait for stream');
        if(ret["status"] == 0) {
          await NativeLibWrapper.instance._methodStream
                .where((m) => m.method == "libInitEnded")
                .map((m){
                    var args = m.arguments;
                    ret = args;
                  }).first;
        }
        return ret;
    }

I would have expected the code to get the method call libInitEnded on my stream and then it should return after that point but it continuously hangs in the await on the stream and from the logs it looks like the libInitEnded is called before the print in the middle.

So is there a better way to structure this? it will not be the only methods going back and forth so I hope to get a good stable solution for this.

Upvotes: 2

Views: 3718

Answers (1)

Richard Heap
Richard Heap

Reputation: 51751

One channel

You should only need one channel. No need for in and out channels. Both ends may invoke operations on the other over the one channel.

There's only one UI thread

When you call from Dart to Native, the native method is handled by the native UI thread. Unless you are using a thread pool, that means that Dart to Native methods are handled in order. There's no point in not awaiting the answer of every native method. Or, in other words, there's no point in launching two native methods at the same time, since they will be executed consecutively by the single native thread. (Note that you should not perform time-consuming operations on the native thread, as this will interfere with other things it does like gesture detection.) Every Dart to native method should return its result.

Using a thread pool

If the single thread / single method call at a time is unacceptable, consider a thread pool at the native end. Now you can have multiple methods in flight, since there are multiple threads of execution. Now you should design your call/response like you might for communication with a server over a socket. The client gives each request an "invoke id". Each method simply returns a boolean that the request was queued. On completion of the request, the other end invokes the 'done' method, passing the original id and the result. The caller can then match up the response id with the request id and handle the response appropriately (and cancel any timer started to detect timeout). Note that responses can then arrive in any order, but are matched with their request by id.

On Android, you must invoke native to Dart methods on the UIThread. If you are calling the 'done' method from a worker thread you need to post a Runnable lambda to the main looper.

Upvotes: 2

Related Questions