Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

onEndpointFound never gets called in dart code. Error : FlutterJNI(26633): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: nearby_connections. Response ID: 4 #75

Closed
rahulmaindargi opened this issue Jul 30, 2024 · 19 comments

Comments

@rahulmaindargi
Copy link
Contributor

Below is the error when discovery finds a device. but onEndpointFound on dart side never gets called.

NearbyConnections(26633): NFC discovery started.
D/NearbyConnections(26633): Invalidating dispatch state.
D/NearbyConnections(26633): Starting NFC dispatching.
D/NearbyConnections(26633): Cannot dispatch NFC events. NFC is not supported.
D/nearby_connections(26633): startDiscovery
D/nearby_connections(26633): onEndpointFound
W/FlutterJNI(26633): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: nearby_connections. Response ID: 4

@mannprerak2
Copy link
Owner

Are you using this in a background service?

This package is not built in a way to be used inside a background service, and I'm not sure if it's even possible to do so.

@rahulmaindargi
Copy link
Contributor Author

rahulmaindargi commented Jul 30, 2024

No I am not using as background service.
but its using a separate isolate after connection is initialized.

I am working on a expense manager app , and want to sync/send data over nearby communication. I am using bloc cubit.
I use separate isolate so that I can show progress of transfer over UI

To be exact, on bloc cubit call, I start nearby Discover, on endpoint found, I request connection, once connection is approved onConnectionInitiated I stop discovery, then then try to send data separate isolate.

but code never reaches upto isolate part. on on endpoint found callback never gets called and in console I get above error message.

@rahulmaindargi
Copy link
Contributor Author

I have no experience of plugin/package development, but from stackoverflow

As per first answer, root cause appears to be using the wrong (or old) method channel. inside plugin/package code.
so raised the issue here.

@rahulmaindargi
Copy link
Contributor Author

if it helps here is my cubit.sendDatamethod has below code

