flutter_nearby_http 0.0.3 copy "flutter_nearby_http: ^0.0.3" to clipboard
flutter_nearby_http: ^0.0.3 copied to clipboard

A package for communication in intermittent connectivity scenarios using a reputation-based mechanism.

example/lib/main.dart

import 'dart:typed_data';
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'package:ReCoopsys_Example/widgets.dart';
import 'package:flutter_nearby_connections_plus/flutter_nearby_connections_plus.dart';
import 'package:flutter_nearby_http/services/image_service.dart';
import 'package:flutter_nearby_http/utils/permissions_manager.dart';
import 'package:path_provider/path_provider.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_nearby_http/models/node_reputation.dart';
import 'package:flutter_nearby_http/models/packet_metrics.dart';
import 'package:flutter_nearby_http/services/nearby_http_service.dart';
import 'package:flutter_nearby_http/services/nearby_service_instance.dart';
import 'package:flutter_nearby_http/services/nearby_service_manager.dart';
import 'package:flutter_nearby_http/utils/device_status_utils.dart';


void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MaterialApp(
    home: DevicesListScreen(),
  ));
}

enum DeviceType { advertiser, browser }

class DevicesListScreen extends StatefulWidget {
  const DevicesListScreen();

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

class _DevicesListScreenState extends State<DevicesListScreen> {
  List<Device> devices = [];
  List<Device> connectedDevices = [];
  late NearbyService nearbyService;
  late StreamSubscription subscription;
  late StreamSubscription receivedDataSubscription;
  List<Uint8List> receivedImages = [];
  DeviceRole? _role;
  bool _canCooperate = true;
  bool _isInitialized = false;
  final List<Device> _discoveredDevices = [];
  List<Device> get discoveredDevices => List.unmodifiable(_discoveredDevices);
  final NearbyServiceManager repManager = NearbyServiceManager();
  late Future<NodeReputation> currentReputation;
  Future<List<NodeReputation>>? history;
  late Future<NodeReputation> lastHistoryRecord;

  late String filePath;


  NearbyService get nearbyServiceGet => nearbyService;

  Map<String, List<int>> delays = HashMap();
  Map<String, int> sentPackets = HashMap();
  Map<String, int> failedPackets = HashMap();
  Map<String, int> reputationScores = HashMap();
  List<PacketMetrics> packetMetricsTable = [];
  bool isInit = false;

  Future<void> _checkStatus() async {
    final nearbyHttpService = repManager.getService();
    await DeviceStatusUtils.checkDeviceStatus(context, nearbyHttpService);
  }


  Future<List<NodeReputation>> loadHistory() async {
    final historyRaw = await repManager.readHistory();
    return historyRaw.map((map) => NodeReputation.fromMap(map)).toList();
  }

  String cooperate = "";

  Future<NodeReputation> loadLastRecord() async {
    final historyRaw = await repManager.readHistory();
    DateTime date = DateTime.now();
    if (historyRaw.isEmpty) return NodeReputation(deviceId: "0", battery: 0, storage: 0, successfulInteractions: 0, totalInteractions: 0, requestedPackets: 0, deliveredPackets: 0, avgLatency: 0, diversity: 0, daysSinceLastInteraction: 0, weights: [], timestamp: date.toString());
    final lastMap = historyRaw.last;
    cooperate = NodeReputation.cooperationStatusString(NodeReputation.fromMap(lastMap));
    return NodeReputation.fromMap(lastMap);
  }

  Future<void> _determineRole() async {
    try {
      final hasInternet = await PermissionsManager.checkInternetConnection();
      setState(() {
        _role = hasInternet ? DeviceRole.advertiser : DeviceRole.browser;
      });
    } catch (e) {
      print('Error: $e');
      setState(() {
        _role = DeviceRole.browser; // Fallback
      });
    }
  }


  Future<void> _initializeApp() async {
    try {
      await _determineRole();
      await PermissionsManager.requestBluetoothPermissions();
      await PermissionsManager.requestLocationPermissions();
      await init();
    } catch (e) {
      print('Error: $e');
    }
  }


