diff --git a/example/lib/main.dart b/example/lib/main.dart index 06bfeeb..70d598e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -53,15 +53,15 @@ class MyApp extends StatelessWidget { ), if (service.currentDeviceInfo != null) Text( - 'Device Name: ${service.currentDeviceInfo!.displayName}\n' - '${Platform.isIOS ? 'Device ID: ${service.currentDeviceInfo!.id}' : ''}', + 'Device Name: ${service.currentDeviceInfo!.displayName}' + '${Platform.isIOS ? '\nDevice ID: ${service.currentDeviceInfo!.id}' : ''}', ), if (Platform.isIOS) Text( 'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}', ), Text( - 'Communication channel state: ${service.communicationChannelState.name.toUpperCase()}', + 'Communication channel state: ${service.communicationChannelState.previewName}', ) ], ), @@ -172,6 +172,16 @@ enum AppState { } } +extension on CommunicationChannelState { + String get previewName { + return switch (this) { + CommunicationChannelState.notConnected => 'Not connected', + CommunicationChannelState.loading => 'Connecting', + CommunicationChannelState.connected => 'Connected', + }; + } +} + class AppService extends ChangeNotifier { late final _nearbyService = NearbyService.getInstance() ..communicationChannelState.addListener(notifyListeners); @@ -187,6 +197,12 @@ class AppService extends ChangeNotifier { StreamSubscription? peersSubscription; StreamSubscription? connectedDeviceSubscription; + @override + void dispose() { + stopListeningAll(); + super.dispose(); + } + CommunicationChannelState get communicationChannelState { return _nearbyService.communicationChannelState.value; } @@ -328,7 +344,7 @@ class AppService extends ChangeNotifier { final wasConnected = connectedDevice?.status.isConnected ?? false; final nowConnected = event?.status.isConnected ?? false; if (wasConnected && !nowConnected) { - restart(); + stopListeningAll(); return; } connectedDevice = event; @@ -358,7 +374,7 @@ class AppService extends ChangeNotifier { Future startCommunicationChannel({ ValueChanged? listener, }) async { - final eventListener = NearbyServiceStreamListener( + final eventListener = NearbyServiceStreamListener( onCreated: (_) { updateState(AppState.communicationChannelCreated); }, @@ -366,7 +382,7 @@ class AppService extends ChangeNotifier { listener?.call(event); }, onError: (e, [StackTrace? s]) { - restart(); + stopListeningAll(); }, ); @@ -396,17 +412,15 @@ class AppService extends ChangeNotifier { print(e); } } finally { - await restart(); + await stopListeningAll(); } notifyListeners(); } - Future restart() async { + Future stopListeningAll() async { await stopListeningConnectedDevice(); await stopListeningPeers(); await stopDiscovery(); - - await discover(); } void updateState(AppState state, {bool shouldNotify = true}) { @@ -768,7 +782,7 @@ class _ConnectedSocketBodyState extends State<_ConnectedSocketBody> { if (device == null) { return Center( child: _ActionButton( - onTap: service.restart, + onTap: service.stopListeningAll, title: 'Restart', ), ); diff --git a/lib/src/models/nearby_message.dart b/lib/src/models/nearby_message.dart index ed3d827..d1a9756 100644 --- a/lib/src/models/nearby_message.dart +++ b/lib/src/models/nearby_message.dart @@ -12,6 +12,13 @@ abstract class NearbyMessage { final String value; + /// + /// Checks if [value] is not empty + /// + bool get isValid { + return value.isNotEmpty; + } + @override bool operator ==(Object other) => identical(this, other) || 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 3b53e4a..602a98f 100644 --- a/lib/src/platforms/android/socket_service/nearby_socket_service.dart +++ b/lib/src/platforms/android/socket_service/nearby_socket_service.dart @@ -27,6 +27,7 @@ class NearbySocketService { String? _connectedDeviceId; WebSocket? _socket; + HttpServer? _server; StreamSubscription? _messagesSubscription; /// @@ -72,21 +73,25 @@ class NearbySocketService { /// Add [OutgoingNearbyMessage]'s JSON representation to [_socket]. /// Future send(OutgoingNearbyMessage message) async { - if (_socket != null && message.receiver.id == _connectedDeviceId) { - final sender = await _manager.getCurrentDeviceInfo(); - if (sender != null) { - _socket!.add( - jsonEncode( - { - 'message': message.value, - 'sender': sender.toJson(), - }, - ), - ); + if (message.isValid) { + if (_socket != null && message.receiver.id == _connectedDeviceId) { + final sender = await _manager.getCurrentDeviceInfo(); + if (sender != null) { + _socket!.add( + jsonEncode( + { + 'message': message.value, + 'sender': sender.toJson(), + }, + ), + ); + } + return true; } - return true; + return false; + } else { + throw NearbyServiceException.invalidMessage(message.value); } - return false; } /// @@ -96,8 +101,12 @@ class NearbySocketService { try { await _messagesSubscription?.cancel(); _messagesSubscription = null; + _socket?.close(); _socket = null; + _server?.close(force: true); + _server = null; _connectedDeviceId = null; + state.value = CommunicationChannelState.notConnected; return true; } catch (e) { return false; @@ -142,11 +151,11 @@ class NearbySocketService { required int port, ValueChanged? serverListener, }) async { - final httpServer = await _network.startServer( + _server = await _network.startServer( ownerIpAddress: info.ownerIpAddress, port: port, ); - httpServer?.listen( + _server?.listen( (request) async { serverListener?.call(request); final isPing = await _pingManager.checkPing(request); @@ -156,7 +165,7 @@ class NearbySocketService { return; } - if (request.uri.path == '/ws') { + if (request.uri.path == _Urls.ws) { _socket = await WebSocketTransformer.upgrade(request); _createSocketSubscription(socketListener); } else { diff --git a/lib/src/platforms/android/socket_service/network.dart b/lib/src/platforms/android/socket_service/network.dart index 744dfbb..ab6d618 100644 --- a/lib/src/platforms/android/socket_service/network.dart +++ b/lib/src/platforms/android/socket_service/network.dart @@ -5,6 +5,10 @@ class _Protocols { static const ws = 'ws://'; } +class _Urls { + static const ws = '/ws'; +} + class NearbyServiceNetwork { final _httpClient = HttpClient(); final _random = Random(); @@ -20,7 +24,7 @@ class NearbyServiceNetwork { final response = await request.close(); return response; } catch (e) { - Logger.error('Server is unreachable'); + Logger.error('Server is unreachable: $e'); return null; } } @@ -37,7 +41,7 @@ class NearbyServiceNetwork { required int port, }) async { try { - final url = 'ws://$ownerIpAddress:$port'; + final url = '${_Protocols.ws}$ownerIpAddress:$port'; Logger.debug('Starting server on $url'); var server = await HttpServer.bind( ownerIpAddress, @@ -47,8 +51,7 @@ class NearbyServiceNetwork { Logger.info('Server running on $url'); return server; } catch (e) { - Logger.error('Error starting socket: $e'); - return null; + throw NearbyServiceException('Error starting socket: $e'); } } @@ -58,14 +61,14 @@ class NearbyServiceNetwork { }) async { try { final connectionId = _random.nextInt(1000) + 100; - final url = '${_Protocols.ws}$ownerIpAddress:$port/ws?as=$connectionId'; + final url = + '${_Protocols.ws}$ownerIpAddress:$port${_Urls.ws}?as=$connectionId'; Logger.debug('Connecting to $url'); final socket = await WebSocket.connect(url); Logger.info('Connected to $url'); return socket; } catch (e) { - Logger.error('Error connecting to server: $e'); - return null; + throw NearbyServiceException('Error connecting to server: $e'); } } } diff --git a/lib/src/platforms/ios/nearby_ios_service.dart b/lib/src/platforms/ios/nearby_ios_service.dart index 5ef825d..012aa02 100644 --- a/lib/src/platforms/ios/nearby_ios_service.dart +++ b/lib/src/platforms/ios/nearby_ios_service.dart @@ -201,6 +201,7 @@ class NearbyIOSService extends NearbyService { FutureOr endCommunicationChannel() async { await _messagesSubscription?.cancel(); _messagesSubscription = null; + _state.value = CommunicationChannelState.notConnected; Logger.debug('Communication channel was cancelled'); return true; } @@ -211,7 +212,10 @@ class NearbyIOSService extends NearbyService { /// @override Future send(OutgoingNearbyMessage message) { - return NearbyServiceIOSPlatform.instance.send(message); + if (message.isValid) { + return NearbyServiceIOSPlatform.instance.send(message); + } + throw NearbyServiceException.invalidMessage(message.value); } /// @@ -242,7 +246,7 @@ class NearbyIOSService extends NearbyService { if (value) { Logger.info(onSuccess); } else { - Logger.error(onError); + throw NearbyServiceException(onError); } } diff --git a/lib/src/types/communication_channel_data.dart b/lib/src/types/communication_channel_data.dart index c8ad3c7..28a2806 100644 --- a/lib/src/types/communication_channel_data.dart +++ b/lib/src/types/communication_channel_data.dart @@ -28,7 +28,7 @@ class NearbyCommunicationChannelData { /// /// Listener for message stream changes. /// - final NearbyServiceStreamListener eventListener; + final NearbyServiceStreamListener eventListener; /// /// Android-specific connection data. diff --git a/lib/src/types/nearby_service_stream_listener.dart b/lib/src/types/nearby_service_stream_listener.dart index 4ce1272..8a2363f 100644 --- a/lib/src/types/nearby_service_stream_listener.dart +++ b/lib/src/types/nearby_service_stream_listener.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:nearby_service/nearby_service.dart'; /// /// Stream Subscription Listener. /// -class NearbyServiceStreamListener { +class NearbyServiceStreamListener { /// /// It is required to pass the [onData] parameter to process the /// data that came through the stream. @@ -18,8 +19,8 @@ class NearbyServiceStreamListener { this.cancelOnError, }); - final ValueChanged onData; - final ValueChanged>? onCreated; + final ValueChanged onData; + final ValueChanged>? onCreated; final VoidCallback? onDone; final void Function(Object, [StackTrace])? onError; final bool? cancelOnError; diff --git a/lib/src/utils/exception.dart b/lib/src/utils/exception.dart index 788ce6f..ddafce7 100644 --- a/lib/src/utils/exception.dart +++ b/lib/src/utils/exception.dart @@ -30,5 +30,11 @@ class NearbyServiceException implements Exception { ); } + factory NearbyServiceException.invalidMessage(String value) { + return NearbyServiceException( + 'The message="$value" is not valid', + ); + } + final Object? error; }