await Nearby().startDiscovery(
        configId,
        Strategy.P2P_POINT_TO_POINT, // https://developers.google.com/nearby/connections/strategies
        onEndpointFound: (String id, String userName, String serviceId) {
          // called when an advertiser is
          logger.d("onEndpointFound $id $userName $serviceId");
}
...

but execution never reaches logger line mentioned, it fails and gives above error.

@mannprerak2
Copy link
Owner

I'm guessing you cannot use a separate isolate, all the nearby connection interaction including any callbacks must be done in the main isolate itself.

Try removing the isolate usage.

@rahulmaindargi
Copy link
Contributor Author

but code doesn't even reach the isolate part.
any isolate related code will come into execution only after onConnectionInitiated but ENdpoint found fails itself.

I will try commenting the actual business logic (that involves isolate) tomorrow and update here.
but I am 99% sure, its not the cause for the issue.

Kindly check below stackoverflow thread once you get time.

https://stackoverflow.com/questions/61934900/tried-to-send-a-platform-message-to-flutter-but-flutterjni-was-detached-from-n

@mannprerak2
Copy link
Owner

Method channel is not being cached. See

channel = new MethodChannel(binding.getBinaryMessenger(), "nearby_connections");

I'm guessing you are somehow using an isolate to call the nearby connection function.
To be sure you can try running the example app on your device, if that works it's probably because of isolate usage.

If you are still not sure, you can post a link to a minimal reproducible sample I might be able to check it out over the weekend.

@rahulmaindargi
Copy link
Contributor Author

image

Sorry for so late reply.

I found that

bool a = await Nearby().startAdvertising(...); logger.d("Advertising started $a");
never completes the future i.e. logger line never gets executed neither any error.

@rahulmaindargi
Copy link
Contributor Author

rahulmaindargi commented Sep 9, 2024

bool a = await Nearby().startAdvertising(...); logger.d("Advertising started $a");
never completes the future i.e. logger line never gets executed neither any error.

Sorry looks like flutter version upgrade messed with gradle big time.
will check and let you know

@rahulmaindargi
Copy link
Contributor Author

I tried to simplify the code, and created a stateful widget like below.
but when I get to this widget mentioned in below code

It never finds endpoints and gives below message in console

D/NearbyConnections(30221): NFC discovery started.
D/NearbyConnections(30221): Invalidating dispatch state.
D/NearbyConnections(30221): Starting NFC dispatching.
D/NearbyConnections(30221): Cannot dispatch NFC events. NFC is not supported.
D/nearby_connections(30221): startDiscovery
D/nearby_connections(30221): startAdvertising
D/nearby_connections(30221): onEndpointFound
W/FlutterJNI(30221): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: nearby_connections. Response ID: 4

Here is the sample widget i have

import 'dart:typed_data';

import 'package:fin_stalk/main.dart';
import 'package:fin_stalk/service/nearby_service/permissions.dart';
import 'package:flutter/material.dart';
import 'package:googleapis/shared.dart';
import 'package:nearby_connections/nearby_connections.dart';

class NearbyConnectionsScreen extends StatefulWidget {
  final String configId;
  const NearbyConnectionsScreen({super.key, required this.configId});

  @override
  State<NearbyConnectionsScreen> createState() =>
      _NearbyConnectionsScreenState();
}

class _NearbyConnectionsScreenState extends State<NearbyConnectionsScreen> {
  final Strategy strategy = Strategy.P2P_POINT_TO_POINT;
  
  List<DiscoveredEndpoint> discoveredEndpoints = [];
  List<ConnectedDevice> connectedDevices = [];
  
  @override
  void initState() {
    super.initState();
    _initNearby();
  }

  @override
  void dispose() {
    Nearby().stopDiscovery();
    Nearby().stopAdvertising();
    super.dispose();
  }

  Future<void> _initNearby() async {
    //await Nearby().askPermissions(); // Request necessary permissions
    await Permissions.checkAndRequestPermissions();
    logger.d("Starting Discovery");
    await Nearby().startDiscovery(
      widget.configId, // Replace with your unique service ID
      strategy,
      onEndpointFound: (String endpointId, String endpointName, String serviceId) {
        var endpoint=DiscoveredEndpoint(endpointId,endpointName,serviceId);
        setState(() {
          discoveredEndpoints.add(endpoint);
        });
      },
      onEndpointLost: (String? endpointId) {
        setState(() {
          discoveredEndpoints.removeWhere((e) => e.endpointId == endpointId);
        });
      },
      serviceId: "sample.abc_abcd",
    ).onError((error, stackTrace) {
      logger.e("Error startDiscovery", time: DateTime.now(), error: error, stackTrace: stackTrace);
      return false;
    },);
    logger.d("Starting startAdvertising");
    await Nearby().startAdvertising(
      widget.configId,
      strategy,
      onConnectionInitiated: (endpoint, connectionInfo) {
        // Handle connection initiation
      },
      onConnectionResult: (endpointId, status) {
        if (status == Status.CONNECTED) {
          var endpoint= discoveredEndpoints.firstWhere((e)=>e.endpointId==endpointId);
          setState(() {
            connectedDevices.add(ConnectedDevice(endpoint));
          });
        } else {
          // Handle connection failure
        }
      },
      onDisconnected: (endpointId) {
        setState(() {
          connectedDevices.removeWhere((d) => d.endpoint.endpointId == endpointId);
        });
      },
      serviceId: "sample.abc_abcd", // Replace with your unique service ID
    ).onError((error, stackTrace) {
      logger.e("Error startAdvertising", time: DateTime.now(), error: error, stackTrace: stackTrace);
      return false;
    },);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Nearby Connections'),
      ),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: discoveredEndpoints.length,
              itemBuilder: (context, index) {
                final endpoint = discoveredEndpoints[index];
                return ListTile(
                  title: Text(endpoint.endpointName),
                  subtitle: Text(endpoint.endpointId),
                  onTap: () => _requestConnection(endpoint),
                );
              },
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: connectedDevices.length,
              itemBuilder: (context, index) {
                final device = connectedDevices[index];
                return ListTile(
                  title: Text(device.endpoint.endpointName),
                  trailing: Text(device.endpoint.endpointId),
                  subtitle: TextField(
                    onSubmitted: (text) => _sendMessage(device, text),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _requestConnection(DiscoveredEndpoint endpoint) async {
    await Nearby().requestConnection(
      widget.configId, // Replace with your device name
      endpoint.endpointId,
      onConnectionInitiated: (endpoint, connectionInfo) {
        // Handle connection initiation
      },
      onConnectionResult: (endpoint, status) {
        // Handle connection result
      },
      onDisconnected: (endpoint) {
        // Handle disconnection
      },
    );
  }

  Future<void> _sendMessage(ConnectedDevice device, String message) async {
    await Nearby().sendBytesPayload(device.endpoint.endpointId, Uint8List.fromList(message.codeUnits));
  }
}

class ConnectedDevice {
  final DiscoveredEndpoint endpoint;

  ConnectedDevice(this.endpoint);
}

class DiscoveredEndpoint{
  String endpointId,  endpointName,  serviceId;
  DiscoveredEndpoint(this.endpointId, this.endpointName, this.serviceId);
}

@rahulmaindargi
Copy link
Contributor Author

Same issue.

D/NearbyConnections(25325): NfcDispatcher created.
D/NearbyConnections(25325): NFC discovery started.
D/NearbyConnections(25325): Invalidating dispatch state.
D/NearbyConnections(25325): Starting NFC dispatching.
D/NearbyConnections(25325): Cannot dispatch NFC events. NFC is not supported.
D/nearby_connections(25325): startDiscovery
D/nearby_connections(25325): onEndpointFound
W/FlutterJNI(25325): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: nearby_connections. Response ID: 4

Even when I use code from example in github

class NearbyConnectionsScreen extends StatefulWidget {
  final String configId;
  const NearbyConnectionsScreen({super.key, required this.configId});

  @override
  State<NearbyConnectionsScreen> createState() => _NearbyConnectionsScreenState();
}

class _NearbyConnectionsScreenState extends State<NearbyConnectionsScreen> {
   String? userName;
  //Random().nextInt(10000).toString();
  final Strategy strategy = Strategy.P2P_POINT_TO_POINT;
  Map<String, ConnectionInfo> endpointMap = {};

  String? tempFileUri; //reference to the file currently being transferred
  Map<int, String> map = {}; //store filename mapped to corresponding payloadId

  @override
  void initState() {
    super.initState();
    Permissions.checkAndRequestPermissions();
    userName = widget.configId;
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
        title: const Text('Nearby Connections'),
    ),
    body:  Center(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListView(
          children: <Widget>[
            Text("User Name: $userName"),
            Wrap(
              children: <Widget>[
                ElevatedButton(
                  child: const Text("Start Advertising"),
                  onPressed: () async {
                    try {
                      bool a = await Nearby().startAdvertising(
                        userName!,
                        strategy,
                        serviceId: "abcde.abc_abcde",
                        onConnectionInitiated: onConnectionInit,
                        onConnectionResult: (id, status) {
                          showSnackbar(status);
                        },
                        onDisconnected: (id) {
                          showSnackbar(
                              "Disconnected: ${endpointMap[id]!.endpointName}, id $id");
                          setState(() {
                            endpointMap.remove(id);
                          });
                        },
                      );
                      showSnackbar("ADVERTISING: $a");
                    } catch (exception) {
                      showSnackbar(exception);
                    }
                  },
                ),
                ElevatedButton(
                  child: const Text("Stop Advertising"),
                  onPressed: () async {
                    await Nearby().stopAdvertising();
                  },
                ),
              ],
            ),
            Wrap(
              children: <Widget>[
                ElevatedButton(
                  child: const Text("Start Discovery"),
                  onPressed: () async {
                    try {
                      bool a = await Nearby().startDiscovery(
                        userName!,
                        strategy,
                        serviceId: "abcde.abc_abcde",
                        onEndpointFound: (id, name, serviceId) {
                          logger.d("onEndpointFound $id $name $serviceId");
                          // show sheet automatically to request connection
                          showModalBottomSheet(
                            context: context,
                            builder: (builder) {
                              return Center(
                                child: Column(
                                  children: <Widget>[
                                    Text("id: $id"),
                                    Text("Name: $name"),
                                    Text("ServiceId: $serviceId"),
                                    ElevatedButton(
                                      child: const Text("Request Connection"),
                                      onPressed: () {
                                        Navigator.pop(context);
                                        Nearby().requestConnection(
                                          userName!,
                                          id,
                                          onConnectionInitiated: (id, info) {
                                            onConnectionInit(id, info);
                                          },
                                          onConnectionResult: (id, status) {
                                            showSnackbar(status);
                                          },
                                          onDisconnected: (id) {
                                            setState(() {
                                              endpointMap.remove(id);
                                            });
                                            showSnackbar(
                                                "Disconnected from: ${endpointMap[id]!.endpointName}, id $id");
                                          },
                                        );
                                      },
                                    ),
                                  ],
                                ),
                              );
                            },
                          );
                        },
                        onEndpointLost: (id) {
                          showSnackbar(
                              "Lost discovered Endpoint: ${endpointMap[id]?.endpointName}, id $id");
                        },
                      );
                      logger.d("DISCOVERING: $a");
                      showSnackbar("DISCOVERING: $a");
                    } catch (e) {
                      showSnackbar(e);
                    }
                  },
                ),
                ElevatedButton(
                  child: const Text("Stop Discovery"),
                  onPressed: () async {
                    await Nearby().stopDiscovery();
                  },
                ),
              ],
            ),
            Text("Number of connected devices: ${endpointMap.length}"),
            ElevatedButton(
              child: const Text("Stop All Endpoints"),
              onPressed: () async {
                await Nearby().stopAllEndpoints();
                setState(() {
                  endpointMap.clear();
                });
              },
            ),
            const Divider(),
            const Text(
              "Sending Data",
            ),
            ElevatedButton(
              child: const Text("Send Random Bytes Payload"),
              onPressed: () async {
                endpointMap.forEach((key, value) {
                  String a = Random().nextInt(100).toString();

                  showSnackbar("Sending $a to ${value.endpointName}, id: $key");
                  Nearby()
                      .sendBytesPayload(key, Uint8List.fromList(a.codeUnits));
                });
              },
            ),
          ],
        ),
      ),
    ),
    );
  }

  void showSnackbar(dynamic a) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(a.toString()),
      ),
    );
  }

  Future<bool> moveFile(String uri, String fileName) async {
    String parentDir = (await getExternalStorageDirectory())!.absolute.path;
    final b =
    await Nearby().copyFileAndDeleteOriginal(uri, '$parentDir/$fileName');

    showSnackbar("Moved file:$b");
    return b;
  }

  /// Called upon Connection request (on both devices)
  /// Both need to accept connection to start sending/receiving
  void onConnectionInit(String id, ConnectionInfo info) {
    showModalBottomSheet(
      context: context,
      builder: (builder) {
        return Center(
          child: Column(
            children: <Widget>[
              Text("id: $id"),
              Text("Token: ${info.authenticationToken}"),
              Text("Name${info.endpointName}"),
              Text("Incoming: ${info.isIncomingConnection}"),
              ElevatedButton(
                child: const Text("Accept Connection"),
                onPressed: () {
                  Navigator.pop(context);
                  setState(() {
                    endpointMap[id] = info;
                  });
                  Nearby().acceptConnection(
                    id,
                    onPayLoadRecieved: (endid, payload) async {
                      if (payload.type == PayloadType.BYTES) {
                        String str = String.fromCharCodes(payload.bytes!);
                        showSnackbar("$endid: $str");

                        if (str.contains(':')) {
                          // used for file payload as file payload is mapped as
                          // payloadId:filename
                          int payloadId = int.parse(str.split(':')[0]);
                          String fileName = (str.split(':')[1]);

                          if (map.containsKey(payloadId)) {
                            if (tempFileUri != null) {
                              moveFile(tempFileUri!, fileName);
                            } else {
                              showSnackbar("File doesn't exist");
                            }
                          } else {
                            //add to map if not already
                            map[payloadId] = fileName;
                          }
                        }
                      } else if (payload.type == PayloadType.FILE) {
                        showSnackbar("$endid: File transfer started");
                        tempFileUri = payload.uri;
                      }
                    },
                    onPayloadTransferUpdate: (endid, payloadTransferUpdate) {
                      if (payloadTransferUpdate.status ==
                          PayloadStatus.IN_PROGRESS) {
                        print(payloadTransferUpdate.bytesTransferred);
                      } else if (payloadTransferUpdate.status ==
                          PayloadStatus.FAILURE) {
                        print("failed");
                        showSnackbar("$endid: FAILED to transfer file");
                      } else if (payloadTransferUpdate.status ==
                          PayloadStatus.SUCCESS) {
                        showSnackbar(
                            "$endid success, total bytes = ${payloadTransferUpdate.totalBytes}");

                        if (map.containsKey(payloadTransferUpdate.id)) {
                          //rename the file now
                          String name = map[payloadTransferUpdate.id]!;
                          moveFile(tempFileUri!, name);
                        } else {
                          //bytes not received till yet
                          map[payloadTransferUpdate.id] = "";
                        }
                      }
                    },
                  );
                },
              ),
              ElevatedButton(
                child: const Text("Reject Connection"),
                onPressed: () async {
                  Navigator.pop(context);
                  try {
                    await Nearby().rejectConnection(id);
                  } catch (e) {
                    showSnackbar(e);
                  }
                },
              ),
            ],
          ),
        );
      },
    );
  }
}

