Search code examples
windowsflutterflutter-pluginflutter-windowsflutter-platform-channel

Flutter platform channels pass "multi-data" to windows function


I am testing the Platform-Channels with the windows visual studio 2022.

I can pass the single value from the flutter UI while I get the battery value, but I want to send multi-data from flutter to the windows code. Here is mine test code which I follow the flutter tutorial:

class _MyHomePageState extends State<MyHomePage> {
  static const MethodChannel platform =
      MethodChannel('samples.flutter.dev/battery');

  String _batteryLevel = 'Unknown battery level.';
  int value = 100;
  Future<void> _getBatteryLevel(int fluval) async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel', fluval);
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }
    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            ElevatedButton(
              onPressed: () {
                _getBatteryLevel(100);
              },
              child: const Text('Get Battery Level'),
            ),
            Text(_batteryLevel),
          ],
        ),
      ),
    );
  }
}

and the windows side:

void initMethodChannel(flutter::FlutterEngine* flutter_instance) {
    const static std::string channel_name("samples.flutter.dev/battery");
    auto channel =
        std::make_unique<flutter::MethodChannel<>>(
            flutter_instance->messenger(), channel_name,
            &flutter::StandardMethodCodec::GetInstance());
    channel->SetMethodCallHandler(
        [](const flutter::MethodCall<>& call,
            std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {

                // cheack method name called from dart
                if (call.method_name().compare("getBatteryLevel") == 0) {
                    int battery_level = GetBatteryLevel();
                    const auto* flu_value = std::get_if<int>(call.arguments());
                    std::cout << typeid(flu_value).name() << std::endl;

                    if (!flu_value) {
                        result->Error("Missing required type parameter","Expected int");
                    }
                    std::cout << "From Flutter: " << *flu_value << std::endl;

                    if (battery_level != -1) {
                        result->Success(battery_level);
                    }
                    else {
                        result->Error("UNAVAILABLE", "Battery level not available.");
                    }
                }
                else {
                    result->NotImplemented();
                }
        });
}

if I want to send multi-data like this:

Future<void> _getBatteryLevel(int fluval, int data2,...,...) ...

How should I do or parse these two values fluval and data2 when call once invokeMethod?

Any examples that I can reference? or tips for the Windows UI design?

I try to search but the most answer is for the Android or IOS app, and the android answer is using call.argument("value") which you can choose specific data value to use but the windows only has call.arguments(); function call, I don't know how to get the specific data I want.

============================================================ [Update] From the @Richard tips and example, I made a test and it works, thanks @Richard!

Here is the Flutter UI example code: (inside the Stateful Widget State class), fluval= 100, fluval2 = 87

  String _getBackV1 = 'Unknown Value 1.';
  String _getBackV2 = 'Unknown Value 2.';
  int value = 100;
  Future<void> _testMapPassValue(int fluval, int fluval2) async {
    String backValue1, backValue2;
    try {
      final Map<String, dynamic>? getReply = await platform
          .invokeMapMethod<String, dynamic>('testPass',
              <String, dynamic>{"fluval": fluval, "fluval2": fluval2});
      backValue1 = "Get Back Value 1 from windows: ${getReply!["fluval"]}";
      backValue2 = "Get Back Value 2 from windows: ${getReply!["fluval2"]}";
    } on PlatformException catch (e) {
      backValue1 = "Failed to get Value 1: '${e.message}'.";
      backValue2 = "Failed to get Value 2: '${e.message}'.";
    }
    setState(() {
      _getBackV1 = backValue1;
      _getBackV2 = backValue2;
    });
  }

At the Windows side example code:

using flutter::EncodableList;
using flutter::EncodableMap;
using flutter::EncodableValue;
constexpr char testMapPassValue[] = "testPass";

constexpr char passKey1[] = "fluval";
constexpr char passKey2[] = "fluval2";

The method call handler:

if (call.method_name().compare(testMapPassValue) == 0) {
  const auto* getArguments_flu = std::get_if<flutter::EncodableMap>(call.arguments());
  assert(getArguments_flu);
  return Flu_2_Win_MethodHandler(*getArguments_flu, std::move(result));
}

void Flu_2_Win_MethodHandler(
    const EncodableMap& args, std::unique_ptr<flutter::MethodResult<>> result) {
    int wins2fluValue1;
    int wins2fluValue2;
    EncodableMap win2fluMap;

    const auto* flu2winsValue1 =
        std::get_if<int>(ValueOrNull(args, passKey1));
    if (!flu2winsValue1) {
        return result->Error("argument_error",
            std::string(passKey1) + " argument missing");
    }
    wins2fluValue1 = *flu2winsValue1;
    std::cout << "From Flutter value1: " << *flu2winsValue1 << std::endl;
    wins2fluValue1 = wins2fluValue1 + 10;
    std::cout << "After calculate value1: " << wins2fluValue1 << std::endl;

    const auto* flu2winsValue2 =
        std::get_if<int>(ValueOrNull(args, passKey2));
    if (!flu2winsValue2) {
        return result->Error("argument_error",
            std::string(passKey2) + " argument missing");
    }
    wins2fluValue2 = *flu2winsValue2;
    std::cout << "From Flutter value1: " << *flu2winsValue2 << std::endl;
    wins2fluValue2 = wins2fluValue2 + 20;
    std::cout << "After calculate value2: " << wins2fluValue2 << std::endl;

    win2fluMap.insert(std::pair<std::string, int>("fluval", wins2fluValue1));
    win2fluMap.insert(std::pair<std::string, int>("fluval2", wins2fluValue2));
    
    return result->Success(std::move(EncodableValue(win2fluMap)));
}

and the final result:

FlutterUI Result

Thank you!


Solution

  • You can only pass a single entity as argument or result, but that entity can be any of the supported types (whether primitive - int, double, etc - or complex - list or map).

    The mapping between Dart and C++ (native for Windows) types is:

    // std::monostate       -> null
    // bool                 -> bool
    // int32_t              -> int
    // int64_t              -> int
    // double               -> double
    // std::string          -> String
    // std::vector<uint8_t> -> Uint8List
    // std::vector<int32_t> -> Int32List
    // std::vector<int64_t> -> Int64List
    // std::vector<float>   -> Float32List
    // std::vector<double>  -> Float64List
    // EncodableList        -> List
    // EncodableMap         -> Map
    

    (see encodable_value.h)

    So, to send your two integers (fluval and data2) you could choose to put them in a Dart list (i.e. <int>[123, 456]) or a Dart map (i.e. {'fluval':123, 'data2':456}). At the native end, arguments will be either an EncodableList or EncodableMap.

    Here's an example of how a complex Dart structure would appear at the native end:

    //   {
    //     'flag': true,
    //     'name': 'Thing',
    //     'values': [1, 2.0, 4],
    //   }
    // would correspond to:
    //   EncodableValue(EncodableMap{
    //       {EncodableValue("flag"), EncodableValue(true)},
    //       {EncodableValue("name"), EncodableValue("Thing")},
    //       {EncodableValue("values"), EncodableValue(EncodableList{
    //                                      EncodableValue(1),
    //                                      EncodableValue(2.0),
    //                                      EncodableValue(4),
    //                                  })},
    //   })
    

    The camera plugin has some example usage (to get the arguments as a map):

    const auto* arguments = std::get_if<flutter::EncodableMap>(method_call.arguments());
    

    In the same file, check out the two utility functions:

    // Looks for |key| in |map|, returning the associated value if it is present, or
    // a nullptr if not.
    const EncodableValue* ValueOrNull(const EncodableMap& map, const char* key) {
      auto it = map.find(EncodableValue(key));
      if (it == map.end()) {
        return nullptr;
      }
      return &(it->second);
    }
    
    // Looks for |key| in |map|, returning the associated int64 value if it is
    // present, or std::nullopt if not.
    std::optional<int64_t> GetInt64ValueOrNull(const EncodableMap& map,
                                               const char* key) {
      auto value = ValueOrNull(map, key);
      if (!value) {
        return std::nullopt;
      }
    
      if (std::holds_alternative<int32_t>(*value)) {
        return static_cast<int64_t>(std::get<int32_t>(*value));
      }
      auto val64 = std::get_if<int64_t>(value);
      if (!val64) {
        return std::nullopt;
      }
      return *val64;
    }
    

    and their typical usage:

      const auto* camera_name =
          std::get_if<std::string>(ValueOrNull(args, kCameraNameKey));
    
      auto camera_id = GetInt64ValueOrNull(args, kCameraIdKey);