ssttonn / flutter_watch_os_connectivity

A plugin that provides a wrapper that enables Flutter apps to communicate with apps running on WatchOS.
BSD 3-Clause "New" or "Revised" License
7 stars 12 forks source link

Possible issue: channel sent a message from native to Flutter on a non-platform thread #4

Open jakusb opened 11 months ago

jakusb commented 11 months ago

I see below message in my log. It seems a method needs to be wrapped in something like: dispatch_async(dispatch_get_main_queue(), ^{});

[VERBOSE-2:shell.cc(1004)] The 'sstonn/flutter_watch_os_connectivity_callback' channel sent a message from native to Flutter on a non-platform thread. Platform channel messages must be sent on the platform thread. Failure to do so may result in data loss or crashes, and must be fixed in the plugin or application code creating that channel. See https://docs.flutter.dev/platform-integration/platform-channels#channels-and-platform-threading for more information.

wolfulve commented 11 months ago

I ran into the same warning/issue indicated in the previous message, along with one crasher issue.

I affected the followings modifications in a local working copy:

  1. I wrapped all the callbackChannel.invokeMethod() calls with DispatchQueue.main.async {} That said, I believe only a few cases were causing the issue, e.g., calls made while not on main thread..
  2. fixed an illegal access (crasher issue), related to threading, that occurred in the replyMessage code block (within the function handle() switch statement). This fix was affected by wrapping all the code within:
  public func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping (`[String : Any]) -> Void) {
  ...
  ...
  }

which is not called on the main thread, with DispatchQueue.main.async {}

  1. lastly, I added code to remove the replyhandlerId from the map after the replyHandler is called, e..g, in the switch statement block for replyHandler, e.g.,
    replyHandler(message)
    messageReplyHandlers.removeValue(forKey: replyHandlerId) // added this line

    Not causing an issue, but, there is no need to keep the ids in the map after the reply handler function has been called.

**Note: since making the above changes, I've not seen any further issues.**

wolfulve commented 10 months ago

I also made the following changes to: ios/lib/watch_os_observer.dart (which was failing)


             .add(ActivationState.values[activateStateIndex]);
         break;
       case "pairDeviceInfoChanged":
-        Map<String, dynamic> rawPairedDeviceInfoJson =
-            (call.arguments as Map? ?? {}).toMapStringDynamic();
+        Map<String, dynamic> rawPairedDeviceInfoJson = {};
+        try {
+          rawPairedDeviceInfoJson =
+              (jsonDecode(call.arguments) as Map<String, dynamic>? ?? {})
+                  .toMapStringDynamic();
+        } catch (e) {
+          pairedDeviceInfoStreamController.addError(e);
+        }
         if (rawPairedDeviceInfoJson["error"] != null) {
           ///* Emit error and return
           pairedDeviceInfoStreamController

           WatchOsPairedDeviceInfo pairedDeviceInfo =
               WatchOsPairedDeviceInfo.fromJson(rawPairedDeviceInfoJson);
           pairedDeviceInfoStreamController.add(pairedDeviceInfo);
-        } catch (e) {
-          pairedDeviceInfoStreamController.addError(e);
+        } catch (ee) {
+          pairedDeviceInfoStreamController.addError(ee);
         }
         break;
       case "messageReceived":
wolfulve commented 10 months ago

Code is switch now appears as:

   case "pairDeviceInfoChanged":
        Map<String, dynamic> rawPairedDeviceInfoJson = {};
        try {
          rawPairedDeviceInfoJson =
              (jsonDecode(call.arguments) as Map<String, dynamic>? ?? {})
                  .toMapStringDynamic();
        } catch (e) {
          pairedDeviceInfoStreamController.addError(e);
        }
        if (rawPairedDeviceInfoJson["error"] != null) {
          ///* Emit error and return
          pairedDeviceInfoStreamController
              .addError(Exception(rawPairedDeviceInfoJson["error"]));
          return;
        }
        try {
          ///* Map raw map to [PairedDeviceInfo] object
          WatchOsPairedDeviceInfo pairedDeviceInfo =
              WatchOsPairedDeviceInfo.fromJson(rawPairedDeviceInfoJson);
          pairedDeviceInfoStreamController.add(pairedDeviceInfo);
        } catch (ee) {
          pairedDeviceInfoStreamController.addError(ee);
        }
        break;
chadpav commented 9 months ago

@wolfulve any chance you might submit a PR to the repo? I came here because of the same issue.

I submitted a PR for the original issue for this post. You had some additional fixes in the code that I don't use so I'm hesitant to include those in my PR.