@mannprerak2
Copy link
Owner

Is the example app behaving in a similar way?

And is there any specific situation where this is happening?

@rahulmaindargi
Copy link
Contributor Author

rahulmaindargi commented Jan 5, 2025

I tried to run example from Github directly, but for some reason it was failing to compile.

Could not open cp_settings generic class cache for settings file 'C:\Workspace\nearby_connections\example\android\settings.gradle' (C:\Users\rahul\.gradle\caches\7.6.3\scripts\3oqyrh911jd2mvvdn2d1zplo2).
> BUG! exception in phase 'semantic analysis' in source unit '_BuildScript_' Unsupported class file major version 65

I used exact same code from example... just removed parts for permission checking buttons etc. as all required permission granted via Permissions.checkAndRequestPermissions(); in above code.

I click on start advertising on one device and start discovering on 2nd device.
on 2nd device which discovering I get the error

D/NearbyConnections(25325): NfcDispatcher created.
D/NearbyConnections(25325): NFC discovery started.
D/NearbyConnections(25325): Invalidating dispatch state.
D/NearbyConnections(25325): Starting NFC dispatching.
D/NearbyConnections(25325): Cannot dispatch NFC events. NFC is not supported.
D/nearby_connections(25325): startDiscovery
D/nearby_connections(25325): onEndpointFound
W/FlutterJNI(25325): Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: nearby_connections. Response ID: 4