  @override
  void initState() {
    super.initState();
    currentReputation = repManager.calculateCurrentReputation();
    history = loadHistory();
    lastHistoryRecord = loadLastRecord();
    _checkStatus();
    _initializeApp();
  }

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

  @override
  Widget build(BuildContext context) {
    if (_role == null) {
      return const Scaffold(
        body: Center(child: CircularProgressIndicator()),
      );
    }
    return Scaffold(
        appBar: AppBar(
          title:
          Text(_role.toString().substring(11).toUpperCase()),
        ),
        backgroundColor: Colors.white,
        body: ListView.builder(
            itemCount: getItemCount(),
            itemBuilder: (context, index) {
              final device = _role == DeviceType.advertiser
                  ? connectedDevices[index]
                  : devices[index];
              return Container(
                margin: EdgeInsets.all(8.0),
                child: Column(
                  children: [
                    Row(
                      children: [
                        Expanded(
                            child: Column(
                              children: [
                                Text(device.deviceName),
                                Text(
                                  getStateName(device.state),
                                  style: TextStyle(
                                      color: getStateColor(device.state)),
                                ),
                                _role == DeviceRole.advertiser ? Text('this device') : Container(),
                              ],
                              crossAxisAlignment: CrossAxisAlignment.start,
                            )),
                        _role == DeviceRole.browser ? GestureDetector(
                          onTap: () {
                            showDialog(
                              context: context,
                              builder: (context) => AlertDialog(
                                title: Text("Reputation History"),
                                content: SizedBox(
                                  width:
                                  MediaQuery.of(context).size.width * 0.8,
                                  child: HistoryTable(history: history),
                                ),
                                actions: [
                                  TextButton(
                                    onPressed: () async {
                                      final dir = await getApplicationDocumentsDirectory();
                                      final nearbyService = NearbyHttpService();

                                      final devId = nearbyService.devInfo;

                                      filePath = '${dir.path}/reputation_$devId.txt';
                                      NearbyHttpService().resetPacketCounters();
                                      history = Future.value(<NodeReputation>[]);


                                      final file = File(filePath);
                                      if (await file.exists()) {
                                        await file.writeAsString('');  // Esvazia o ficheiro
                                        print("Reset History to $devId.");
                                      }
                                      Navigator.of(context).pop();
                                    },
                                    child: Text('Reset'),
                                  ),
                                  TextButton(
                                    child: Text("Exit"),
                                    onPressed: () =>
                                        Navigator.of(context).pop(),
                                  ),
                                ],
                              ),
                            );
                          },
                          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(
                                'Cooperation',
                                style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                          ),
                        ) :  GestureDetector(
                          onTap: () async {
                            final lastRecord = await loadLastRecord();
                            ReputationDialogs.showLastRecordPopup(context, cooperate, lastRecord);
                          },
                          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(
                                'Cooperation',
                                style: TextStyle(
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold),
                              ),
                            ),
                          ),
                        ),
                        // 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,
                    ),
                    _role == DeviceRole.browser ? GestureDetector(
                      onTap: () => _onTabItemListener(device),
                      child: Container(
                        margin: EdgeInsets.symmetric(horizontal: 8.0),
                        padding: EdgeInsets.all(8.0),
                        height: 35,
                        width: 300,
                        color: getButtonColor(device.state),
                        child: Center(
                          child: Text(
                            "AskForPicture (HTTPS | JPG)",
                            style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold),
                          ),
                        ),
                      ),
                    ) : Container(),
                    SizedBox(
                      height: 8.0,
                    ),
                    Divider(
                      height: 1,
                      color: Colors.grey,
                    )
                  ],
                ),
              );
            }));
  }

  String getStateName(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
        return "disconnected";
      case SessionState.connecting:
        return "waiting";
      default:
        return "connected";
    }
  }

  String getButtonStateName(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
      case SessionState.connecting:
        return "Connect";
      default:
        return "Disconnect";
    }
  }

  Color getStateColor(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
        return Colors.black;
      case SessionState.connecting:
        return Colors.grey;
      default:
        return Colors.green;
    }
  }

  Color getButtonColor(SessionState state) {
    switch (state) {
      case SessionState.notConnected:
      case SessionState.connecting:
        return Colors.red;
      default:
        return Colors.green;
    }
  }

  void sendImageBackToDevice(String deviceId, String imageUrl) async {
    final startTime = DateTime.now();
    startTimes.putIfAbsent(deviceId, () => []).add(startTime);

    try {
      Uint8List imageBytes = await ImageService.downloadImage(imageUrl);
      String base64Image = base64Encode(await ImageService.resizeImage(imageBytes));
      Device foundDevice =
      connectedDevices.firstWhere((device) => device.deviceId == deviceId);

      NearbyHttpService()
          .sentPackets
          .update(deviceId, (v) => v + 1, ifAbsent: () => 1);

      nearbyService.sendMessage(
        foundDevice.deviceId,
        base64Image,
      );
      _role == DeviceRole.advertiser
          ? Container()
          : showDialog(
        context: context,
        builder: (context) => AlertDialog(
          title: Text("Reputation History"),
          content: SizedBox(
            width: MediaQuery.of(context).size.width * 0.8,
            child: HistoryTable(history: history),
          ),
          actions: [
            TextButton(
              child: Text("Exit"),
              onPressed: () => Navigator.of(context).pop(),
            ),
          ],
        ),
      );
      ReputationDialogs.showImageDialog(context, imageBytes);
      await repManager.init();
      await repManager.calculateCurrentReputation();
      setState(() {
        history = loadHistory();
      });
    } catch (e) {
      NearbyHttpService()
          .failedPackets
          .update(deviceId, (v) => v + 1, ifAbsent: () => 1);
      packetMetricsTable.add(PacketMetrics(
        deviceId: deviceId,
        packetSent: true,
        packetDiscarded: true,
        delay: 0,
        dateTime: startTime,
        packetSize: 0,
      ));
    }

    packetMetricsTable.forEach((m) {
      m.dateTime.add(Duration(milliseconds: m.delay));
    });
  }

  _onTabItemListener(Device device) {
    if (device.state == SessionState.connected) {
      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: () {
                    final startTime = DateTime.now();
                    startTimes
                        .putIfAbsent(device.deviceId, () => [])
                        .add(startTime);
                    nearbyService.sendMessage(
                        device.deviceId, myController.text);
                    myController.text = '';
                  },
                )
              ],
            );
          });
    }
  }



  _onButtonClicked(Device device) {
    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;
    }
  }

  Future<void> inviteClosestPeer() async {
    if (_discoveredDevices.isEmpty) return;
    final closest = _discoveredDevices.first;
    await nearbyService.invitePeer(
      deviceID: closest.deviceId,
      deviceName: closest.deviceName,
    );
  }

  Future<void> _handleDeviceRole() async {
    if (_role == DeviceRole.advertiser && _canCooperate) {
      print("The device is an advertiser.");
      await nearbyService.stopAdvertisingPeer();
      await nearbyService.stopBrowsingForPeers();
      await Future.delayed(Duration(microseconds: 200));
      await nearbyService.startAdvertisingPeer();
      // await nearbyService.startBrowsingForPeers();
    } else if (_role == DeviceRole.browser) {
      print("The device is a browser.");
      await nearbyService.stopBrowsingForPeers();
      await Future.delayed(Duration(microseconds: 200));
      await nearbyService.startBrowsingForPeers();
    } else {
      await nearbyService.stopBrowsingForPeers();
      await nearbyService.stopAdvertisingPeer();
    }
  }

  int getItemCount() {
    if (_role == DeviceType.advertiser) {
      return connectedDevices.length;
    } else {
      return devices.length;
    }
  }

  Map<String, List<DateTime>> startTimes = {};

  Future<void> init() async {
    if (_isInitialized) return;
    nearbyService = NearbyServiceInstance().service;
    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: 'mp-connection',
        deviceName: devInfo,
        strategy: Strategy.P2P_CLUSTER,
        callback: (isRunning) async {
          if (isRunning) {
            await _handleDeviceRole();
            _isInitialized = true;
          } else {
            print("Error starting the Nearby service...");
          }
        });
    subscription =
        nearbyService.stateChangedSubscription(callback: (devicesList) {
          devicesList.forEach((element) {
            _discoveredDevices.add(element);

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

          devices.forEach((device) {
            NearbyHttpService()
                .reputationScores
                .putIfAbsent(device.deviceId, () => 0);
            NearbyHttpService().sentPackets.putIfAbsent(device.deviceId, () => 0);
            NearbyHttpService().failedPackets.putIfAbsent(device.deviceId, () => 0);
          });

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

    receivedDataSubscription =
        nearbyService.dataReceivedSubscription(callback: (data) async {
          Map<String, dynamic> jsonMap = jsonDecode(jsonEncode(data));

          String deviceId = jsonMap['deviceId'];
          DateTime endTime = DateTime.now();

          int delay = 0;
          DateTime startTime = endTime;

          if (startTimes.containsKey(deviceId) &&
              startTimes[deviceId]!.isNotEmpty) {
            startTime = startTimes[deviceId]!.removeAt(0);
            delay = endTime.difference(startTime).inMilliseconds;
            delays.putIfAbsent(deviceId, () => []).add(delay);
          }

          int packetSize = 0;
          String message = jsonMap['message'];
          if (!message.startsWith('http')) {
            try {
              packetSize = base64Decode(message).length;
            } catch (_) {
              packetSize = 0;
            }
          }

          packetMetricsTable.add(PacketMetrics(
            deviceId: deviceId,
            packetSent: true,
            packetDiscarded: false,
            delay: delay,
            dateTime: startTime,
            packetSize: packetSize,
          ));

          if (message.startsWith('http://') || message.startsWith('https://')) {
            ImageService.downloadImage(message).then((_) {
              sendImageBackToDevice(deviceId, message);
            });
          } else {
            Uint8List imageBytes = base64Decode(message);

            NearbyHttpService()
                .sentPackets
                .update(deviceId, (v) => v + 1, ifAbsent: () => 1);

            await repManager.init();
            repManager.updateMetrics(
              sent: NearbyHttpService().sentPackets,
              failed: NearbyHttpService().failedPackets,
              delays: delays,
              metrics: NearbyHttpService().packetMetricsTable,
            );
            await repManager.calculateCurrentReputation();
            setState(() {
              history = loadHistory(); // força rebuild da tabela
            });
            ReputationDialogs.showImageDialog(context, imageBytes);
            showDialog(
              context: context,
              builder: (context) => AlertDialog(
                title: Text("Reputation History"),
                content: SizedBox(
                  width:
                  MediaQuery.of(context).size.width * 0.8,
                  child: HistoryTable(history: history),
                ),
                actions: [
                  TextButton(
                    onPressed: () async {
                      final dir = await getApplicationDocumentsDirectory();
                      final nearbyService = NearbyHttpService();

                      final devId = nearbyService.devInfo;

                      filePath = '${dir.path}/reputation_$devId.txt';
                      NearbyHttpService().resetPacketCounters();
                      history = Future.value(<NodeReputation>[]);


                      final file = File(filePath);
                      if (await file.exists()) {
                        await file.writeAsString('');  // Esvazia o ficheiro
                        print("Reset History to $devId.");
                      }
                      Navigator.of(context).pop();
                    },
                    child: Text('Reset'),
                  ),
                  TextButton(
                    child: Text("Exit"),
                    onPressed: () =>
                        Navigator.of(context).pop(),
                  ),
                ],
              ),
            );
          }
        });
    _role = repManager.getService().role!;
  }
}
3
likes
135
points
36
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A package for communication in intermittent connectivity scenarios using a reputation-based mechanism.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

battery_plus, connectivity_plus, device_info_plus, flutter, flutter_image_compress, flutter_nearby_connections_plus, http, image, path_provider, permission_handler, provider

More

Packages that depend on flutter_nearby_http