diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 9625e10..7c56964 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 11.0
+ 12.0
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 0cbdea6..06bfeeb 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -51,15 +51,18 @@ class MyApp extends StatelessWidget {
'Platform: ${service.platformVersion}\n'
'Model: ${service.platformModel}',
),
- if (service.currentDevice != null)
+ if (service.currentDeviceInfo != null)
Text(
- 'Device Name: ${service.currentDevice!.displayName}\n'
- '${Platform.isIOS ? 'Device ID: ${service.currentDevice!.id}' : ''}\n',
+ 'Device Name: ${service.currentDeviceInfo!.displayName}\n'
+ '${Platform.isIOS ? 'Device ID: ${service.currentDeviceInfo!.id}' : ''}',
),
if (Platform.isIOS)
Text(
- 'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}\n',
+ 'You are ${service.isIOSBrowser ? 'going to find your friend' : 'waiting for another user to connect'}',
),
+ Text(
+ 'Communication channel state: ${service.communicationChannelState.name.toUpperCase()}',
+ )
],
),
),
@@ -170,12 +173,13 @@ enum AppState {
}
class AppService extends ChangeNotifier {
- final _nearbyService = NearbyService.getInstance();
+ late final _nearbyService = NearbyService.getInstance()
+ ..communicationChannelState.addListener(notifyListeners);
AppState state = AppState.idle;
List? peers;
NearbyDevice? connectedDevice;
- NearbyDeviceInfo? currentDevice;
+ NearbyDeviceInfo? currentDeviceInfo;
String platformVersion = 'Unknown';
String platformModel = 'Unknown';
@@ -183,8 +187,8 @@ class AppService extends ChangeNotifier {
StreamSubscription? peersSubscription;
StreamSubscription? connectedDeviceSubscription;
- bool get isCommunicationChannelConnecting {
- return _nearbyService.isCommunicationChannelConnecting.value;
+ CommunicationChannelState get communicationChannelState {
+ return _nearbyService.communicationChannelState.value;
}
bool get isIOSBrowser {
@@ -202,7 +206,7 @@ class AppService extends ChangeNotifier {
notifyListeners();
}
- Future getSavedDeviceName() async {
+ Future getSavedIOSDeviceName() async {
return (await _nearbyService.ios?.getSavedDeviceName()) ?? platformModel;
}
@@ -211,7 +215,7 @@ class AppService extends ChangeNotifier {
await _nearbyService.initialize(
data: NearbyInitializeData(iosDeviceName: iosDeviceName),
);
- currentDevice = (await _nearbyService.getCurrentDevice())?.info;
+ currentDeviceInfo = await _nearbyService.getCurrentDeviceInfo();
updateState(
Platform.isAndroid ? AppState.permissions : AppState.selectClientType,
);
@@ -432,7 +436,7 @@ class _IdleBodyState extends State<_IdleBody> {
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
- context.read().getSavedDeviceName().then((value) {
+ context.read().getSavedIOSDeviceName().then((value) {
controller.text = value;
controller.selection = TextSelection.collapsed(offset: value.length);
setState(() {
@@ -719,7 +723,8 @@ class _ConnectedBody extends StatelessWidget {
else
_DevicePreview(device: device, largeView: true),
const SizedBox(height: 10),
- if (!(service.isCommunicationChannelConnecting))
+ if (service.communicationChannelState !=
+ CommunicationChannelState.loading)
_ActionButton(
onTap: () => service.startCommunicationChannel(
listener: (event) => AppShackBar.show(
diff --git a/ios/Classes/MyDeviceDataGenerator.swift b/ios/Classes/MyDeviceDataGenerator.swift
index 0346487..592a451 100644
--- a/ios/Classes/MyDeviceDataGenerator.swift
+++ b/ios/Classes/MyDeviceDataGenerator.swift
@@ -4,6 +4,7 @@ import MultipeerConnectivity
let SERVICE_TYPE = "mp-connection"
let PEER_ID = "PEER-ID"
let DEVICE_NAME = "DEVICE-NAME"
+let ON_MESSAGE_RECEIVED = Notification.Name("NearbySessionOnMessageReceived")
class MyDeviceDataGenerator {
static func generate(name: String?) -> NearbyDevice {
diff --git a/ios/Classes/NearbyServicePlugin.swift b/ios/Classes/NearbyServicePlugin.swift
index 60829af..8a03528 100644
--- a/ios/Classes/NearbyServicePlugin.swift
+++ b/ios/Classes/NearbyServicePlugin.swift
@@ -38,8 +38,8 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
NotificationCenter.default.addObserver(
instance,
- selector: #selector(messageReceived),
- name: NearbySession.messageReceived,
+ selector: #selector(onMessageReceived),
+ name: ON_MESSAGE_RECEIVED,
object: nil
)
@@ -49,12 +49,6 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
NearbyDevicesStore.instance.clear()
}
- @objc func messageReceived(notification: Notification) {
- DispatchQueue.main.async {
- let result = NearbyMessageConverter.convert(userInfo: notification.userInfo)
- self.channel.invokeMethod("invoke_nearby_service_message_received", arguments: result)
- }
- }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -124,6 +118,12 @@ public class NearbyServicePlugin: NSObject, FlutterPlugin {
result(FlutterMethodNotImplemented)
}
}
+ @objc func onMessageReceived(notification: Notification) {
+ DispatchQueue.main.async {
+ let result = NearbyMessageConverter.convert(userInfo: notification.userInfo)
+ self.channel.invokeMethod("invoke_nearby_service_message_received", arguments: result)
+ }
+ }
private func getArgument(for name: String, call: FlutterMethodCall) -> T? {
guard let data = call.arguments as? Dictionary else {
diff --git a/ios/Classes/NearbySession.swift b/ios/Classes/NearbySession.swift
index 10138ba..35953d8 100644
--- a/ios/Classes/NearbySession.swift
+++ b/ios/Classes/NearbySession.swift
@@ -16,8 +16,6 @@ class NearbySession: NSObject {
self.session = MCSession(peer: peerID)
}
- static let messageReceived = Notification.Name("NearbySessionReceivedMessage")
-
static func create(peerID: MCPeerID) -> NearbySession {
let instance = NearbySession(peerID: peerID)
instance.session.delegate = instance
@@ -34,7 +32,7 @@ extension NearbySession: MCSessionDelegate {
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
NotificationCenter.default.post(
- name: NearbySession.messageReceived,
+ name: ON_MESSAGE_RECEIVED,
object: nil,
userInfo: ["from": peerID, "data": data]
)
diff --git a/lib/nearby_service.dart b/lib/nearby_service.dart
index 6ad26a1..0d63a99 100644
--- a/lib/nearby_service.dart
+++ b/lib/nearby_service.dart
@@ -12,7 +12,21 @@ export 'src/models/models.dart';
export 'src/utils/utils.dart';
export 'src/types/types.dart';
+///
+/// The main tool for working with a P2P network.
+/// Implementations:
+/// * for Android - [NearbyAndroidService]
+/// * for IOS - [NearbyIOSService]
+///
+/// **The plugin is not supported for other platforms yet**
+///
abstract class NearbyService {
+ ///
+ /// The only way to get an instance of [NearbyService].
+ ///
+ /// Creates a service suitable for the current platform.
+ /// Otherwise it throws [NearbyServiceException].
+ ///
static NearbyService getInstance({NearbyServiceLogLevel? logLevel}) {
if (logLevel != null) {
Logger.level = logLevel;
@@ -31,65 +45,201 @@ abstract class NearbyService {
}
}
+ ///
+ /// Returns [NearbyService] cast as [NearbyIOSService] if the current
+ /// platform is IOS. Otherwise, returns null.
+ ///
late final NearbyIOSService? ios = get(
onIOS: (e) => e,
);
+
+ ///
+ /// Returns [NearbyService] cast as [NearbyAndroidService] if the current
+ /// platform is Android. Otherwise, returns null.
+ ///
late final NearbyAndroidService? android = get(
onAndroid: (e) => e,
);
- ValueListenable get isCommunicationChannelConnecting;
+ ///
+ /// **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.
+ ///
+ ValueListenable get communicationChannelState;
+ ///
+ /// Gets version of current platform.
+ ///
+ /// * Sample answer for Android: "Android 14"
+ /// * Sample answer for iOS: "IOS 17.2"
+ ///
Future getPlatformVersion() {
return NearbyServicePlatform.instance.getPlatformVersion();
}
+ ///
+ /// Gets model of current device.
+ ///
+ /// * Sample answer for Android: "Android"
+ /// * Sample answer for iOS: "IPhone 15 Pro"
+ ///
Future getPlatformModel() {
return NearbyServicePlatform.instance.getPlatformModel();
}
- Future getCurrentDevice() {
- return NearbyServicePlatform.instance.getCurrentDevice();
+ ///
+ /// Getting info about the current device in P2P scope.
+ ///
+ /// This method can be used to define the name
+ /// of the current device to be displayed on the network to other users.
+ ///
+ /// Also [NearbyDeviceInfo] contains the connection ID. Note that
+ /// the ID obtained from [getCurrentDeviceInfo] for Android
+ /// will always be **02:00:00:00:00:00** for privacy issues.
+ /// For iOS, it can be safely used.
+ ///
+ Future getCurrentDeviceInfo() {
+ return NearbyServicePlatform.instance.getCurrentDeviceInfo();
}
+ ///
+ /// Since Wi-fi must be enabled to use the plugin in Android,
+ /// [openServicesSettings] can be used to redirect the user to the **Wi-fi**
+ /// service settings on Android.
+ ///
+ /// For iOS it is not necessary to have Wi-fi enabled.
+ /// In case of its absence, the platform will try to establish a connection
+ /// by other methods. However, this method will open the settings page
+ /// for iOS, if you want the user to use Wi-fi.
+ ///
Future openServicesSettings() {
return NearbyServicePlatform.instance.openServicesSettings();
}
+ ///
+ /// A single retrieval of the current list of devices in a P2P network.
+ ///
+ /// Returns the list of [NearbyDevice] that have been stored so far.
+ /// If you want to use a constantly updated list of devices, use [getPeersStream].
+ ///
Future> getPeers() {
return NearbyServicePlatform.instance.getPeers();
}
+ ///
+ /// Returns a constantly updating list of [NearbyDevice] that
+ /// the platform-specific service has found at each point in time.
+ ///
Stream> getPeersStream() {
return NearbyServicePlatform.instance.getPeersStream();
}
+ ///
+ /// Returns the constantly updating [NearbyDevice] you are currently connected to.
+ /// If it returns null, then there is no connection at the moment.
+ ///
Stream getConnectedDeviceStream(NearbyDevice device) {
return NearbyServicePlatform.instance.getConnectedDeviceStream(device);
}
+ ///
+ /// Initialization of a platform-specific service.
+ ///
+ /// The [initialize] method must be called before calling any
+ /// other getters and methods related to P2P network (all except
+ /// [getPlatformVersion] and [getPlatformModel]).
+ ///
Future initialize({
NearbyInitializeData data = const NearbyInitializeData(),
});
+ ///
+ /// Starts searching for devices using a platform-specific service.
+ ///
+ /// Note that the [NearbyIOSService] implementation starts **browsing** or
+ /// **advertising** depending on the [NearbyIOSService.isBrowser].
+ ///
Future discover();
+ ///
+ /// Stops searching for devices using a platform-specific service.
+ ///
+ /// Note that the [NearbyIOSService] implementation stops **browsing** or
+ /// **advertising** depending on the [NearbyIOSService.isBrowser].
+ ///
Future stopDiscovery();
+ ///
+ /// Connects to passed [device] using a platform-specific service.
+ ///
+ /// Note that the [NearbyIOSService] implementation **invites** or
+ /// **accepts invite** depending on the [NearbyIOSService.isBrowser].
+ ///
+ /// Note that if [Platform.isIOS] == true, [NearbyIOSDevice] should be passed.
+ /// If [Platform.isAndroid] == true, [NearbyAndroidDevice] should be passed.
+ ///
Future connect(NearbyDevice device);
+ ///
+ /// 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.
+ ///
Future disconnect(NearbyDevice device);
+ ///
+ /// If the device is already connected, it does not mean that you can
+ /// send and receive data.
+ ///
+ /// There is a separate function for this in [NearbyService] - communication channel.
+ /// 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.
+ ///
FutureOr startCommunicationChannel(
NearbyCommunicationChannelData data,
);
+ ///
+ /// If you called [startCommunicationChannel], remember that you have
+ /// created a subscription to receive messages.
+ ///
+ /// Accordingly, it is essential to terminate any subscription.
+ /// Use [endCommunicationChannel] for this purpose.
+ ///
FutureOr endCommunicationChannel();
+ ///
+ /// Method to send data to the created communication channel.
+ ///
FutureOr send(OutgoingNearbyMessage message);
}
extension NearbyServiceGetterExtension on NearbyService {
+ ///
+ /// If you want to do different actions or get different data
+ /// **depending on the platform**, use [get].
+ ///
+ /// * The [onAndroid] callback returns this instance of [NearbyService],
+ /// cast as [NearbyAndroidService] if [Platform.isAndroid] is true.
+ ///
+ /// * The [onIOS] callback returns this instance of [NearbyService],
+ /// cast as [NearbyIOSService] if [Platform.isIOS] is true.
+ ///
+ /// * The [onAny] callback returns this instance of [NearbyService] with
+ /// no casting if both [Platform.isAndroid] and [Platform.isIOS] are false.
+ ///
+ /// **Note: any of the callbacks must not be null!**
+ ///
T? get({
T Function(NearbyAndroidService)? onAndroid,
T Function(NearbyIOSService)? onIOS,
diff --git a/lib/nearby_service_method_channel.dart b/lib/nearby_service_method_channel.dart
index def4f6e..e58a978 100644
--- a/lib/nearby_service_method_channel.dart
+++ b/lib/nearby_service_method_channel.dart
@@ -21,10 +21,10 @@ class MethodChannelNearbyService extends NearbyServicePlatform {
}
@override
- Future getCurrentDevice() async {
+ Future getCurrentDeviceInfo() async {
return NearbyDeviceMapper.instance.mapToDevice(
await methodChannel.invokeMethod('getCurrentDevice'),
- );
+ )?.info;
}
@override
diff --git a/lib/nearby_service_platform_interface.dart b/lib/nearby_service_platform_interface.dart
index c41b5ae..046ef66 100644
--- a/lib/nearby_service_platform_interface.dart
+++ b/lib/nearby_service_platform_interface.dart
@@ -31,7 +31,7 @@ abstract class NearbyServicePlatform extends PlatformInterface {
throw UnimplementedError('getPlatformModel() has not been implemented.');
}
- Future getCurrentDevice() {
+ Future getCurrentDeviceInfo() {
throw UnimplementedError('getCurrentDevice() has not been implemented.');
}
diff --git a/lib/src/models/communication_channel_state.dart b/lib/src/models/communication_channel_state.dart
new file mode 100644
index 0000000..7bfd5a5
--- /dev/null
+++ b/lib/src/models/communication_channel_state.dart
@@ -0,0 +1,5 @@
+///
+/// The status of the communication channel for data exchange.
+/// Use it to determine if you can send data over the communication channel or not.
+///
+enum CommunicationChannelState { notConnected, loading, connected }
diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart
index f38f5d7..f8a24de 100644
--- a/lib/src/models/models.dart
+++ b/lib/src/models/models.dart
@@ -1,3 +1,4 @@
export 'nearby_device.dart';
export 'nearby_device_status.dart';
export 'nearby_message.dart';
+export 'communication_channel_state.dart';
diff --git a/lib/src/models/nearby_device.dart b/lib/src/models/nearby_device.dart
index e3cbb72..9566276 100644
--- a/lib/src/models/nearby_device.dart
+++ b/lib/src/models/nearby_device.dart
@@ -1,15 +1,58 @@
import 'dart:io';
import 'package:nearby_service/nearby_service.dart';
+import 'package:nearby_service/src/utils/unknown.dart';
+///
+/// The model of the device found in the P2P network.
+///
abstract class NearbyDevice {
- static const unknown = 'unknown';
-
+ ///
+ /// The base device contains [info] and [status].
+ /// These are parameters that devices will have independent of the platform.
+ ///
const NearbyDevice({required this.info, required this.status});
+ ///
+ /// The minimum information about the device required
+ /// to display it in the list and connect to it.
+ ///
final NearbyDeviceInfo info;
+
+ ///
+ /// The connection status of the device.
+ ///
final NearbyDeviceStatus status;
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyDevice &&
+ runtimeType == other.runtimeType &&
+ info == other.info &&
+ status == other.status;
+
+ @override
+ int get hashCode => info.hashCode ^ status.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyDevice{info: $info, status: $status}';
+ }
+
+ ///
+ /// If you want to get different data
+ /// **depending on the platform**, use [byPlatform].
+ ///
+ /// * The [onAndroid] callback returns this instance of [NearbyDevice],
+ /// cast as [NearbyAndroidDevice] if [Platform.isAndroid] is true.
+ ///
+ /// * The [onIOS] callback returns this instance of [NearbyDevice],
+ /// cast as [NearbyIOSDevice] if [Platform.isIOS] is true.
+ ///
+ /// * The [onAny] callback returns this instance of [NearbyDevice] with
+ /// no casting if both [Platform.isAndroid] and [Platform.isIOS] are false.
+ ///
T? byPlatform({
T Function(NearbyDevice)? onAny,
T Function(NearbyAndroidDevice)? onAndroid,
@@ -25,7 +68,13 @@ abstract class NearbyDevice {
}
}
+///
+/// Converter for devices from JSON to models.
+///
abstract interface class NearbyDeviceMapper {
+ ///
+ /// Get the mapper instance for the current platform.
+ ///
static NearbyDeviceMapper get instance {
if (Platform.isAndroid) {
return NearbyAndroidMapper();
@@ -39,27 +88,59 @@ abstract interface class NearbyDeviceMapper {
);
}
+ ///
+ /// Converts JSON to a list of [NearbyDevice].
+ ///
List mapToDeviceList(dynamic value);
+ ///
+ /// Converts JSON to a [NearbyDevice].
+ ///
NearbyDevice? mapToDevice(dynamic value);
}
+///
+/// Minimal information about the device.
+///
class NearbyDeviceInfo {
+ ///
+ /// Used to connect to the device via [id].
+ /// Depending on the platform, [id] means different parameters.
+ ///
+ /// * For Android [id] is the MAC address of the device.
+ /// * For IOS [id] is the [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) passed from the IOS platform.
+ ///
const NearbyDeviceInfo({
required this.displayName,
required this.id,
});
+ ///
+ /// Get [NearbyDeviceInfo] from [Map].
+ ///
factory NearbyDeviceInfo.fromJson(Map? json) {
return NearbyDeviceInfo(
- displayName: json?['displayName'] ?? NearbyDevice.unknown,
- id: json?['id'] ?? NearbyDevice.unknown,
+ displayName: json?['displayName'] ?? kNearbyUnknown,
+ id: json?['id'],
);
}
+ ///
+ /// The name of the device in the context of a P2P network.
+ ///
final String displayName;
+
+ ///
+ /// Depending on the platform, [id] means different parameters.
+ ///
+ /// * For Android [id] is the MAC address of the device.
+ /// * For IOS [id] is the [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) passed from the IOS platform.
+ ///
final String id;
+ ///
+ /// Get [Map] from [NearbyDeviceInfo].
+ ///
Map toJson() {
return {
'id': id,
diff --git a/lib/src/models/nearby_device_status.dart b/lib/src/models/nearby_device_status.dart
index e594210..f0e49e9 100644
--- a/lib/src/models/nearby_device_status.dart
+++ b/lib/src/models/nearby_device_status.dart
@@ -1,3 +1,6 @@
+///
+/// Status of device connection.
+///
enum NearbyDeviceStatus {
available,
connected,
@@ -5,8 +8,36 @@ enum NearbyDeviceStatus {
connecting,
unavailable;
+ ///
+ /// Checks if status is [NearbyDeviceStatus.connected]
+ ///
bool get isConnected => this == NearbyDeviceStatus.connected;
+ ///
+ /// Checks if status is [NearbyDeviceStatus.available]
+ ///
+ bool get isAvailable => this == NearbyDeviceStatus.available;
+
+ ///
+ /// Checks if status is [NearbyDeviceStatus.failed]
+ ///
+ bool get isFailed => this == NearbyDeviceStatus.failed;
+
+ ///
+ /// Checks if status is [NearbyDeviceStatus.connecting]
+ ///
+ bool get isConnecting => this == NearbyDeviceStatus.connecting;
+
+ ///
+ /// Checks if status is [NearbyDeviceStatus.unavailable]
+ ///
+ bool get isUnavailable => this == NearbyDeviceStatus.unavailable;
+
+ ///
+ /// Get [NearbyDeviceStatus] from the Android platform code.
+ ///
+ /// You can read about it on the [Android developer site](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice#constants_1).
+ ///
static NearbyDeviceStatus fromAndroidCode(num? code) {
if (code == null) {
return NearbyDeviceStatus.failed;
@@ -21,6 +52,11 @@ enum NearbyDeviceStatus {
};
}
+ ///
+ /// Get [NearbyDeviceStatus] from the IOS platform code.
+ ///
+ /// You can read about it on the [IOS developer site](https://developer.apple.com/documentation/multipeerconnectivity/mcsessionstate).
+ ///
static NearbyDeviceStatus fromIosCode(String? code) {
if (code == null) {
return NearbyDeviceStatus.failed;
diff --git a/lib/src/models/nearby_message.dart b/lib/src/models/nearby_message.dart
index eb6f716..ed3d827 100644
--- a/lib/src/models/nearby_message.dart
+++ b/lib/src/models/nearby_message.dart
@@ -1,18 +1,54 @@
import 'package:nearby_service/nearby_service.dart';
-import 'package:nearby_service/src/utils/logger.dart';
+///
+/// Basic Message Abstraction.
+///
abstract class NearbyMessage {
+ ///
+ /// The basic message contains only [value] - the content
+ /// to be sent or received.
+ ///
const NearbyMessage({required this.value});
final String value;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyMessage &&
+ runtimeType == other.runtimeType &&
+ value == other.value;
+
+ @override
+ int get hashCode => value.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyMessage{value: $value}';
+ }
}
+///
+/// The message that will be sent from the current device.
+///
class OutgoingNearbyMessage extends NearbyMessage {
+ ///
+ /// To send a message, in addition to [value], you need to pass [receiver]
+ /// to know to whom the message is addressed.
+ ///
const OutgoingNearbyMessage({
required super.value,
required this.receiver,
});
+ ///
+ /// Data of the user to whom the message is addressed.
+ ///
+ final NearbyDeviceInfo receiver;
+
+ ///
+ /// Get [Map] from [OutgoingNearbyMessage].
+ ///
Map toJson() {
return {
'message': value,
@@ -20,26 +56,59 @@ class OutgoingNearbyMessage extends NearbyMessage {
};
}
- final NearbyDeviceInfo receiver;
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ super == other &&
+ other is OutgoingNearbyMessage &&
+ runtimeType == other.runtimeType &&
+ receiver == other.receiver;
+
+ @override
+ int get hashCode => super.hashCode ^ receiver.hashCode;
}
+///
+/// Message received by the current device.
+///
class ReceivedNearbyMessage extends NearbyMessage {
+ ///
+ /// The received message contains a [sender] in addition to [value],
+ /// to know from whom the message came.
+ ///
const ReceivedNearbyMessage({
required super.value,
required this.sender,
});
+ ///
+ /// Get [ReceivedNearbyMessage] from [Map].
+ ///
factory ReceivedNearbyMessage.fromJson(Map? json) {
- try {
- return ReceivedNearbyMessage(
- value: json?['message'] ?? '',
- sender: NearbyDeviceInfo.fromJson(json?['sender']),
- );
- } catch (e) {
- Logger.error(e);
- throw NearbyServiceException('Can\'t map to device $json');
- }
+ return ReceivedNearbyMessage(
+ value: json?['message'] ?? '',
+ sender: NearbyDeviceInfo.fromJson(json?['sender']),
+ );
}
+ ///
+ /// Data of the user from whom the message came.
+ ///
final NearbyDeviceInfo sender;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ super == other &&
+ other is ReceivedNearbyMessage &&
+ runtimeType == other.runtimeType &&
+ sender == other.sender;
+
+ @override
+ int get hashCode => super.hashCode ^ sender.hashCode;
+
+ @override
+ String toString() {
+ return 'ReceivedNearbyMessage{sender: $sender}';
+ }
}
diff --git a/lib/src/platforms/android/models/nearby_connection_info.dart b/lib/src/platforms/android/models/nearby_connection_info.dart
index 5f6bf9b..289f40d 100644
--- a/lib/src/platforms/android/models/nearby_connection_info.dart
+++ b/lib/src/platforms/android/models/nearby_connection_info.dart
@@ -1,36 +1,83 @@
-import 'dart:convert';
+import 'package:nearby_service/src/utils/json_decoder.dart';
+import 'package:nearby_service/src/utils/unknown.dart';
+///
+/// The class representing the connection information
+/// of a Wi-Fi p2p group connection for Android.
+///
class NearbyConnectionAndroidInfo {
- static const unknown = 'unknown';
-
+ ///
+ /// The class representing the Android class
+ /// [WifiP2pInfo](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo).
+ ///
const NearbyConnectionAndroidInfo({
required this.ownerIpAddress,
required this.groupFormed,
required this.isGroupOwner,
});
- factory NearbyConnectionAndroidInfo.fromJson(Map json) {
- final ownerIpAddress = (json['groupOwnerAddress'] ?? unknown) as String;
+ ///
+ /// Get [NearbyConnectionAndroidInfo] from [Map].
+ ///
+ factory NearbyConnectionAndroidInfo.fromJson(Map? json) {
+ final ownerIpAddress =
+ (json?['groupOwnerAddress'] ?? kNearbyUnknown) as String;
return NearbyConnectionAndroidInfo(
ownerIpAddress: ownerIpAddress.replaceFirst('/', ''),
- groupFormed: json['groupFormed'] ?? false,
- isGroupOwner: json['isGroupOwner'] ?? false,
+ groupFormed: json?['groupFormed'] ?? false,
+ isGroupOwner: json?['isGroupOwner'] ?? false,
);
}
+ ///
+ /// Group owner address.
+ /// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
+ ///
final String ownerIpAddress;
+
+ ///
+ /// Indicates if the current device is the group owner.
+ /// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
+ ///
final bool isGroupOwner;
+
+ ///
+ /// Indicates if the current device is the group owner.
+ /// Source [WifiP2pInfo documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo)
+ ///
final bool groupFormed;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyConnectionAndroidInfo &&
+ runtimeType == other.runtimeType &&
+ ownerIpAddress == other.ownerIpAddress &&
+ isGroupOwner == other.isGroupOwner &&
+ groupFormed == other.groupFormed;
+
+ @override
+ int get hashCode =>
+ ownerIpAddress.hashCode ^ isGroupOwner.hashCode ^ groupFormed.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyConnectionAndroidInfo{ownerIpAddress: $ownerIpAddress, isGroupOwner: $isGroupOwner, groupFormed: $groupFormed}';
+ }
}
+/// Mapper from JSON to [NearbyConnectionAndroidInfo]
class NearbyConnectionInfoMapper {
NearbyConnectionInfoMapper._();
+ ///
+ /// Converts JSON to a [NearbyConnectionAndroidInfo].
+ ///
static NearbyConnectionAndroidInfo? mapToInfo(dynamic value) {
- final jsonValue = jsonDecode(value) as Map?;
- if (jsonValue == null) {
+ if (value == null) {
return null;
}
- return NearbyConnectionAndroidInfo.fromJson(jsonValue);
+ final decoded = JSONDecoder.decodeMap(value);
+ return NearbyConnectionAndroidInfo.fromJson(decoded);
}
}
diff --git a/lib/src/platforms/android/models/nearby_device.dart b/lib/src/platforms/android/models/nearby_device.dart
index 5aa2ee9..72cdeed 100644
--- a/lib/src/platforms/android/models/nearby_device.dart
+++ b/lib/src/platforms/android/models/nearby_device.dart
@@ -1,7 +1,21 @@
import 'package:nearby_service/nearby_service.dart';
-import 'package:nearby_service/src/utils/decoder.dart';
+import 'package:nearby_service/src/utils/json_decoder.dart';
+import 'package:nearby_service/src/utils/unknown.dart';
+///
+/// A device on a P2P network obtained from the Android platform.
+///
class NearbyAndroidDevice extends NearbyDevice {
+ ///
+ /// The class representing the Android class
+ /// [WifiP2pDevice](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice).
+ ///
+ /// Automatically generates the [info] field from the [deviceName]
+ /// and [deviceAddress] fields.
+ ///
+ /// They are used because in a Wifi Direct environment
+ /// the MAC address of the device is an identifier on the network.
+ ///
NearbyAndroidDevice({
required String deviceName,
required super.status,
@@ -20,13 +34,16 @@ class NearbyAndroidDevice extends NearbyDevice {
),
);
+ ///
+ /// Gets [NearbyAndroidDevice] from [Map]
+ ///
factory NearbyAndroidDevice.fromJson(Map? json) {
return NearbyAndroidDevice(
- deviceName: json?['deviceName'] ?? NearbyDevice.unknown,
- deviceAddress: json?['deviceAddress'] ?? NearbyDevice.unknown,
+ deviceName: json?['deviceName'] ?? kNearbyUnknown,
+ deviceAddress: json?['deviceAddress'] ?? kNearbyUnknown,
isGroupOwner: json?['isGroupOwner'] ?? false,
isServiceDiscoveryCapable: json?['isServiceDiscoveryCapable'] ?? false,
- primaryDeviceType: json?['primaryDeviceType'] ?? NearbyDevice.unknown,
+ primaryDeviceType: json?['primaryDeviceType'] ?? kNearbyUnknown,
secondaryDeviceType: json?['secondaryDeviceType'],
wpsDisplaySupported: json?['wpsDisplaySupported'] ?? false,
wpsKeypadSupported: json?['wpsKeypadSupported'] ?? false,
@@ -35,20 +52,92 @@ class NearbyAndroidDevice extends NearbyDevice {
);
}
+ ///
+ /// The device MAC address uniquely identifies a Wi-Fi p2p device.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final String deviceAddress;
+
+ ///
+ /// True if the device is a group owner.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final bool isGroupOwner;
+
+ ///
+ /// True if the device is capable of service discovery.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final bool isServiceDiscoveryCapable;
+
+ ///
+ /// Primary device type identifies the type of device.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final String primaryDeviceType;
+
+ ///
+ /// Secondary device type is an optional attribute.
+ /// that can be provided by a device in addition to the primary device type.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final String? secondaryDeviceType;
+
+ ///
+ /// True if WPS keypad configuration is supported.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final bool wpsKeypadSupported;
+
+ ///
+ /// True if WPS push button configuration is supported.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final bool wpsPbcSupported;
+
+ ///
+ /// True if WPS display configuration is supported.
+ /// Source [WifiP2pDevice documentation](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice)
+ ///
final bool wpsDisplaySupported;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ super == other &&
+ other is NearbyAndroidDevice &&
+ runtimeType == other.runtimeType &&
+ deviceAddress == other.deviceAddress &&
+ isGroupOwner == other.isGroupOwner &&
+ isServiceDiscoveryCapable == other.isServiceDiscoveryCapable &&
+ primaryDeviceType == other.primaryDeviceType &&
+ secondaryDeviceType == other.secondaryDeviceType &&
+ wpsKeypadSupported == other.wpsKeypadSupported &&
+ wpsPbcSupported == other.wpsPbcSupported &&
+ wpsDisplaySupported == other.wpsDisplaySupported;
+
+ @override
+ int get hashCode =>
+ super.hashCode ^
+ deviceAddress.hashCode ^
+ isGroupOwner.hashCode ^
+ isServiceDiscoveryCapable.hashCode ^
+ primaryDeviceType.hashCode ^
+ secondaryDeviceType.hashCode ^
+ wpsKeypadSupported.hashCode ^
+ wpsPbcSupported.hashCode ^
+ wpsDisplaySupported.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyAndroidDevice{deviceAddress: $deviceAddress, isGroupOwner: $isGroupOwner, isServiceDiscoveryCapable: $isServiceDiscoveryCapable, primaryDeviceType: $primaryDeviceType, secondaryDeviceType: $secondaryDeviceType, wpsKeypadSupported: $wpsKeypadSupported, wpsPbcSupported: $wpsPbcSupported, wpsDisplaySupported: $wpsDisplaySupported}';
+ }
}
class NearbyAndroidMapper implements NearbyDeviceMapper {
@override
List mapToDeviceList(dynamic value) {
- final decoded = Decoder.decodeList(value);
+ final decoded = JSONDecoder.decodeList(value);
return [
...?decoded?.map(
(e) => NearbyAndroidDevice.fromJson(e as Map?),
@@ -58,6 +147,6 @@ class NearbyAndroidMapper implements NearbyDeviceMapper {
@override
NearbyDevice? mapToDevice(dynamic value) {
- return NearbyAndroidDevice.fromJson(Decoder.decodeMap(value));
+ return NearbyAndroidDevice.fromJson(JSONDecoder.decodeMap(value));
}
}
diff --git a/lib/src/platforms/android/nearby_android_service.dart b/lib/src/platforms/android/nearby_android_service.dart
index 122e9ca..ff9b4d3 100644
--- a/lib/src/platforms/android/nearby_android_service.dart
+++ b/lib/src/platforms/android/nearby_android_service.dart
@@ -5,18 +5,30 @@ import 'package:nearby_service/nearby_service.dart';
import 'socket_service/nearby_socket_service.dart';
+///
+/// Android implementation for [NearbyService].
+///
+/// Uses [NearbyServiceAndroidPlatform] to perform actions.
+/// Connects to the device via a socket from [NearbySocketService].
+///
class NearbyAndroidService extends NearbyService {
late final _socketService = NearbySocketService(this);
@override
- ValueListenable get isCommunicationChannelConnecting {
- return _socketService.isConnecting;
+ ValueListenable get communicationChannelState {
+ return _socketService.state;
}
NearbyConnectionAndroidInfo? get connectionInfo {
return _socketService.connectionInfo;
}
+ ///
+ /// Initializes Android [WifiP2PManager](https://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager)
+ ///
+ /// Starts listening for changes to the P2P network.
+ /// Adds platform-level action listeners.
+ ///
@override
Future initialize({
NearbyInitializeData data = const NearbyInitializeData(),
@@ -24,40 +36,48 @@ class NearbyAndroidService extends NearbyService {
return NearbyServiceAndroidPlatform.instance.initialize();
}
+ ///
+ /// Starts discovery of the Wifi Direct network.
+ ///
@override
Future discover() {
return NearbyServiceAndroidPlatform.instance.discover();
}
+ ///
+ /// Stops discovery of the Wifi Direct network.
+ ///
@override
Future stopDiscovery() {
return NearbyServiceAndroidPlatform.instance.stopDiscovery();
}
+ ///
+ /// Connects to the [device] on the Wifi Direct network.
+ ///
+ /// Note! Requires [NearbyAndroidDevice] to be passed.
+ ///
@override
Future connect(NearbyDevice device) {
_requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.connect(device.info.id);
}
+ ///
+ /// Disconnects from the [device] on the Wifi Direct network.
+ ///
+ /// Note! Requires [NearbyAndroidDevice] to be passed.
+ ///
@override
Future disconnect(NearbyDevice device) {
_requireAndroidDevice(device);
return NearbyServiceAndroidPlatform.instance.disconnect(device.info.id);
}
- Future requestPermissions() {
- return NearbyServiceAndroidPlatform.instance.requestPermissions();
- }
-
- Future checkWifiService() {
- return NearbyServiceAndroidPlatform.instance.checkWifiService();
- }
-
- Future getConnectionInfo() {
- return NearbyServiceAndroidPlatform.instance.getConnectionInfo();
- }
-
+ ///
+ /// Starts a socket service to transfer data. Uses device with
+ /// [NearbyCommunicationChannelData.connectedDeviceId].
+ ///
@override
FutureOr startCommunicationChannel(
NearbyCommunicationChannelData data,
@@ -65,16 +85,51 @@ class NearbyAndroidService extends NearbyService {
return _socketService.startSocket(data: data);
}
+ ///
+ /// Ends the socket service to stop transferring data.
+ ///
@override
FutureOr endCommunicationChannel() {
return _socketService.cancel();
}
+ ///
+ /// Adds [OutgoingNearbyMessage] to the socket.
+ ///
@override
FutureOr send(OutgoingNearbyMessage message) {
return _socketService.send(message);
}
+ ///
+ /// Request permissions at the platform level.
+ /// **This is required for Android for using the plugin!**
+ ///
+ /// For Android APIs less 33 requests `ACCESS_FINE_LOCATION` permission.
+ ///
+ /// For Android APIs equal to 33 or more, requests `ACCESS_FINE_LOCATION`
+ /// and `NEARBY_WIFI_DEVICES` permissions.
+ ///
+ Future requestPermissions() {
+ return NearbyServiceAndroidPlatform.instance.requestPermissions();
+ }
+
+ ///
+ /// Checks if Wi-fi is enabled.
+ /// **Wi-fi must be enabled for Android to use the plugin!**
+ ///
+ Future checkWifiService() {
+ return NearbyServiceAndroidPlatform.instance.checkWifiService();
+ }
+
+ ///
+ /// Returns [NearbyConnectionAndroidInfo] -
+ /// information about the connection information.
+ ///
+ Future getConnectionInfo() {
+ return NearbyServiceAndroidPlatform.instance.getConnectionInfo();
+ }
+
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 7f58207..3b53e4a 100644
--- a/lib/src/platforms/android/socket_service/nearby_socket_service.dart
+++ b/lib/src/platforms/android/socket_service/nearby_socket_service.dart
@@ -12,6 +12,9 @@ part 'ping_manager.dart';
part 'network.dart';
+///
+/// A service for creating a communication channel on the Android platform.
+///
class NearbySocketService {
NearbySocketService(this._manager);
@@ -19,17 +22,27 @@ class NearbySocketService {
final _pingManager = NearbySocketPingManager();
final _network = NearbyServiceNetwork();
- final isConnecting = ValueNotifier(false);
+ final state = ValueNotifier(CommunicationChannelState.notConnected);
NearbyConnectionAndroidInfo? connectionInfo;
String? _connectedDeviceId;
WebSocket? _socket;
StreamSubscription? _messagesSubscription;
+ ///
+ /// Start a socket with the user's role defined.
+ /// If he is the owner of the group, he becomes a server.
+ /// Otherwise, he becomes a client.
+ ///
+ /// * The server starts up and waits for a request from the
+ /// client to establish a connection.
+ /// * The client pings the server until he receives a pong.
+ /// When he does, he tries to connect to the server.
+ ///
Future startSocket({
required NearbyCommunicationChannelData data,
}) async {
- isConnecting.value = true;
+ state.value = CommunicationChannelState.loading;
_connectedDeviceId = data.connectedDeviceId;
connectionInfo = await _manager.getConnectionInfo();
if (connectionInfo != null && connectionInfo!.groupFormed) {
@@ -55,15 +68,18 @@ class NearbySocketService {
return false;
}
+ ///
+ /// Add [OutgoingNearbyMessage]'s JSON representation to [_socket].
+ ///
Future send(OutgoingNearbyMessage message) async {
if (_socket != null && message.receiver.id == _connectedDeviceId) {
- final sender = await _manager.getCurrentDevice();
+ final sender = await _manager.getCurrentDeviceInfo();
if (sender != null) {
_socket!.add(
jsonEncode(
{
'message': message.value,
- 'sender': sender.info.toJson(),
+ 'sender': sender.toJson(),
},
),
);
@@ -73,9 +89,11 @@ class NearbySocketService {
return false;
}
+ ///
+ /// Turns off [_messagesSubscription] and [_socket].
+ ///
Future cancel() async {
try {
- isConnecting.value = false;
await _messagesSubscription?.cancel();
_messagesSubscription = null;
_socket = null;
@@ -153,7 +171,7 @@ class NearbySocketService {
void _createSocketSubscription(NearbyServiceStreamListener socketListener) {
Logger.debug('Starting socket subscription');
- isConnecting.value = false;
+
if (_connectedDeviceId != null) {
_messagesSubscription = _socket
?.map(MessagesStreamMapper.toMessage)
@@ -162,17 +180,24 @@ class NearbySocketService {
.map((e) => MessagesStreamMapper.replaceId(e, _connectedDeviceId!))
.listen(
socketListener.onData,
- onDone: socketListener.onDone,
+ onDone: () {
+ state.value = CommunicationChannelState.notConnected;
+ socketListener.onDone?.call();
+ },
onError: (e, s) {
Logger.error(e);
+ state.value = CommunicationChannelState.notConnected;
socketListener.onError?.call(e, s);
},
cancelOnError: socketListener.cancelOnError,
);
}
if (_messagesSubscription != null) {
+ state.value = CommunicationChannelState.connected;
Logger.info('Socket subscription was created successfully');
socketListener.onCreated?.call(_messagesSubscription!);
+ } else {
+ state.value = CommunicationChannelState.notConnected;
}
}
}
diff --git a/lib/src/platforms/ios/models/nearby_device.dart b/lib/src/platforms/ios/models/nearby_device.dart
index 751fd91..001afdf 100644
--- a/lib/src/platforms/ios/models/nearby_device.dart
+++ b/lib/src/platforms/ios/models/nearby_device.dart
@@ -1,7 +1,18 @@
import 'package:nearby_service/nearby_service.dart';
-import 'package:nearby_service/src/utils/decoder.dart';
+import 'package:nearby_service/src/utils/json_decoder.dart';
+///
+/// A device on a P2P network obtained from the IOS platform.
+///
class NearbyIOSDevice extends NearbyDevice {
+ ///
+ /// A class representing an IOS device on a P2P network.
+ ///
+ /// [NearbyDeviceInfo] for IOS consists of the [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) passed from the platform
+ /// and a displayName passed from the platform.
+ ///
+ /// [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) is an identifier on the local network for IOS.
+ ///
NearbyIOSDevice({
required super.info,
required super.status,
@@ -10,6 +21,9 @@ class NearbyIOSDevice extends NearbyDevice {
this.deviceType,
});
+ ///
+ /// Gets [NearbyIOSDevice] from [Map].
+ ///
factory NearbyIOSDevice.fromJson(Map? json) {
return NearbyIOSDevice(
info: NearbyDeviceInfo.fromJson(json),
@@ -20,15 +34,45 @@ class NearbyIOSDevice extends NearbyDevice {
);
}
+ ///
+ /// `UIDevice.current.systemName` from IOS Platform.
+ ///
final String? os;
+
+ ///
+ /// `UIDevice.current.systemVersion` from IOS Platform.
+ ///
final String? osVersion;
+
+ ///
+ /// `UIDevice.current.model` from IOS Platform.
+ ///
final String? deviceType;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ super == other &&
+ other is NearbyIOSDevice &&
+ runtimeType == other.runtimeType &&
+ os == other.os &&
+ osVersion == other.osVersion &&
+ deviceType == other.deviceType;
+
+ @override
+ int get hashCode =>
+ super.hashCode ^ os.hashCode ^ osVersion.hashCode ^ deviceType.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyIOSDevice{os: $os, osVersion: $osVersion, deviceType: $deviceType}';
+ }
}
class NearbyIOSMapper implements NearbyDeviceMapper {
@override
List mapToDeviceList(dynamic value) {
- final decoded = Decoder.decodeList(value);
+ final decoded = JSONDecoder.decodeList(value);
return [
...?decoded?.map(
(e) => NearbyIOSDevice.fromJson(e as Map?),
@@ -39,7 +83,7 @@ class NearbyIOSMapper implements NearbyDeviceMapper {
@override
NearbyDevice? mapToDevice(dynamic value) {
return NearbyIOSDevice.fromJson(
- Decoder.decodeMap(value),
+ JSONDecoder.decodeMap(value),
);
}
}
diff --git a/lib/src/platforms/ios/nearby_ios_service.dart b/lib/src/platforms/ios/nearby_ios_service.dart
index 8cd4644..5ef825d 100644
--- a/lib/src/platforms/ios/nearby_ios_service.dart
+++ b/lib/src/platforms/ios/nearby_ios_service.dart
@@ -5,22 +5,54 @@ import 'package:nearby_service/nearby_service.dart';
import 'package:nearby_service/src/utils/logger.dart';
import 'package:nearby_service/src/utils/stream_mapper.dart';
+///
+/// IOS implementation for [NearbyService].
+///
+/// Uses [NearbyServiceIOSPlatform] to perform actions.
+/// Connects to the device by subscribing to messages from the selected
+/// device by identifier.
+///
class NearbyIOSService extends NearbyService {
final _isBrowser = ValueNotifier(true);
- final _isCommunicationChannelConnecting = ValueNotifier(false);
+ final _state = ValueNotifier(CommunicationChannelState.notConnected);
StreamSubscription? _messagesSubscription;
@override
- ValueListenable get isCommunicationChannelConnecting =>
- _isCommunicationChannelConnecting;
+ ValueListenable get communicationChannelState =>
+ _state;
+ ///
+ /// 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';
}
+ ///
+ /// Initializes [MCNearbyServiceAdvertiser](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyserviceadvertiser)
+ /// and [MCNearbyServiceBrowser](https://developer.apple.com/documentation/multipeerconnectivity/mcnearbyservicebrowser)
+ /// to allow this device to be both.
+ ///
+ /// Creates [MCPeerID](https://developer.apple.com/documentation/multipeerconnectivity/mcpeerid) for
+ /// this device.
+ ///
+ /// The name of the device on the network can be
+ /// specified on initialization via the parameter [data].
+ ///
+ /// [NearbyInitializeData.iosDeviceName] will be passed to the platform as initial
+ /// name. If a new name is not passed, the previous name stored
+ /// in [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults)
+ /// will be used. If there is no saved name, `UIDevice.current.name` will be used.
+ ///
@override
Future initialize({
NearbyInitializeData data = const NearbyInitializeData(),
@@ -37,6 +69,12 @@ class NearbyIOSService extends NearbyService {
return result;
}
+ ///
+ /// Starts discovery on the local P2P network.
+ ///
+ /// Starts browsing for peers if [isBrowser] is true.
+ /// Starts advertising for peers if [isBrowser] is false.
+ ///
@override
Future discover() async {
final result = _isBrowser.value
@@ -50,6 +88,12 @@ class NearbyIOSService extends NearbyService {
return result;
}
+ ///
+ /// Slops discovery on the local P2P network.
+ ///
+ /// Slops browsing for peers if [isBrowser] is true.
+ /// Slops advertising for peers if [isBrowser] is false.
+ ///
@override
Future stopDiscovery() async {
final result = _isBrowser.value
@@ -64,6 +108,14 @@ class NearbyIOSService extends NearbyService {
return result;
}
+ ///
+ /// Connects to the [device] on the P2P network.
+ ///
+ /// Invites [device] if [isBrowser] is true.
+ /// Accepts invite from [device] if [isBrowser] is false.
+ ///
+ /// Note! Requires [NearbyIOSDevice] to be passed.
+ ///
@override
Future connect(NearbyDevice device) async {
_requireIOSDevice(device);
@@ -81,6 +133,11 @@ class NearbyIOSService extends NearbyService {
return result;
}
+ ///
+ /// Disconnects from the [device] on the P2P network.
+ ///
+ /// Note! Requires [NearbyIOSDevice] to be passed.
+ ///
@override
Future disconnect(NearbyDevice device) async {
_requireIOSDevice(device);
@@ -95,12 +152,16 @@ class NearbyIOSService extends NearbyService {
return result;
}
+ ///
+ /// Starts listening for messages from device with
+ /// [NearbyCommunicationChannelData.connectedDeviceId].
+ ///
@override
FutureOr startCommunicationChannel(
NearbyCommunicationChannelData data,
) async {
Logger.debug('Creating messages subscription');
- _isCommunicationChannelConnecting.value = true;
+ _state.value = CommunicationChannelState.loading;
await endCommunicationChannel();
final eventListener = data.eventListener;
_messagesSubscription = NearbyServiceIOSPlatform.instance.messagesStream
@@ -110,9 +171,13 @@ class NearbyIOSService extends NearbyService {
.cast()
.listen(
eventListener.onData,
- onDone: eventListener.onDone,
+ onDone: () {
+ _state.value = CommunicationChannelState.notConnected;
+ eventListener.onDone?.call();
+ },
onError: (e, s) {
Logger.error(e);
+ _state.value = CommunicationChannelState.notConnected;
eventListener.onError?.call(e, s);
},
cancelOnError: eventListener.cancelOnError,
@@ -120,11 +185,18 @@ class NearbyIOSService extends NearbyService {
if (_messagesSubscription != null) {
Logger.info('Messages subscription was created successfully');
eventListener.onCreated?.call(_messagesSubscription!);
+ _state.value = CommunicationChannelState.connected;
+ } else {
+ _state.value = CommunicationChannelState.notConnected;
}
- _isCommunicationChannelConnecting.value = false;
+
return true;
}
+ ///
+ /// Stops listening for messages from previously passed device with
+ /// [NearbyCommunicationChannelData.connectedDeviceId].
+ ///
@override
FutureOr endCommunicationChannel() async {
await _messagesSubscription?.cancel();
@@ -133,15 +205,30 @@ class NearbyIOSService extends NearbyService {
return true;
}
+ ///
+ /// Sends [OutgoingNearbyMessage] to [OutgoingNearbyMessage.receiver] via
+ /// IOS platform.
+ ///
@override
Future send(OutgoingNearbyMessage message) {
return NearbyServiceIOSPlatform.instance.send(message);
}
+ ///
+ /// If you want to ask the user to change the name on the network,
+ /// you can retrieve the name previously saved in
+ /// [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults) using this method.
+ ///
+ /// Changing the name on the network is only available for IOS,
+ /// so the [NearbyIOSService] only can be used for that.
+ ///
Future getSavedDeviceName() {
return NearbyServiceIOSPlatform.instance.getSavedDeviceName();
}
+ ///
+ /// Changes the [isBrowser] to the passed [value].
+ ///
void setIsBrowser({required bool value}) {
Logger.debug('Is Browser Value was set to $value');
_isBrowser.value = value;
diff --git a/lib/src/types/communication_channel_data.dart b/lib/src/types/communication_channel_data.dart
index 1af136a..e76719e 100644
--- a/lib/src/types/communication_channel_data.dart
+++ b/lib/src/types/communication_channel_data.dart
@@ -3,26 +3,102 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:nearby_service/nearby_service.dart';
-class NearbyCommunicationChannelData {
+///
+/// A class for creating a communication channel.
+///
+class NearbyCommunicationChannelData {
+ ///
+ /// Contains [connectedDeviceId] of the device to be connected
+ /// to and additional data.
+ ///
+ /// Since Android connection is more customizable,
+ /// additional data [androidData] is created for it.
+ ///
const NearbyCommunicationChannelData(
this.connectedDeviceId, {
required this.eventListener,
this.androidData = const NearbyAndroidCommunicationChannelData(),
});
+ ///
+ /// Identifier of the device to be connected to.
+ ///
final String connectedDeviceId;
+
+ ///
+ /// Listener for message stream changes.
+ ///
final NearbyServiceStreamListener eventListener;
+
+ ///
+ /// Android-specific connection data.
+ ///
final NearbyAndroidCommunicationChannelData androidData;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyCommunicationChannelData &&
+ runtimeType == other.runtimeType &&
+ connectedDeviceId == other.connectedDeviceId &&
+ eventListener == other.eventListener &&
+ androidData == other.androidData;
+
+ @override
+ int get hashCode =>
+ connectedDeviceId.hashCode ^
+ eventListener.hashCode ^
+ androidData.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyCommunicationChannelData{connectedDeviceId: $connectedDeviceId, eventListener: $eventListener, androidData: $androidData}';
+ }
}
+///
+/// Android-specific connection data.
+///
class NearbyAndroidCommunicationChannelData {
+ ///
+ /// By default, no data needs to be passed, all values are already set.
+ /// This class is used to customize connection of server to client or client to server.
+ ///
const NearbyAndroidCommunicationChannelData({
this.clientReconnectInterval = const Duration(seconds: 5),
this.serverListener,
this.port = 4045,
});
+ ///
+ /// The interval at which the client will ping the server
+ /// while waiting for it to be created.
+ ///
final Duration clientReconnectInterval;
+
+ ///
+ /// Listener of events that come to the server.
+ ///
final ValueChanged? serverListener;
final int port;
+
+ @override
+ bool operator ==(Object other) =>
+ identical(this, other) ||
+ other is NearbyAndroidCommunicationChannelData &&
+ runtimeType == other.runtimeType &&
+ clientReconnectInterval == other.clientReconnectInterval &&
+ serverListener == other.serverListener &&
+ port == other.port;
+
+ @override
+ int get hashCode =>
+ clientReconnectInterval.hashCode ^
+ serverListener.hashCode ^
+ port.hashCode;
+
+ @override
+ String toString() {
+ return 'NearbyAndroidCommunicationChannelData{clientReconnectInterval: $clientReconnectInterval, serverListener: $serverListener, port: $port}';
+ }
}
diff --git a/lib/src/types/initialize_data.dart b/lib/src/types/initialize_data.dart
index 3f82e9a..fedd4bd 100644
--- a/lib/src/types/initialize_data.dart
+++ b/lib/src/types/initialize_data.dart
@@ -1,5 +1,17 @@
+///
+/// Data for plugin initialization.
+///
class NearbyInitializeData {
+ ///
+ /// By default, it does not require any data.
+ ///
+ /// There is an option to pass the device name to IOS [iosDeviceName].
+ /// For Android platform changing device name in P2P network is not supported.
+ ///
const NearbyInitializeData({this.iosDeviceName});
+ ///
+ /// The device name for IOS on the P2P network.
+ ///
final String? iosDeviceName;
}
diff --git a/lib/src/types/nearby_service_stream_listener.dart b/lib/src/types/nearby_service_stream_listener.dart
index 89c0c64..4ce1272 100644
--- a/lib/src/types/nearby_service_stream_listener.dart
+++ b/lib/src/types/nearby_service_stream_listener.dart
@@ -2,7 +2,14 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
+///
+/// Stream Subscription Listener.
+///
class NearbyServiceStreamListener {
+ ///
+ /// It is required to pass the [onData] parameter to process the
+ /// data that came through the stream.
+ ///
const NearbyServiceStreamListener({
required this.onData,
this.onCreated,
diff --git a/lib/src/utils/exception.dart b/lib/src/utils/exception.dart
index fee02f7..788ce6f 100644
--- a/lib/src/utils/exception.dart
+++ b/lib/src/utils/exception.dart
@@ -2,17 +2,28 @@ import 'dart:io';
import 'package:nearby_service/src/utils/logger.dart';
+///
+/// Nearby Service Plugin Exception.
+///
+/// Indicates what problem occurred in the plugin operation.
+///
class NearbyServiceException implements Exception {
NearbyServiceException(this.error) {
Logger.error(error);
}
+ ///
+ /// A call from an unsupported platform.
+ ///
factory NearbyServiceException.unsupportedPlatform({required String caller}) {
return NearbyServiceException(
'$caller is not supported for platform ${Platform.operatingSystem}',
);
}
+ ///
+ /// A decoding error.
+ ///
factory NearbyServiceException.unsupportedDecoding(dynamic value) {
return NearbyServiceException(
'Got unknown value=$value with runtimeType=${value.runtimeType}',
diff --git a/lib/src/utils/decoder.dart b/lib/src/utils/json_decoder.dart
similarity index 94%
rename from lib/src/utils/decoder.dart
rename to lib/src/utils/json_decoder.dart
index 5230833..3143892 100644
--- a/lib/src/utils/decoder.dart
+++ b/lib/src/utils/json_decoder.dart
@@ -2,8 +2,8 @@ import 'dart:convert';
import 'package:nearby_service/nearby_service.dart';
-class Decoder {
- Decoder._();
+class JSONDecoder {
+ JSONDecoder._();
static Map? decodeMap(dynamic value) {
if (value == null) {
diff --git a/lib/src/utils/log_level.dart b/lib/src/utils/log_level.dart
index ea112db..e719557 100644
--- a/lib/src/utils/log_level.dart
+++ b/lib/src/utils/log_level.dart
@@ -1,3 +1,11 @@
+///
+/// Determines what level of logging the plugin will use.
+///
+/// * [debug] - Display all logs, including debugging operations logs.
+/// * [info] - Display information logs and errors.
+/// * [error] - Display only errors.
+/// * [disabled] - Do not display any logs.
+///
enum NearbyServiceLogLevel {
debug,
info,
diff --git a/lib/src/utils/stream_mapper.dart b/lib/src/utils/stream_mapper.dart
index 4d437ec..51096a7 100644
--- a/lib/src/utils/stream_mapper.dart
+++ b/lib/src/utils/stream_mapper.dart
@@ -1,5 +1,5 @@
import 'package:nearby_service/nearby_service.dart';
-import 'package:nearby_service/src/utils/decoder.dart';
+import 'package:nearby_service/src/utils/json_decoder.dart';
import 'logger.dart';
@@ -19,7 +19,7 @@ abstract class MessagesStreamMapper {
static ReceivedNearbyMessage? toMessage(dynamic event) {
try {
- final decoded = Decoder.decodeMap(event);
+ final decoded = JSONDecoder.decodeMap(event);
return ReceivedNearbyMessage.fromJson(decoded);
} catch (e) {
Logger.debug(
diff --git a/lib/src/utils/unknown.dart b/lib/src/utils/unknown.dart
new file mode 100644
index 0000000..aa77eea
--- /dev/null
+++ b/lib/src/utils/unknown.dart
@@ -0,0 +1 @@
+const kNearbyUnknown = 'unknown';
diff --git a/test/nearby_service_test.dart b/test/nearby_service_test.dart
index 60a0ae4..c089ba4 100644
--- a/test/nearby_service_test.dart
+++ b/test/nearby_service_test.dart
@@ -50,7 +50,7 @@ class MockNearbyServicePlatform
}
@override
- Future getCurrentDevice() {
+ Future getCurrentDeviceInfo() {
// TODO: implement getCurrentDevice
throw UnimplementedError();
}