My devices are
Samsung M14 with Android 14
Samsung S24 Ultra Android 14

Both devices are connected to Same WIFI network.

@rahulmaindargi
Copy link
Contributor Author

I do not have any experience in Flutter plugin development.

I tried to debug a little by adding some logs to local version.

onAttachedToEngine gets called when starting application where channel is created
but immediately after that onDetachedFromEngine also gets called.

onDetachedFromEngine doesn't have any code in it, but as its lifecycle method not sure if it getting called has any significant meaning for FlutterJNI was detached, Could not send. Channel: nearby_connections

Just thought to post if it helps to resolve the issue.

@mannprerak2
Copy link
Owner

mannprerak2 commented Jan 5, 2025

The issue here is - onDetachedFromEngine.

Once this method is called, there can no longer be any communication between java->dart. This is only untill onAttachedToEngine is called again as well.

What this means if the android activity goes in the background you wont get any updates.

P.S i am unable to do any testing as of now, I can try maybe next week.

@rahulmaindargi
Copy link
Contributor Author

I was going through below article to learn about plugin development.

https://blog.stackademic.com/integrating-native-code-with-flutter-8fecca6db524

as per my understanding of the article there are 3 diff types of channels.

  1. Method Channel: - to call from Dart to Native... and native can only return result. Native can not call dart code.
  2. Event Channel:- on events on Native channel (like device found) , Native can call Dart code
  3. Basic Message Channel:- TO send binary messages from Dart to native and vice a versa.

May be implementation needs to be changed to use either Basic Message Channel or combination of Method Channel & Event Channel.

If its not related or incorrect feel free to neglect.

@mannprerak2
Copy link
Owner

Event Channel is used for streaming data like audio or video feed from device.

Anyways, changing the channel wouldn't help.

@rahulmaindargi
Copy link
Contributor Author

rahulmaindargi commented Jan 7, 2025

I tried on my local branch by combination of Method Channel & Event Channel.
without much code change to logic.. and it appears to be working at least for me...

at least I was able to find the devices. request connection and accept the connection.
will continue further testing tomorrow or by weekend.
I created Pull request for you to see,
#79

@mannprerak2
Copy link
Owner

Hi @rahulmaindargi, I've released a new version with your fix - nearby_connections: ^4.2.0.

Thank you for contributing 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants