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

Zero Documentation #33

Open
J4sonsNowww123 opened this issue Feb 3, 2022 · 2 comments
Open

Zero Documentation #33

J4sonsNowww123 opened this issue Feb 3, 2022 · 2 comments

Comments

@J4sonsNowww123
Copy link

J4sonsNowww123 commented Feb 3, 2022

The Documentation to this Library is extremely lacking and it's very hard to understand what the example does.
Could you please go through your example and add with // comments, what each code block exactly does?
It would make it a lot easier to understand and implement at other places.
https://pub.dev/packages/flutter_nearby_connections/example

This fix would take roughly 10 minutes, as you are the one who already understands the code and know what each line does.
Eventually, if I have enough time and energy to look at it, I may be able to make a some sort of documentation.
This is my impression of the code:

At the beginning the devices decide, whether they are a browser (searcher)
or an advertiser.
the browser will find other advertisers on the list,
which the advertiser only waits until he gets connected.
Then both can send messages to each other, once the connection has been established.

Initializing & Settings
Here are all Settings declared, which are meant required for a
successfull Peer-to-Peer connection.

For Example we choose here the strategy, which is P2P_Cluster.
Once this action nearbyService is called,
it checks if the devicetype is browser.
Presumably this doesn't actually check IF the Device this program is used
on IS a Browser, but much rather if the device is currently
advertising or browsing / looking for other devices.

Either way it then stops searching for peers (other devices),
waits 200 microseconds, which is aproximately 0.002 seconds, until it starts browsing for peers again.

If the device turns out to be a "advertiser"
it stops advertising peers and stops browsing,
then waits 0.002 seconds until it starts
advertising peers and starts browsing.

await nearbyService.init(
        serviceType: 'mpconn',
        deviceName: devInfo,
        strategy: Strategy.P2P_CLUSTER,
        callback: (isRunning) async {
          if (isRunning) {
            if (widget.deviceType == DeviceType.browser) {
              await nearbyService.stopBrowsingForPeers();
              await Future.delayed(Duration(microseconds: 200));
              await nearbyService.startBrowsingForPeers();
            } else {
              await nearbyService.stopAdvertisingPeer();
              await nearbyService.stopBrowsingForPeers();
              await Future.delayed(Duration(microseconds: 200));
              await nearbyService.startAdvertisingPeer();
              await nearbyService.startBrowsingForPeers();
            }
          }
        });

Declarations

enum DeviceType { advertiser, browser }

class DevicesListScreen extends StatefulWidget {
  const DevicesListScreen({required this.deviceType});
final DeviceType deviceType;

class _DevicesListScreenState extends State<DevicesListScreen> {
  List<Device> devices = [];
  List<Device> connectedDevices = [];
  late NearbyService nearbyService;
  late StreamSubscription subscription;
  late StreamSubscription receivedDataSubscription;

Check for Android or IOS

nearbyService = NearbyService();
    String devInfo = '';
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    
    //If this is android then this will be executed
    if (Platform.isAndroid) {
      AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
      devInfo = androidInfo.model!;
    }

