-3

In Flutter it is possible to develop plugins for executing platform specific code. For example on a windows host it is possible to invoke c++ code from the Flutter client with:

final int result = await platform.invokeMethod('getBatteryLevel');

At the windows host you can send a reply to this call for example with:

channel.SetMethodCallHandler(
    [](const flutter::MethodCall<>& call,
      std::unique_ptr<flutter::MethodResult<>> result) {
        if (call.method_name() == "getBatteryLevel") {
          int battery_level = GetBatteryLevel();
          if (battery_level != -1) {
            result->Success(battery_level);
          }
          else {
            result->Error("UNAVAILABLE", "Battery level not available.");
          }
        }
        else {
          result->NotImplemented();
        }
    });

I want to go the other direction. The following code sends the battery level from the host to the Flutter client:

int battery_level = GetBatteryLevel();
method_channel_->InvokeMethod(
                "sendBatteryLevel",
                std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
                    {flutter::EncodableValue("batteryLevel"), flutter::EncodableValue(battery_level)},
                }));
//string answer = await a string answer from method_channel_

But how can I send a return value back from the client to the host? I want to answer this call on the client, for example like

_handleMethodCall(MethodCall call) async {
    switch (call.method) {
      case "batteryLevel":
        final args = call.arguments as Map;
        final batteryLevel= args['batteryLevel'] as int;
        //Send return value
        call.answer('Thank you for informing me!'); //not working, this is what I want to do
        break;
    }
}

The method InvokeMethod(...) from flutter::MethodChannel has a flutter::MethodResult parameter. But i didn't manage to to call it properly in order to receive a return value for the call from the flutter client

Update: I tried smorgans suggestion already using this client code:

_handleMethodCall(MethodCall call) async {
    switch (call.method) {
      case "batteryLevel":
        final args = call.arguments as Map;
        final batteryLevel= args['batteryLevel'] as int;
        //Send return value
        return 'Thank you for informing me!'; //I want to receive this string at the C++ host code
    }
}

My problem is that i don't get the C++ host code working to receive the answer. I tried the following:

int battery_level = GetBatteryLevel();
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> resultPointer; // How do I declare this properly?
method_channel_->InvokeMethod(
                "sendBatteryLevel",
                std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
                    {flutter::EncodableValue("batteryLevel"), flutter::EncodableValue(battery_level)},
                }), resultPointer);
//std::string answer = exctractAnswerFromMethodResult(resultPointer); // how do I do this?

I tried to receive the answer as shown above, but i didn't manage to pass the MethodResult parameter properly to method_channel_->InvokeMethod. The code from above results in the this compiler error:

Compiler Error

Eberulf
  • 1
  • 3

2 Answers2

0

On the Dart side:

As it says in the MethodChannel documentation:

If the future returned by the handler completes with a result, that value is sent back to the platform plugin caller

Your call.answer line (which doesn't work because there is no such method on MethodCall) should just be a return.

On the C++ side:

MethodResult is an abstract class, so you cannot instantiate it. You need to either make your own subclass with the functionality you want and instantiate that, or use the provided MethodResultFunctions class if you want to use lambdas instead.

You also need to std::move the object you declare when you pass it, since it's a unique_ptr; you are passing ownership of the handler to the method.

The method channel unit tests might be useful as examples, but it also looks like you may want to spend some time with some general C++ class tutorials. Using these C++ APIs correctly is going to require require familiarity with core C++ concepts like classes and unique_ptr.

smorgan
  • 20,228
  • 3
  • 47
  • 55
  • Thank you for your fast reply. I tried this already, but I struggle to receive the answer at the C++ host cod. I updated one of my attempts together with the corresponding error. Could you please tell me how to specify the resultPointer to receive the answer properly? It would be really helpful as Im struggling on this since 2 weeks. – Eberulf Jul 10 '23 at 15:33
  • @Eberulf Waiting until someone answers the question you asked and then editing the question to be so different that the answer doesn't make sense any more undermines the useful of this site. It's supposed to be a reference for others who might have the same question later. – smorgan Jul 11 '23 at 21:09
  • I wasn't aware of that. I rolled my answer back and added an Update section at the end instead. I hope this is more acceptable. Im still interested in any help I can get on that topic. – Eberulf Jul 14 '23 at 07:00
  • @Eberulf I've answered your updated question now. – smorgan Jul 15 '23 at 23:42
-1

Following the suggestions of @smorgan, I managed to receive the client answer with the following host code:

#include "flutter/method_result_functions.h"

.
.
.

bool received_reply = false;
auto result_handler = std::make_unique<flutter::MethodResultFunctions<>>(
                [&received_reply](const flutter::EncodableValue* success_value) {
                  received_reply = true;
                  std::cout << "received answer: " << std::get<std::string>(*success_value) << std::endl;
                },
                nullptr, nullptr);

int battery_level = GetBatteryLevel();

method_channel_->InvokeMethod(
                "sendBatteryLevel",
                std::make_unique<flutter::EncodableValue>(flutter::EncodableMap{
                    {flutter::EncodableValue("batteryLevel"), flutter::EncodableValue(battery_level)},
                }), std::move(result_handler));

Therefore, I consider this problem solved. Thanks for helping me!

Eberulf
  • 1
  • 3
  • It looks like you copied code directly from the unit test I referenced. In real-world usage, `result_handler` will be called asynchronously, so writing to your `received_reply` variable will have undefined behavior; most likely it will corrupt memory, or crash outright. I strongly recommend against using reference capture in lambdas unless you have significant C++ experience. – smorgan Jul 19 '23 at 22:55
  • Thanks for that clarification. I thought so already, but I didn't test it. Why are your integration tests working then? Is one of the following statements after channel.InvokeMethod(...) waiting for the reply, so received_reply cannot go out of scope? Is there a recommended way to receive the success result in a synchronously way? – Eberulf Jul 21 '23 at 07:38
  • It's not an integration test, it's a unit test, testing just that specific part of the code. It works because the callback is manually triggered right after the handler is registered. Nothing asynchronous happens in the unit test, and no actual Flutter engine is involved. As for your question about receiving a result synchronously in actual usage, it's impossible. There's not just no recommended way, there is *no* way. It's an asynchronous calling system that involves multiple threads. – smorgan Jul 22 '23 at 00:18