From 7049c39cf017904615fc5e971ebeabc5a6d27e34 Mon Sep 17 00:00:00 2001 From: Kseniia Nikitina Date: Sun, 18 Aug 2024 15:42:53 +0200 Subject: [PATCH] BREAKING CHANGE: Update API for connection, communication channel state and is browser value (#14) * refactor(lib, example, example_full): use streams for communication channel and is ios browser * feat: add abstract toJson() to message * refactor: add deprecations for nearby_service * feat(example): update main.dart * feat(nearby_service): add comments * feat(nearby_service_platform_interface): add deprecation to getConnectedDeviceStream * feat(nearby_service/message): add concrete implementation for toJson() * feat(nearby_service/android): add deprecations * fix(nearby_service/android): reset check for android device * fix(nearby_service/ios): change ios service to deprecations variant * chore(nearby_service): edit deprecation messages * chore: update gitignore * doc: add CONTRIBUTING file * doc: update CONTRIBUTING file * chore: update CONTRIBUTING.md * chore: update CONTRIBUTING.md * chore: version 0.1.0 * fix(example_full): move startListeningCommunicationChannelState() upper --- .gitignore | 3 +- CHANGELOG.md | 14 ++ CONTRIBUTING.md | 61 +++++++++ example/lib/main.dart | 24 ++-- example_full/lib/domain/app_service.dart | 38 ++++-- lib/nearby_service.dart | 92 ++++++++++++-- lib/nearby_service_method_channel.dart | 10 +- lib/nearby_service_platform_interface.dart | 10 ++ lib/src/base/nearby_message_base.dart | 7 + lib/src/model/nearby_message.dart | 14 +- .../android/nearby_android_service.dart | 40 +++++- .../socket_service/nearby_socket_service.dart | 26 ++-- lib/src/platforms/ios/nearby_ios_service.dart | 120 +++++++++++++----- pubspec.yaml | 2 +- test/nearby_service_test.dart | 6 + 15 files changed, 388 insertions(+), 79 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.gitignore b/.gitignore index a7e8cc0..facb2a6 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ build/ **/Podfile.lock # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ +playground/* \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f8bb08..557efef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## 0.1.0 + +**!! BREAKING CHANGES !!** + +- Method `connect()` is **deprecated**. Added `connectById()` method instead +- Method `disconnect()` is **deprecated**. Added `disconnectById()` method instead +- Getter `communicationChannelState` is **deprecated**. Added `getCommunicationChannelStateStream()` method + and `communicationChannelStateValue` getter instead +- Getter `isBrowser` is **deprecated**. Added `getIsBrowserStream()` method and `isBrowserValue` getter instead +- Added `toJson()` method to `NearbyMessage` class and its subclasses + +More information about the deprecated API here: https://github.com/ksenia312/nearby_service/pull/14. +In the next versions, the deprecated API will be removed. + ## 0.0.9 - Add initialization checks for Android and IOS diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..24f732a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +[![Xenikii Website](https://img.shields.io/badge/-xenikii.one-313866?style=for-the-badge&logoColor=white)](https://xenikii.one) +[![LICENSE BSD](https://img.shields.io/badge/License-BSD-504099?style=for-the-badge)](https://github.com/ksenia312/nearby_service/blob/main/LICENSE) +[![Pub package](https://img.shields.io/pub/v/nearby_service.svg?style=for-the-badge&color=974EC3)](https://pub.dev/packages/nearby_service) +[![Pub Likes](https://img.shields.io/pub/likes/nearby_service?style=for-the-badge&color=FE7BE5)](https://pub.dev/packages/nearby_service) + +## Contributing to Nearby Service + +Thank you for considering contributing to the Nearby Service package! Please follow the guidelines below based on your +use case. + +#### Reporting a bug + +If you have found a bug, please follow these steps: + +1. **Search for existing issues**: Before opening a new issue, please check + the [Issues](https://github.com/ksenia312/nearby_service/issues) to see if the bug has already been reported. +2. **Open a new Bug Report issue**: If the bug has not been reported yet, open a new + issue [here](https://github.com/ksenia312/nearby_service/issues/new/choose) and use template `Bug Report` to indicate + that this is a bug report. +3. **Provide details**: Include a clear and concise description of the bug, steps to reproduce it, and any relevant logs + or screenshots. + +#### Suggesting an improvement or feature + +If you have an idea for an improvement or a new feature, follow these steps: + +1. **Search for existing issues**: Check the [Issues](https://github.com/ksenia312/nearby_service/issues) to see if your + suggestion has already been made. +2. **Open a new Feature Request issue**: If not, open a new + issue [here](https://github.com/ksenia312/nearby_service/issues/new/choose) and use + template `Feature Request` to indicate that this is a suggestion for improvement. +3. **Describe your suggestion**: Provide a detailed description of the improvement or feature, including potential use + cases and any other relevant information. + +#### Submitting a bug fix or improvement + +If you have created a fix for a bug or an improvement, please follow these steps: + +1. **Search for existing issues**: Ensure the issue has not already been fixed or the improvement has not been + implemented by checking the [Issues](https://github.com/ksenia312/nearby_service/issues). +2. **Fork the repository**: Fork the [Nearby Service repository](https://github.com/ksenia312/nearby_service) to your + own GitHub account. +3. **Create a new branch**: In your forked repository, create a new branch for your fix or improvement. +4. **Implement your fix or improvement**: Make your changes in the new branch. +5. **Open a Pull Request**: Once your changes are complete, open a Pull Request (PR) from your forked repository’s + branch to the `main` branch of the original repository. +6. **Fill in the PR template**: In your PR, fill in the PR template with the appropriate information. + +#### Asking a question about nearby_service or source code + +If you have a question about how to use Nearby Service or about the source code, please follow these steps: + +1. **Search for existing questions**: Check the [Issues](https://github.com/ksenia312/nearby_service/issues) to see if + your question has already been answered. +2. **Open a new Question issue**: If your question has not been addressed, open a new + issue [here](https://github.com/ksenia312/nearby_service/issues/new/choose) and use one of `Question` templates to + indicate that this is a question. +3. **Describe your question**: Clearly state your question and provide any necessary context. + +--- +Your effort is appreciated 💗 diff --git a/example/lib/main.dart b/example/lib/main.dart index 4688334..802a85d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -60,7 +60,7 @@ class _AppBodyState extends State { late final _nearbyService = NearbyService.getInstance( /// Define log level here logLevel: NearbyServiceLogLevel.debug, - )..communicationChannelState.addListener(() => setState(() {})); + ); AppState _state = AppState.idle; @@ -70,9 +70,11 @@ class _AppBodyState extends State { /// List of discovered devices List _peers = []; StreamSubscription? _peersSubscription; + CommunicationChannelState _communicationChannelState = + CommunicationChannelState.notConnected; /// Temporary solution to check the connection, - /// use [NearbyService.getConnectedDeviceStream] for this purpose + /// use [NearbyService.getConnectedDeviceStreamById] for this purpose /// in your application Timer? _connectionCheckTimer; NearbyDevice? _connectedDevice; @@ -158,10 +160,6 @@ class _AppBodyState extends State { return Container(); } - CommunicationChannelState get _communicationChannelState { - return _nearbyService.communicationChannelState.value; - } - Future _initialize() async { await _nearbyService.initialize(); } @@ -204,7 +202,7 @@ class _AppBodyState extends State { Future _connect(NearbyDevice device) async { // Be careful with already connected devices, // double connection may be unnecessary - final result = await _nearbyService.connect(device); + final result = await _nearbyService.connectById(device.info.id); if (result || device.status.isConnected) { final channelStarting = _tryCommunicate(device); if (!channelStarting) { @@ -240,8 +238,14 @@ class _AppBodyState extends State { } void _startCommunicationChannel(NearbyDevice device) { - if (!_communicationChannelState.isNotConnected) return; - + if (_communicationChannelState != CommunicationChannelState.notConnected) { + // channel is loading or already connected + return; + } + // start listening communication channel state + _nearbyService.getCommunicationChannelStateStream().listen((event) { + _communicationChannelState = event; + }); _nearbyService.startCommunicationChannel( NearbyCommunicationChannelData( device.info.id, @@ -275,7 +279,7 @@ class _AppBodyState extends State { Future _disconnect() async { try { - await _nearbyService.disconnect(_connectedDevice!); + await _nearbyService.disconnectById(_connectedDevice!.info.id); } finally { await _nearbyService.endCommunicationChannel(); await _nearbyService.stopDiscovery(); diff --git a/example_full/lib/domain/app_service.dart b/example_full/lib/domain/app_service.dart index 65b10f7..795b957 100644 --- a/example_full/lib/domain/app_service.dart +++ b/example_full/lib/domain/app_service.dart @@ -8,14 +8,16 @@ import 'package:nearby_service_example_full/utils/files_saver.dart'; import 'app_state.dart'; class AppService extends ChangeNotifier { - late final _nearbyService = NearbyService.getInstance() - ..communicationChannelState.addListener(notifyListeners); + late final _nearbyService = NearbyService.getInstance(); AppState state = AppState.idle; List? peers; NearbyDevice? connectedDevice; NearbyDeviceInfo? currentDeviceInfo; NearbyConnectionAndroidInfo? _connectionAndroidInfo; + CommunicationChannelState _communicationChannelState = + CommunicationChannelState.notConnected; + bool _isIOSBrowser = true; String platformVersion = 'Unknown'; String platformModel = 'Unknown'; @@ -47,6 +49,10 @@ class AppService extends ChangeNotifier { await _nearbyService.initialize( data: NearbyInitializeData(iosDeviceName: iosDeviceName), ); + _nearbyService.ios?.getIsBrowserStream().listen((event) { + _isIOSBrowser = event; + }); + startListeningCommunicationChannelState(); updateState( Platform.isAndroid ? AppState.permissions : AppState.selectClientType, ); @@ -152,7 +158,7 @@ class AppService extends ChangeNotifier { Future connect(NearbyDevice device) async { try { - await _nearbyService.connect(device); + await _nearbyService.connectById(device.info.id); } on NearbyServiceBusyException catch (_) { _logBusyException(); } catch (e, s) { @@ -163,7 +169,7 @@ class AppService extends ChangeNotifier { Future disconnect([NearbyDevice? device]) async { try { - await _nearbyService.disconnect(device); + await _nearbyService.disconnectById(device?.info.id); } on NearbyServiceBusyException catch (_) { _logBusyException(); } catch (e, s) { @@ -204,12 +210,11 @@ class AppService extends ChangeNotifier { } extension GettersExtension on AppService { - CommunicationChannelState get communicationChannelState { - return _nearbyService.communicationChannelState.value; - } + CommunicationChannelState get communicationChannelState => + _communicationChannelState; bool get isIOSBrowser { - return _nearbyService.ios?.isBrowser.value ?? false; + return _isIOSBrowser; } bool? get isAndroidGroupOwner { @@ -233,6 +238,21 @@ extension ConnectionInfoExtension on AppService { _notify(); } + void startListeningCommunicationChannelState() { + try { + _connectionInfoSubscription = + _nearbyService.getCommunicationChannelStateStream().listen( + (event) async { + _communicationChannelState = event; + _notify(); + }, + ); + } catch (e, s) { + _log(e, s); + } + _notify(); + } + Future stopListeningConnectionInfo() async { await _connectionInfoSubscription?.cancel(); _connectionInfoSubscription = null; @@ -266,7 +286,7 @@ extension ConnectedDeviceExtension on AppService { updateState(AppState.loadingConnection); try { _connectedDeviceSubscription = - _nearbyService.getConnectedDeviceStream(device).listen( + _nearbyService.getConnectedDeviceStreamById(device.info.id).listen( (event) async { final wasConnected = connectedDevice?.status.isConnected ?? false; final nowConnected = event?.status.isConnected ?? false; diff --git a/lib/nearby_service.dart b/lib/nearby_service.dart index 27577c8..f506455 100644 --- a/lib/nearby_service.dart +++ b/lib/nearby_service.dart @@ -74,8 +74,26 @@ abstract class NearbyService { /// For **IOS** this is the state of the message stream subscription. /// which is generated for the device with the current connected device ID. /// + @Deprecated( + 'Use getCommunicationChannelStateStream or communicationChannelStateValue instead', + ) ValueListenable get communicationChannelState; + /// + /// **A value to determine the communication channel's status.** + /// + /// For **Android** this is the socket connection state. + /// The server can wait for the client to connect, + /// and the client can be waiting for the server to be created. + /// Also, both can be in connected and unconnected states. + /// + /// For **IOS** this is the state of the message stream subscription. + /// which is generated for the device with the current connected device ID. + /// + /// **Can be used to retrieve the current state of the communication channel without listening to the stream via** [getCommunicationChannelStateStream]. + /// + CommunicationChannelState get communicationChannelStateValue; + /// /// Gets version of current platform. /// @@ -147,10 +165,20 @@ abstract class NearbyService { /// Returns the constantly updating [NearbyDevice] you are currently connected to. /// If it returns null, then there is no connection at the moment. /// + @Deprecated('Use getConnectedDeviceStreamById instead') Stream getConnectedDeviceStream(NearbyDevice device) { return NearbyServicePlatform.instance.getConnectedDeviceStream(device); } + /// + /// Returns the constantly updating [NearbyDevice] you are currently connected to. + /// If it returns null, then there is no connection at the moment. + /// + Stream getConnectedDeviceStreamById(String deviceId) { + return NearbyServicePlatform.instance + .getConnectedDeviceStreamById(deviceId); + } + /// /// Initialization of a platform-specific service. /// @@ -166,7 +194,7 @@ abstract class NearbyService { /// Starts searching for devices using a platform-specific service. /// /// Note that the [NearbyIOSService] implementation starts **browsing** or - /// **advertising** depending on the [NearbyIOSService.isBrowser]. + /// **advertising** depending on the [NearbyIOSService.isBrowserValue]. /// /// On Android can throw mapped from native platform exceptions: /// 1. [NearbyServiceBusyException] @@ -181,7 +209,7 @@ abstract class NearbyService { /// Stops searching for devices using a platform-specific service. /// /// Note that the [NearbyIOSService] implementation stops **browsing** or - /// **advertising** depending on the [NearbyIOSService.isBrowser]. + /// **advertising** depending on the [NearbyIOSService.isBrowserValue]. /// /// On Android can throw mapped from native platform exceptions: /// 1. [NearbyServiceBusyException] @@ -208,16 +236,18 @@ abstract class NearbyService { /// 4. [NearbyServiceGenericErrorException] /// 5. [NearbyServiceUnknownException] /// + @Deprecated('Use connectById instead') Future connect(NearbyDevice device); /// - /// Disconnects from passed [device] using a platform-specific service. + /// Connects to passed [deviceId] using a platform-specific service. + /// + /// Note that the [NearbyIOSService] implementation **invites** or + /// **accepts invite** depending on the [NearbyIOSService.isBrowserValue]. /// /// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed. /// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed. /// - /// **For IOS [device] is required!!!** - /// /// On Android can throw mapped from native platform exceptions: /// 1. [NearbyServiceBusyException] /// 2. [NearbyServiceP2PUnsupportedException] @@ -225,8 +255,41 @@ abstract class NearbyService { /// 4. [NearbyServiceGenericErrorException] /// 5. [NearbyServiceUnknownException] /// + Future connectById(String deviceId); + + /// + /// Disconnects from passed [device] using a platform-specific service. + /// + /// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed. + /// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed. + /// + /// On Android can throw mapped from native platform exceptions: + /// 1. [NearbyServiceBusyException] + /// 2. [NearbyServiceP2PUnsupportedException] + /// 3. [NearbyServiceNoServiceRequestsException] + /// 4. [NearbyServiceGenericErrorException] + /// 5. [NearbyServiceUnknownException] + /// + /// **For IOS [device] is required!!!** + @Deprecated('Use disconnectById instead') Future disconnect([NearbyDevice? device]); + /// + /// Disconnects from passed [deviceId] using a platform-specific service. + /// + /// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed. + /// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed. + /// + /// On Android can throw mapped from native platform exceptions: + /// 1. [NearbyServiceBusyException] + /// 2. [NearbyServiceP2PUnsupportedException] + /// 3. [NearbyServiceNoServiceRequestsException] + /// 4. [NearbyServiceGenericErrorException] + /// 5. [NearbyServiceUnknownException] + /// + /// **For IOS [deviceId] is required!!!** + Future disconnectById([String? deviceId]); + /// /// If the device is already connected, it does not mean that you can /// send and receive data. @@ -235,11 +298,9 @@ abstract class NearbyService { /// You need to call [startCommunicationChannel] before using [send]. /// A communication channel can only be created if you are connected to some device. /// - /// You can monitor changes in communication channel state using the [communicationChannelState] getter. + /// You can monitor changes in communication channel state using the [getCommunicationChannelStateStream] method. /// - FutureOr startCommunicationChannel( - NearbyCommunicationChannelData data, - ); + FutureOr startCommunicationChannel(NearbyCommunicationChannelData data); /// /// If you called [startCommunicationChannel], remember that you have @@ -250,6 +311,19 @@ abstract class NearbyService { /// FutureOr endCommunicationChannel(); + /// + /// **A stream with values of [CommunicationChannelState] to determine the communication channel's status.** + /// + /// For **Android** this is the socket connection state. + /// The server can wait for the client to connect, + /// and the client can be waiting for the server to be created. + /// Also, both can be in connected and unconnected states. + /// + /// For **IOS** this is the state of the message stream subscription. + /// which is generated for the device with the current connected device ID. + /// + Stream getCommunicationChannelStateStream(); + /// /// Method to send data to the created communication channel. /// diff --git a/lib/nearby_service_method_channel.dart b/lib/nearby_service_method_channel.dart index 6a0e81b..dc994b9 100644 --- a/lib/nearby_service_method_channel.dart +++ b/lib/nearby_service_method_channel.dart @@ -54,13 +54,17 @@ class MethodChannelNearbyService extends NearbyServicePlatform { } @override + @Deprecated('Use getConnectedDeviceStreamById instead') Stream getConnectedDeviceStream(NearbyDevice device) { + return getConnectedDeviceStreamById(device.info.id); + } + + @override + Stream getConnectedDeviceStreamById(String deviceId) { const connectedDeviceChannel = EventChannel( "nearby_service_connected_device", ); - return connectedDeviceChannel - .receiveBroadcastStream(device.info.id) - .map((e) { + return connectedDeviceChannel.receiveBroadcastStream(deviceId).map((e) { final updatedResult = ResultHandler.instance.handle(e); return NearbyDeviceMapper.instance.mapToDevice(updatedResult); }); diff --git a/lib/nearby_service_platform_interface.dart b/lib/nearby_service_platform_interface.dart index 046ef66..39dc700 100644 --- a/lib/nearby_service_platform_interface.dart +++ b/lib/nearby_service_platform_interface.dart @@ -48,11 +48,21 @@ abstract class NearbyServicePlatform extends PlatformInterface { throw UnimplementedError('streamPeers() has not been implemented.'); } + @Deprecated('Use getConnectedDeviceStreamById instead') Stream getConnectedDeviceStream(NearbyDevice device) { throw UnimplementedError( 'getConnectedDeviceStream() has not been implemented.'); } + Stream getConnectedDeviceStreamById(String deviceId) { + throw UnimplementedError( + 'getConnectedDeviceStreamById() has not been implemented.', + ); + } + + @Deprecated( + 'This method will be removed. Method disconnect is platform-specific and you should use NearbyServiceIOSPlatform.disconnectById or NearbyServiceAndroidPlatform.disconnectById instead.', + ) Future disconnect(NearbyDevice device) { throw UnimplementedError('disconnect() has not been implemented.'); } diff --git a/lib/src/base/nearby_message_base.dart b/lib/src/base/nearby_message_base.dart index 7555b28..79fb513 100644 --- a/lib/src/base/nearby_message_base.dart +++ b/lib/src/base/nearby_message_base.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:nearby_service/nearby_service.dart'; /// @@ -36,4 +37,10 @@ abstract base class NearbyMessage { String toString() { return 'NearbyMessage{content: $content}'; } + + /// + /// Get [Map] from [NearbyMessage]. + /// + @mustCallSuper + Map toJson() => {'content': content.toJson()}; } diff --git a/lib/src/model/nearby_message.dart b/lib/src/model/nearby_message.dart index 827bfa7..b0e4298 100644 --- a/lib/src/model/nearby_message.dart +++ b/lib/src/model/nearby_message.dart @@ -23,10 +23,11 @@ final class OutgoingNearbyMessage /// /// Get [Map] from [OutgoingNearbyMessage]. /// + @override Map toJson() { return { - 'content': content.toJson(), 'receiver': receiver.toJson(), + ...super.toJson(), }; } @@ -92,4 +93,15 @@ final class ReceivedNearbyMessage String toString() { return 'ReceivedNearbyMessage{sender: $sender content: $content}'; } + + /// + /// Get [Map] from [ReceivedNearbyMessage]. + /// + @override + Map toJson() { + return { + 'sender': sender.toJson(), + ...super.toJson(), + }; + } } diff --git a/lib/src/platforms/android/nearby_android_service.dart b/lib/src/platforms/android/nearby_android_service.dart index 7a48cc6..db16f40 100644 --- a/lib/src/platforms/android/nearby_android_service.dart +++ b/lib/src/platforms/android/nearby_android_service.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:flutter/foundation.dart'; import 'package:nearby_service/nearby_service.dart'; @@ -14,10 +13,16 @@ import 'socket_service/nearby_socket_service.dart'; class NearbyAndroidService extends NearbyService { late final _socketService = NearbySocketService(this); + @Deprecated( + 'Use getCommunicationChannelStateStream or communicationChannelStateValue instead', + ) @override - ValueListenable get communicationChannelState { - return _socketService.state; - } + ValueListenable get communicationChannelState => + _socketService.communicationChannelState; + + @override + CommunicationChannelState get communicationChannelStateValue => + _socketService.communicationChannelStateValue; /// /// Initializes Android [WifiP2PManager](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager) @@ -53,19 +58,39 @@ class NearbyAndroidService extends NearbyService { /// /// Note! Requires [NearbyAndroidDevice] to be passed. /// + @Deprecated('Use connectById instead') @override Future connect(NearbyDevice device) { _requireAndroidDevice(device); return NearbyServiceAndroidPlatform.instance.connect(device.info.id); } + /// + /// Connects to the [deviceId] on the Wifi Direct network. + /// + @override + Future connectById(String deviceId) { + return NearbyServiceAndroidPlatform.instance.connect(deviceId); + } + /// /// Disconnects from the [device] on the Wifi Direct network. /// + /// [device] is not required for Android. + /// + @Deprecated('Use disconnectById instead') + @override + Future disconnect([NearbyDevice? device]) { + return NearbyServiceAndroidPlatform.instance.disconnect(); + } + + /// + /// Disconnects from the [deviceId] on the Wifi Direct network. + /// /// Note! Requires [NearbyAndroidDevice] to be passed. /// @override - Future disconnect([NearbyDevice? device]) { + Future disconnectById([String? deviceId]) { return NearbyServiceAndroidPlatform.instance.disconnect(); } @@ -143,6 +168,11 @@ class NearbyAndroidService extends NearbyService { return NearbyServiceAndroidPlatform.instance.getConnectionInfoStream(); } + @override + Stream getCommunicationChannelStateStream() { + return _socketService.stateController.stream.asBroadcastStream(); + } + void _requireAndroidDevice(NearbyDevice device) { assert( device is NearbyAndroidDevice, diff --git a/lib/src/platforms/android/socket_service/nearby_socket_service.dart b/lib/src/platforms/android/socket_service/nearby_socket_service.dart index f37a1f4..406c4ae 100644 --- a/lib/src/platforms/android/socket_service/nearby_socket_service.dart +++ b/lib/src/platforms/android/socket_service/nearby_socket_service.dart @@ -30,7 +30,10 @@ class NearbySocketService { _pingManager, ); - final state = ValueNotifier(CommunicationChannelState.notConnected); + late final stateController = + StreamController.broadcast() + ..add(_state.value) + ..stream.asBroadcastStream().listen((e) => _state.value = e); NearbyAndroidCommunicationChannelData _androidData = const NearbyAndroidCommunicationChannelData(); @@ -40,6 +43,13 @@ class NearbySocketService { HttpServer? _server; StreamSubscription? _messagesSubscription; + final _state = ValueNotifier(CommunicationChannelState.notConnected); + + CommunicationChannelState get communicationChannelStateValue => _state.value; + + ValueListenable get communicationChannelState => + _state; + /// /// Start a socket with the user's role defined. /// If he is the owner of the group, he becomes a server. @@ -53,7 +63,7 @@ class NearbySocketService { Future startSocket({ required NearbyCommunicationChannelData data, }) async { - state.value = CommunicationChannelState.loading; + stateController.add(CommunicationChannelState.loading); _androidData = data.androidData; _connectedDeviceId = data.connectedDeviceId; @@ -123,7 +133,7 @@ class NearbySocketService { _server = null; _connectedDeviceId = null; - state.value = CommunicationChannelState.notConnected; + stateController.add(CommunicationChannelState.notConnected); return true; } catch (e) { return false; @@ -134,7 +144,7 @@ class NearbySocketService { required NearbyServiceMessagesListener socketListener, required NearbyConnectionAndroidInfo info, }) async { - if (state.value.isLoading) { + if (_state.value.isLoading) { final response = await _network.pingServer( address: info.ownerIpAddress, port: _androidData.port, @@ -216,23 +226,23 @@ class NearbySocketService { } }, onDone: () { - state.value = CommunicationChannelState.notConnected; + stateController.add(CommunicationChannelState.notConnected); socketListener.onDone?.call(); }, onError: (e, s) { Logger.error(e); - state.value = CommunicationChannelState.notConnected; + stateController.add(CommunicationChannelState.notConnected); socketListener.onError?.call(e, s); }, cancelOnError: socketListener.cancelOnError, ); } if (_messagesSubscription != null) { - state.value = CommunicationChannelState.connected; + stateController.add(CommunicationChannelState.connected); Logger.info('Socket subscription was created successfully'); socketListener.onCreated?.call(); } else { - state.value = CommunicationChannelState.notConnected; + stateController.add(CommunicationChannelState.notConnected); } } diff --git a/lib/src/platforms/ios/nearby_ios_service.dart b/lib/src/platforms/ios/nearby_ios_service.dart index 8ff9bd1..c773264 100644 --- a/lib/src/platforms/ios/nearby_ios_service.dart +++ b/lib/src/platforms/ios/nearby_ios_service.dart @@ -14,29 +14,56 @@ import 'package:nearby_service/src/utils/stream_mapper.dart'; /// class NearbyIOSService extends NearbyService { final _isBrowser = ValueNotifier(true); - final _state = ValueNotifier(CommunicationChannelState.notConnected); + final _communicationChannelState = + ValueNotifier(CommunicationChannelState.notConnected); + + late final _isBrowserController = StreamController.broadcast() + ..add(_isBrowser.value) + ..stream.asBroadcastStream().listen((e) => _isBrowser.value = e); + + late final _stateController = + StreamController.broadcast() + ..add(_communicationChannelState.value) + ..stream + .asBroadcastStream() + .listen((e) => _communicationChannelState.value = e); StreamSubscription? _messagesSubscription; StreamSubscription? _resourcesSubscription; @override + CommunicationChannelState get communicationChannelStateValue => + _communicationChannelState.value; + + @override + @Deprecated( + 'Use getCommunicationChannelStateStream or communicationChannelStateValue instead', + ) ValueListenable get communicationChannelState => - _state; + _communicationChannelState; /// /// Determines whether the current device is a **Browser** or **Advertiser**. /// + @Deprecated('Use getIsBrowserStream or isBrowserValue instead') + ValueListenable get isBrowser => _isBrowser; + + /// + /// Determines whether the current device is a **Browser** or **Advertiser**. + /// + bool get isBrowserValue => _isBrowser.value; + + /// + /// Stream that determines whether the current device is a **Browser** or **Advertiser**. + /// /// * Browser will only see devices with Advertiser status in the peers list. /// Browser sends connection requests. /// * Advertiser will see in the peers list only devices with Browser /// status that have sent it a connection request. /// Advertiser accepts or rejects connection requests. /// - ValueListenable get isBrowser => _isBrowser; - - String get _currentConnectionType { - return _isBrowser.value ? 'browsing' : 'advertising'; - } + Stream getIsBrowserStream() => + _isBrowserController.stream.asBroadcastStream(); /// /// Initializes [MCNearbyServiceAdvertiser](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyserviceadvertiser) @@ -73,8 +100,8 @@ class NearbyIOSService extends NearbyService { /// /// Starts discovery on the local P2P network. /// - /// Starts browsing for peers if [isBrowser] is true. - /// Starts advertising for peers if [isBrowser] is false. + /// Starts browsing for peers if [isBrowserValue] is true. + /// Starts advertising for peers if [isBrowserValue] is false. /// @override Future discover() async { @@ -92,8 +119,8 @@ class NearbyIOSService extends NearbyService { /// /// Slops discovery on the local P2P network. /// - /// Slops browsing for peers if [isBrowser] is true. - /// Slops advertising for peers if [isBrowser] is false. + /// Slops browsing for peers if [isBrowserValue] is true. + /// Slops advertising for peers if [isBrowserValue] is false. /// @override Future stopDiscovery() async { @@ -112,24 +139,35 @@ class NearbyIOSService extends NearbyService { /// /// Connects to the [device] on the P2P network. /// - /// Invites [device] if [isBrowser] is true. - /// Accepts invite from [device] if [isBrowser] is false. + /// Invites [device] if [isBrowserValue] is true. + /// Accepts invite from [device] if [isBrowserValue] is false. /// /// Note! Requires [NearbyIOSDevice] to be passed. - /// + @Deprecated('Use connectById instead') @override Future connect(NearbyDevice device) async { _requireIOSDevice(device); + return connectById(device.info.id); + } + + /// + /// Connects to the [deviceId] on the P2P network. + /// + /// Invites [deviceId] if [isBrowserValue] is true. + /// Accepts invite from [deviceId] if [isBrowserValue] is false. + /// + @override + Future connectById(String deviceId) async { final result = _isBrowser.value - ? await NearbyServiceIOSPlatform.instance.invite(device.info.id) - : await NearbyServiceIOSPlatform.instance.acceptInvite(device.info.id); + ? await NearbyServiceIOSPlatform.instance.invite(deviceId) + : await NearbyServiceIOSPlatform.instance.acceptInvite(deviceId); _logResult( result, onSuccess: '${_isBrowser.value ? 'Sent invitation to' : 'Accepted invitation from'} ' - '${device.info.id}', - onError: 'Failed to connect to ${device.info.id}', + '$deviceId', + onError: 'Failed to connect to $deviceId', ); return result; } @@ -139,17 +177,25 @@ class NearbyIOSService extends NearbyService { /// /// Note! Requires [NearbyIOSDevice] to be passed. /// + @Deprecated('Use disconnectById instead') @override Future disconnect([NearbyDevice? device]) async { if (device == null) return false; _requireIOSDevice(device); - final result = await NearbyServiceIOSPlatform.instance.disconnect( - device.info.id, - ); + return disconnectById(device.info.id); + } + + /// + /// Disconnects from the [deviceId] on the P2P network. + /// + @override + Future disconnectById([String? deviceId]) async { + if (deviceId == null) return false; + final result = await NearbyServiceIOSPlatform.instance.disconnect(deviceId); _logResult( result, - onSuccess: 'Disconnected from ${device.info.id}', - onError: 'Failed to disconnect from ${device.info.id}', + onSuccess: 'Disconnected from $deviceId', + onError: 'Failed to disconnect from $deviceId', ); return result; } @@ -163,7 +209,8 @@ class NearbyIOSService extends NearbyService { NearbyCommunicationChannelData data, ) async { Logger.debug('Creating messages subscription'); - _state.value = CommunicationChannelState.loading; + _stateController.add(CommunicationChannelState.loading); + await endCommunicationChannel(); final eventListener = data.messagesListener; final filesListener = data.filesListener; @@ -175,12 +222,12 @@ class NearbyIOSService extends NearbyService { .listen( eventListener.onData, onDone: () { - _state.value = CommunicationChannelState.notConnected; + _stateController.add(CommunicationChannelState.notConnected); eventListener.onDone?.call(); }, onError: (e, s) { Logger.error(e); - _state.value = CommunicationChannelState.notConnected; + _stateController.add(CommunicationChannelState.notConnected); eventListener.onError?.call(e, s); }, cancelOnError: eventListener.cancelOnError, @@ -194,7 +241,7 @@ class NearbyIOSService extends NearbyService { onDone: filesListener?.onDone, onError: (e, s) { Logger.error(e); - _state.value = CommunicationChannelState.notConnected; + _stateController.add(CommunicationChannelState.notConnected); filesListener?.onError?.call(e, s); }, cancelOnError: filesListener?.cancelOnError, @@ -202,9 +249,9 @@ class NearbyIOSService extends NearbyService { if (_messagesSubscription != null) { Logger.info('Messages subscription was created successfully'); eventListener.onCreated?.call(); - _state.value = CommunicationChannelState.connected; + _stateController.add(CommunicationChannelState.connected); } else { - _state.value = CommunicationChannelState.notConnected; + _stateController.add(CommunicationChannelState.notConnected); } if (_resourcesSubscription != null) { Logger.info('Resources subscription was created successfully'); @@ -223,7 +270,7 @@ class NearbyIOSService extends NearbyService { await _resourcesSubscription?.cancel(); _messagesSubscription = null; _resourcesSubscription = null; - _state.value = CommunicationChannelState.notConnected; + _stateController.add(CommunicationChannelState.notConnected); Logger.debug('Communication channel was cancelled'); return true; } @@ -240,6 +287,11 @@ class NearbyIOSService extends NearbyService { throw NearbyServiceException.invalidMessage(message.content); } + @override + Stream getCommunicationChannelStateStream() { + return _stateController.stream.asBroadcastStream(); + } + /// /// If you want to ask the user to change the name on the network, /// you can retrieve the name previously saved in @@ -253,11 +305,11 @@ class NearbyIOSService extends NearbyService { } /// - /// Changes the [isBrowser] to the passed [value]. + /// Changes the [isBrowserValue] to the passed [value]. /// void setIsBrowser({required bool value}) { Logger.debug('Is Browser Value was set to $value'); - _isBrowser.value = value; + _isBrowserController.add(value); } void _logResult( @@ -272,6 +324,10 @@ class NearbyIOSService extends NearbyService { } } + String get _currentConnectionType { + return _isBrowser.value ? 'browsing' : 'advertising'; + } + void _requireIOSDevice(NearbyDevice device) { assert( device is NearbyIOSDevice, diff --git a/pubspec.yaml b/pubspec.yaml index 24ca7b1..3eac8e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: nearby_service description: Nearby Service Flutter Plugin is used to create connections in a P2P network. Supports sending text messages and files. -version: 0.0.9 +version: 0.1.0 homepage: https://github.com/ksenia312/nearby_service repository: https://github.com/ksenia312/nearby_service diff --git a/test/nearby_service_test.dart b/test/nearby_service_test.dart index c089ba4..3abdd8c 100644 --- a/test/nearby_service_test.dart +++ b/test/nearby_service_test.dart @@ -49,6 +49,12 @@ class MockNearbyServicePlatform throw UnimplementedError(); } + @override + Stream getConnectedDeviceStreamById(String deviceId) { + // TODO: implement getConnectedDeviceStreamById + throw UnimplementedError(); + } + @override Future getCurrentDeviceInfo() { // TODO: implement getCurrentDevice