    //If this is run on IOS, then this will be executed
    if (Platform.isIOS) {
      IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
      devInfo = iosInfo.localizedModel!;
    }

Start & End Connection

_onButtonClicked(Device device) {
    switch (device.state) {
      case SessionState.notConnected:
        nearbyService.invitePeer(
          deviceID: device.deviceId,
          deviceName: device.deviceName,
        );
        break;
        //If connected make a disconnection
      case SessionState.connected:
        nearbyService.disconnectPeer(deviceID: device.deviceId);
        break;
        //If it's during a connection ignore it
      case SessionState.connecting:
        break;
    }
  }

Subscription
If the subscriptionstate, aka the status of the connection between each other has changed, it wil print all elements of devices it has found and their state. (Basically if they're already connected or not.)

If this is run on an android device and the device is currently connected,
the searching for peers stops. Else if it's not connected the searching for peers starts.

subscription =
        nearbyService.stateChangedSubscription(callback: (devicesList) {
      devicesList.forEach((element) {
        print(
            " deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}");

        if (Platform.isAndroid) {
          if (element.state == SessionState.connected) {
            nearbyService.stopBrowsingForPeers();
          } else {
            nearbyService.startBrowsingForPeers();
          }
        }
      });

      setState(() {
        devices.clear();
        devices.addAll(devicesList);
        connectedDevices.clear();
        connectedDevices.addAll(devicesList
            .where((d) => d.state == SessionState.connected)
            .toList());
      });
    });

Send Data

nearbyService.sendMessage(
device.deviceId, myController.text);
myController.text = '';

Received Data

receivedDataSubscription =
        nearbyService.dataReceivedSubscription(callback: (data) {
      print("dataReceivedSubscription: ${jsonEncode(data)}");
      showToast(jsonEncode(data),
          context: context,
          axis: Axis.horizontal,
          alignment: Alignment.center,
          position: StyledToastPosition.bottom);
    });

End search

void dispose() {
  subscription.cancel();
  receivedDataSubscription.cancel();
  nearbyService.stopBrowsingForPeers();
  nearbyService.stopAdvertisingPeer();
  super.dispose();
}

But it's still not clear what exactly.
Your code works great, but it's a mystery how it works.
Thanks.

@J4sonsNowww123
Copy link
Author

J4sonsNowww123 commented Feb 3, 2022

Here I made an order.
pick what you are,
connect to device
send a message by clicking on it
press disconnect.
then go through the steps I made.

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
//import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:flutter/material.dart';
import 'package:flutter_nearby_connections/flutter_nearby_connections.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:logger/logger.dart';

void main() {
  runApp(MyApp());
}

var logger = Logger(
  printer: PrettyPrinter(),
);
Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case '/':
      return MaterialPageRoute(builder: (_) => Home());
    case 'browser':
      return MaterialPageRoute(
          builder: (_) => DevicesListScreen(deviceType: DeviceType.browser));
    case 'advertiser':
      return MaterialPageRoute(
          builder: (_) => DevicesListScreen(deviceType: DeviceType.advertiser));
    default:
      return MaterialPageRoute(
          builder: (_) => Scaffold(
                body: Center(
                    child: Text('No route defined for ${settings.name}')),
              ));
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      onGenerateRoute: generateRoute,
      initialRoute: '/',
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          TextButton(
            child: Text("Log"),
            onPressed: () {
              logger.d('TEST LOGGING!');
            },
          ),
          Expanded(
            child: InkWell(
              onTap: () {
                Navigator.pushNamed(context, 'browser');
                //DevicesListScreen(deviceType: DeviceType.browser));
                //DeviceListScreen gets the deviceType DeviceType.browser fed
                // Through this the DeviceListScreen knows what to do next, if it's a browser.
              },
              child: Container(
                color: Colors.red,
                child: Center(
                    child: Text(
                  'BROWSER',
                  style: TextStyle(color: Colors.white, fontSize: 40),
                )),
              ),
            ),
          ),
          Expanded(
            child: InkWell(
              onTap: () {
                Navigator.pushNamed(context, 'advertiser');
                // Opens
              },
              child: Container(
                color: Colors.green,
                child: Center(
                    child: Text(
                  'ADVERTISER',
                  style: TextStyle(color: Colors.white, fontSize: 40),
                )),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

enum DeviceType { advertiser, browser }

class DevicesListScreen extends StatefulWidget {
  //Devices like DeviceType.browser and DeviceType.advertiser are given here
  //and made into objects.
  const DevicesListScreen({required this.deviceType});

  final DeviceType deviceType;

  @override
  _DevicesListScreenState createState() => _DevicesListScreenState();
}

class _DevicesListScreenState extends State<DevicesListScreen> {
  //A List is made of all the devices which are found
  List<Device> devices = [];
  List<Device> connectedDevices = [];
  //A List is made of all the devices which are connected.
  late NearbyService nearbyService;
  late StreamSubscription subscription;
  late StreamSubscription receivedDataSubscription;
  //Some variables are declared, which later will be defined
  bool isInit = false;

  //When the Widget is created, initState is initialized as an instance of this widget.

  //BROWSER STEP: 1

  /*
  * ADVERTISER STEP: 1
  */

  //STARTUP INITIALIZING
  @override
  void initState() {
    print("Line 126");
    super.initState();
    init();
  }

  //Shutdown the the search
  @override
  void dispose() {
    print("Line 134");
    subscription.cancel();
    receivedDataSubscription.cancel();
    nearbyService.stopBrowsingForPeers();
    nearbyService.stopAdvertisingPeer();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.deviceType.toString().substring(11).toUpperCase()),
        ),
        backgroundColor: Colors.white,
        body: ListView.builder(
            itemCount: getItemCount(),
            itemBuilder: (context, index) {
              final device = widget.deviceType == DeviceType.advertiser
                  ? connectedDevices[index]
                  : devices[index];
              return Container(
                margin: EdgeInsets.all(8.0),
                child: Column(
                  children: [
                    Row(
                      children: [
                        Expanded(
                            child: GestureDetector(
                          onTap: () => _onTabItemListener(device),
                          child: Column(
                            children: [
                              Text(device.deviceName),
                              Text(
                                getStateName(device.state),
                                style: TextStyle(
                                    color: getStateColor(device.state)),
                              ),
                            ],
                            crossAxisAlignment: CrossAxisAlignment.start,
                          ),
                        )),
                        // Request connect
                        GestureDetector(
                          onTap: () => _onButtonClicked(device),
                          child: Container(
                            margin: EdgeInsets.symmetric(horizontal: 8.0),
                            padding: EdgeInsets.all(8.0),
                            height: 35,
                            width: 100,
                            color: getButtonColor(device.state),
                            child: Center(
                              child: Text(
                                getButtonStateName(device.state),
                                style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                          ),
                        )
                      ],
                    ),
                    SizedBox(
                      height: 8.0,
                    ),
                    Divider(
                      height: 1,
                      color: Colors.grey,
                    )
                  ],
                ),
              );
            }));
  }

  //ALSO GIVES THE STATE IF SOMETHING HAS BEEN CONNECTED, IS WAITING OR DISCONNECTED.
  String getStateName(SessionState state) {
    //BROWSER STEP: 6

    /*
    * ADVERTISER STEP: 6
    */
    print("Line 212");
    switch (state) {
      case SessionState.notConnected:
        return "disconnected";
      case SessionState.connecting:
        return "waiting";
      default:
        return "connected";
    }
  }

  //Says if the button connect or disconnect was pressed.
  //Maybe it changes also the text of the button, because of the setState

  //CHANGES THE STATE OF THE BUTTON CONNECTED OR DISCONNECTED, LIKE A SWITCH
  String getButtonStateName(SessionState state) {
    //BROWSER STEP: 9

    /*
    * ADVERTISER STEP: 9
    */
    print("Line 226");
    switch (state) {
      case SessionState.notConnected:
      case SessionState.connecting:
        return "Connect";
      default:
        return "Disconnect";
    }
  }

  //CHANGES THE STATE OF THE BUTTON COLOURS NOT CONNECTED, CONNECTING AND DISCONNECTED
  Color getStateColor(SessionState state) {
    //BROWSER STEP: 7

    /*
    * ADVERTISER STEP: 7
    */

    print("Line 238");
    switch (state) {
      case SessionState.notConnected:
        return Colors.black;
      case SessionState.connecting:
        return Colors.grey;
      default:
        return Colors.green;
    }
  }

  //CHANGES THE COLOURS OF THE BUTTON FROM RED TO GREEN, LIKE A SWITCH
  Color getButtonColor(SessionState state) {
    //BROWSER STEP: 8

    /*
    * ADVERTISER STEP: 8
    */
    print("Line 252");
    switch (state) {
      case SessionState.notConnected:
      case SessionState.connecting:
        return Colors.green;
      default:
        return Colors.red;
    }
  }

  //DEVICE SELECTION HANDLER
  _onTabItemListener(Device device) {
    //BROWSER STEP: 11 (The device was clicked on, to send a message.)

    /*
    * ADVERTISER STEP: 11 (The device was clicked on, to send a message.)
    */

    //HANDLES THE SENDING OF THE MESSAGE IF THE DEVICE IS CONNECTED
    print("Line 263");
    if (device.state == SessionState.connected) {
      //BROWSER STEP: 12
      /*
      * ADVERTISER STEP: 12
      */
      print("Line 265");
      showDialog(
          context: context,
          builder: (BuildContext context) {
            final myController = TextEditingController();
            return AlertDialog(
              title: Text("Send message"),
              content: TextField(controller: myController),
              actions: [
                TextButton(
                  child: Text("Cancel"),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
                TextButton(
                  child: Text("Send"),
                  onPressed: () {
                    //BROWSER STEP: 13
                    /*
                    * ADVERTISER STEP: 13
                    */

                    //CALLING A HANDLER TO ACTUALLY SEND THE MESSAGE
                    nearbyService.sendMessage(
                        device.deviceId, myController.text);
                    myController.text = '';
                  },
                ),
              ],
            );
          });
    }
  }

  //CONNECT AND DISCONNECT
  int getItemCount() {
    //BROWSER STEP: 3
    //BROWSER STEP: 5
    //BROWSER STEP: 15 (Connection finished.)
    /*
    * ADVERTISER STEP: 3
    * ADVERTISER STEP: 5
    * ADVERTISER STEP: 15 (Connection finished.)
    */
    print("Line 295");
    if (widget.deviceType == DeviceType.advertiser) {
      return connectedDevices.length;
    } else {
      return devices.length;
    }
  }

  //HANDLING THE BUTTON CLICK OF CONNECT AND DISCONNECT
  _onButtonClicked(Device device) {
    //BROWSER STEP: 10 (The Advertiser has now been connected to the browser. The whole process
    //of button pressing has been documented.)
    print("Line 304");
    switch (device.state) {
      case SessionState.notConnected:
        nearbyService.invitePeer(
          deviceID: device.deviceId,
          deviceName: device.deviceName,
        );
        break;
      case SessionState.connected:
        nearbyService.disconnectPeer(deviceID: device.deviceId);
        break;
      case SessionState.connecting:
        break;
    }
  }

  //INIT IS CALLED AND THE DEVICE MODEL IS SET. (ANDROID IOS)
  void init() async {
    //BROWSER STEP: 2

    /*
    * ADVERTISER STEP: 2
    */
    print("Line 321");
    nearbyService = NearbyService();
    String devInfo = '';
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    if (Platform.isAndroid) {
      AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
      devInfo = androidInfo.model!;
    }
    if (Platform.isIOS) {
      IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
      devInfo = iosInfo.localizedModel!;
    }
    await nearbyService.init(
        serviceType: 'mpconn',
        deviceName: devInfo,
        strategy: Strategy.P2P_CLUSTER,
        callback: (isRunning) async {
          if (isRunning) {
            if (widget.deviceType == DeviceType.browser) {
              await nearbyService.stopBrowsingForPeers();
              await Future.delayed(Duration(microseconds: 200));
              await nearbyService.startBrowsingForPeers();
            } else {
              await nearbyService.stopAdvertisingPeer();
              await nearbyService.stopBrowsingForPeers();
              await Future.delayed(Duration(microseconds: 200));
              await nearbyService.startAdvertisingPeer();
              await nearbyService.startBrowsingForPeers();
            }
          }
        });
    subscription =
        nearbyService.stateChangedSubscription(callback: (devicesList) {
      devicesList.forEach((element) {
        print(
            " deviceId: ${element.deviceId} | deviceName: ${element.deviceName} | state: ${element.state}");

        if (Platform.isAndroid) {
          if (element.state == SessionState.connected) {
            nearbyService.stopBrowsingForPeers();
          } else {
            nearbyService.startBrowsingForPeers();
          }
        }
      });

      //CHECKS FOR DEVICES AND SHOWS YOU THEIR STATE
      setState(() {
        //BROWSER STEP: 4 (I wait for another device and search for it.)
        //BROWSER STEP: 14 (The Connection is ended.)

        /*
        * ADVERTISER STEP: 4 (The Browser connects to this Advertiser after being found)
        * ADVERTISER STEP: 14 (The Connection is ended.)
        */
        print("Line 368");
        devices.clear();
        devices.addAll(devicesList);
        connectedDevices.clear();
        connectedDevices.addAll(devicesList
            .where((d) => d.state == SessionState.connected)
            .toList());
      });
    });

    //Message received
    receivedDataSubscription =
        nearbyService.dataReceivedSubscription(callback: (data) {
      print("TheMessage: ${jsonEncode(data)}");
      showToast(jsonEncode(data),
          context: context,
          axis: Axis.horizontal,
          alignment: Alignment.center,
          position: StyledToastPosition.bottom);
    });
  }
}

Even with titles to all the important functions and what they do.

@MozyOk
Copy link

MozyOk commented Feb 22, 2022

Thanks, @J4sonsNowww123, very helpful for me this info!

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