nearby_connections/lib/nearby_connections.dart

401 lines
12 KiB
Dart
Raw Normal View History

2019-05-10 06:54:05 +00:00
import 'dart:async';
2019-05-11 19:13:34 +00:00
import 'dart:typed_data';
2019-05-10 06:54:05 +00:00
import 'package:flutter/foundation.dart';
2019-05-10 06:54:05 +00:00
import 'package:flutter/services.dart';
2019-08-06 15:23:37 +00:00
/// **P2P_CLUSTER** - best for small payloads and multiplayer games
2019-08-08 08:57:10 +00:00
///
2019-08-06 15:23:37 +00:00
/// **P2P_STAR** - best for medium payloads, higher bandwidth than cluster
2019-08-08 08:57:10 +00:00
///
2019-08-06 15:23:37 +00:00
/// **P2P_POINT_TO_POINT** - single connection, very high bandwidth
enum Strategy { P2P_CLUSTER, P2P_STAR, P2P_POINT_TO_POINT }
2019-05-11 08:34:12 +00:00
enum Status { CONNECTED, REJECTED, ERROR }
2019-08-08 08:57:10 +00:00
enum PayloadStatus { NONE, SUCCESS, FAILURE, IN_PROGRRESS, CANCELED }
enum PayloadType { NONE, BYTES, FILES, STREAM }
typedef void OnConnctionInitiated(
String endpointId, ConnectionInfo connectionInfo);
typedef void OnConnectionResult(String endpointId, Status status);
typedef void OnDisconnected(String endpointId);
typedef void OnEndpointFound(
String endpointId, String endpointName, String serviceId);
typedef void OnEndpointLost(String endpointId);
2019-08-08 08:57:10 +00:00
/// For Bytes, this contains the bytes dala
///
/// For File, this marks the start of transfer
///
/// Uint8List bytes may be null, if [payloadType] is not [PayloadType.BYTES]
typedef void OnPayloadReceived(
String endpointId, Uint8List bytes, PayloadType payloadType);
/// Called only once for Bytes and repeatedly for File until transfer is complete
typedef void OnPayloadTransferUpdate(
{String endpointId,
int payloadId,
PayloadStatus payloadStatus,
int bytesTransferred,
int totalBytes});
2019-08-06 15:23:37 +00:00
// typedef void OnPayloadTransferUpdate();
2019-05-15 08:13:10 +00:00
/// The NearbyConnection class
///
/// Only one instance is maintained
/// even on calling Nearby() multiple times
///
/// All methods are asynchronous.
2019-05-10 12:54:38 +00:00
class Nearby {
//for maintaining only 1 instance of this class
2019-05-11 08:34:12 +00:00
static Nearby _instance;
factory Nearby() {
if (_instance == null) {
_instance = Nearby._();
}
return _instance;
}
Nearby._() {
_channel.setMethodCallHandler((handler) {
2019-05-11 19:13:34 +00:00
print("=========in handler============");
2019-05-11 18:12:35 +00:00
Map<dynamic, dynamic> args = handler.arguments;
2019-05-11 08:34:12 +00:00
print(handler.method);
args.forEach((s, d) {
print(s + " : " + d.toString());
});
print("=====================");
switch (handler.method) {
case "ad.onConnectionInitiated":
String endpointId = args['endpointId'];
String endpointName = args['endpointName'];
String authenticationToken = args['authenticationToken'];
bool isIncomingConnection = args['isIncomingConnection'];
_advertConnectionInitiated?.call(
endpointId,
ConnectionInfo(
endpointName, authenticationToken, isIncomingConnection));
2019-05-11 08:34:12 +00:00
return null;
case "ad.onConnectionResult":
String endpointId = args['endpointId'];
Status statusCode = Status.values[args['statusCode']];
_advertConnectionResult?.call(endpointId, statusCode);
2019-05-11 08:34:12 +00:00
return null;
case "ad.onDisconnected":
String endpointId = args['endpointId'];
_advertDisconnected?.call(endpointId);
return null;
case "dis.onConnectionInitiated":
String endpointId = args['endpointId'];
String endpointName = args['endpointName'];
String authenticationToken = args['authenticationToken'];
bool isIncomingConnection = args['isIncomingConnection'];
_discoverConnectionInitiated?.call(
endpointId,
ConnectionInfo(
endpointName, authenticationToken, isIncomingConnection));
return null;
case "dis.onConnectionResult":
String endpointId = args['endpointId'];
Status statusCode = Status.values[args['statusCode']];
_discoverConnectionResult?.call(endpointId, statusCode);
return null;
case "dis.onDisconnected":
String endpointId = args['endpointId'];
_discoverDisconnected?.call(endpointId);
2019-05-11 08:34:12 +00:00
return null;
case "dis.onEndpointFound":
2019-05-11 18:12:35 +00:00
print("in switch");
2019-05-11 08:34:12 +00:00
String endpointId = args['endpointId'];
String endpointName = args['endpointName'];
String serviceId = args['serviceId'];
_onEndpointFound?.call(endpointId, endpointName, serviceId);
2019-05-11 08:34:12 +00:00
return null;
case "dis.onEndpointLost":
String endpointId = args['endpointId'];
_onEndpointLost?.call(endpointId);
2019-05-11 08:34:12 +00:00
return null;
2019-05-11 19:13:34 +00:00
case "onPayloadReceived":
String endpointId = args['endpointId'];
2019-08-08 08:57:10 +00:00
int type = args['type'];
2019-05-11 19:13:34 +00:00
Uint8List bytes = args['bytes'];
2019-08-08 08:57:10 +00:00
_onPayloadReceived?.call(endpointId, bytes, PayloadType.values[type]);
2019-05-11 19:13:34 +00:00
2019-08-08 08:57:10 +00:00
break;
case "onPayloadTransferUpdate":
String endpointId = args['endpointId'];
int payloadId = args['payloadId'];
int success = args['success'];
int bytesTransferred = args['bytesTransferred'];
int totalBytes = args['totalBytes'];
_onPayloadTransferUpdate?.call(
endpointId: endpointId,
payloadId: payloadId,
payloadStatus: PayloadStatus.values[success],
bytesTransferred: bytesTransferred,
totalBytes: totalBytes,
);
2019-05-11 19:13:34 +00:00
break;
2019-05-11 08:34:12 +00:00
}
2019-07-15 13:51:22 +00:00
return null;
2019-05-11 08:34:12 +00:00
});
}
2019-05-10 12:54:38 +00:00
OnConnctionInitiated _advertConnectionInitiated, _discoverConnectionInitiated;
OnConnectionResult _advertConnectionResult, _discoverConnectionResult;
OnDisconnected _advertDisconnected, _discoverDisconnected;
OnEndpointFound _onEndpointFound;
OnEndpointLost _onEndpointLost;
2019-05-11 19:13:34 +00:00
OnPayloadReceived _onPayloadReceived;
2019-08-08 08:57:10 +00:00
OnPayloadTransferUpdate _onPayloadTransferUpdate;
2019-05-11 19:13:34 +00:00
2019-05-10 06:54:05 +00:00
static const MethodChannel _channel =
const MethodChannel('nearby_connections');
2019-05-15 08:13:10 +00:00
/// Convinience method
///
/// retruns true/false based on location permissions.
/// Discovery cannot be started with insufficient permission
2019-05-10 12:54:38 +00:00
Future<bool> checkPermissions() async => await _channel.invokeMethod(
'checkPermissions',
);
2019-05-15 08:13:10 +00:00
/// Convinience method
///
/// Asks location permission
Future<void> askPermission() async {
await _channel.invokeMethod(
'askPermissions',
);
}
2019-05-15 08:13:10 +00:00
/// Start Advertising
///
/// [userNickName] and [strategy] should not be null
Future<bool> startAdvertising(
String userNickName,
Strategy strategy, {
@required OnConnctionInitiated onConnectionInitiated,
@required OnConnectionResult onConnectionResult,
@required OnDisconnected onDisconnected,
}) async {
assert(userNickName != null && strategy != null);
this._advertConnectionInitiated = onConnectionInitiated;
this._advertConnectionResult = onConnectionResult;
this._advertDisconnected = onDisconnected;
return await _channel.invokeMethod('startAdvertising', <String, dynamic>{
'userNickName': userNickName,
'strategy': strategy.index
});
}
2019-05-15 08:13:10 +00:00
/// Stop Advertising
///
/// This doesn't disconnect from any connected Endpoint
///
/// For disconnection use
/// [stopAllEndpoints] or [disconnectFromEndpoint]
Future<void> stopAdvertising() async {
await _channel.invokeMethod('stopAdvertising');
}
2019-05-11 08:34:12 +00:00
2019-05-15 08:13:10 +00:00
/// Start Discovery
///
/// [userNickName] and [strategy] should not be null
2019-05-11 08:34:12 +00:00
Future<bool> startDiscovery(
String userNickName,
Strategy strategy, {
@required OnEndpointFound onEndpointFound,
@required OnEndpointLost onEndpointLost,
}) async {
2019-05-11 08:34:12 +00:00
assert(userNickName != null && strategy != null);
2019-05-11 18:12:35 +00:00
this._onEndpointFound = onEndpointFound;
this._onEndpointLost = onEndpointLost;
2019-05-11 08:34:12 +00:00
return await _channel.invokeMethod('startDiscovery', <String, dynamic>{
'userNickName': userNickName,
'strategy': strategy.index
});
}
2019-05-15 08:13:10 +00:00
/// Stop Discovery
///
/// This doesn't disconnect from any connected Endpoint
///
/// It is reccomended to call this method
/// once you have connected to an endPoint
/// as it uses heavy radio operations
/// which may affect connection speed and integrity
2019-05-11 08:34:12 +00:00
Future<void> stopDiscovery() async {
await _channel.invokeMethod('stopDiscovery');
}
2019-05-15 08:13:10 +00:00
/// Stop All Endpoints
///
/// Disconnects all connections,
/// this will call the onDisconnected method on callbacks of
/// all connected endPoints
Future<void> stopAllEndpoints() async {
await _channel.invokeMethod('stopAllEndpoints');
}
2019-05-15 08:13:10 +00:00
/// Disconnect from Endpoints
///
/// Disconnects the connections to given endPointId
/// this will call the onDisconnected method on callbacks of
/// connected endPoint
Future<void> disconnectFromEndpoint(String endpointId) async {
await _channel.invokeMethod(
'disconnectFromEndpoint', <String, dynamic>{'endpointId': endpointId});
}
2019-05-15 08:13:10 +00:00
/// Request Connection
///
/// Call this method when Discoverer calls the
/// [OnEndpointFound] method
///
/// This will call the [OnConnctionInitiated] method on
/// both the endPoint and this
Future<bool> requestConnection(
String userNickName,
String endpointId, {
@required OnConnctionInitiated onConnectionInitiated,
@required OnConnectionResult onConnectionResult,
@required OnDisconnected onDisconnected,
}) async {
this._discoverConnectionInitiated = onConnectionInitiated;
this._discoverConnectionResult = onConnectionResult;
this._discoverDisconnected = onDisconnected;
return await _channel.invokeMethod(
'requestConnection',
<String, dynamic>{
'userNickName': userNickName,
'endpointId': endpointId,
},
);
}
2019-05-15 08:13:10 +00:00
/// Accept Connection
///
/// Needs be called by both discoverer and advertiser
/// to connect
///
/// Call this in [OnConnctionInitiated]
/// to accept an incoming connection
2019-07-15 13:51:22 +00:00
///
/// [OnConnectionResult] is called on both
2019-05-15 08:13:10 +00:00
/// only if both of them accept the connection
2019-05-11 19:13:34 +00:00
Future<bool> acceptConnection(
String endpointId, {
@required OnPayloadReceived onPayLoadRecieved,
2019-08-08 08:57:10 +00:00
OnPayloadTransferUpdate onPayloadTransferUpdate,
2019-05-11 19:13:34 +00:00
}) async {
this._onPayloadReceived = onPayLoadRecieved;
2019-08-08 08:57:10 +00:00
this._onPayloadTransferUpdate = onPayloadTransferUpdate;
return await _channel.invokeMethod(
'acceptConnection',
<String, dynamic>{
'endpointId': endpointId,
},
);
}
2019-05-15 08:13:10 +00:00
/// Reject Connection
///
/// To be called by both discoverer and advertiser
///
/// Call this in [OnConnctionInitiated]
/// to reject an incoming connection
2019-07-15 13:51:22 +00:00
///
/// [OnConnectionResult] is called on both
/// even if one of them rejects the connection
Future<bool> rejectConnection(String endpointId) async {
return await _channel.invokeMethod(
2019-05-11 20:00:07 +00:00
'rejectConnection',
<String, dynamic>{
'endpointId': endpointId,
},
);
}
2019-05-11 19:13:34 +00:00
2019-05-15 08:13:10 +00:00
/// Send bytes [Uint8List] payload to endpoint
2019-07-15 13:51:22 +00:00
///
2019-05-15 08:13:10 +00:00
/// Convert String to Uint8List as follows
2019-07-15 13:51:22 +00:00
///
2019-05-15 08:13:10 +00:00
/// ```dart
/// String a = "hello";
/// Uint8List bytes = Uint8List.fromList(a.codeUnits);
/// ```
2019-05-11 19:13:34 +00:00
Future<void> sendPayload(String endpointId, Uint8List bytes) async {
return await _channel.invokeMethod(
'sendPayload',
<String, dynamic>{
'endpointId': endpointId,
'bytes': bytes,
},
);
}
2019-08-08 08:57:10 +00:00
/// Returns the payloadID as soon as file transfer has begun
///
/// File is received in DOWNLOADS_DIRECTORY and is given a generic name
/// without extension
/// You must also send a bytes payload to send the filename and extension
/// so that receiver can rename the file accordingly
/// Send the payloadID and filename to receiver as bytes payload
Future<int> sendFilePayload(String endpointId, String filePath) async {
return await _channel.invokeMethod(
'sendFilePayload',
<String, dynamic>{
'endpointId': endpointId,
'filePath': filePath,
},
);
}
}
2019-05-15 08:13:10 +00:00
/// ConnectionInfo class
2019-07-15 13:51:22 +00:00
///
2019-05-15 08:13:10 +00:00
/// Its a parameter in [OnConnctionInitiated]
2019-07-15 13:51:22 +00:00
///
2019-05-15 08:13:10 +00:00
/// [endPointName] is userNickName of requester
2019-07-15 13:51:22 +00:00
///
2019-08-06 15:23:37 +00:00
/// [authenticationToken] can be used to check the connection security
2019-05-15 08:13:10 +00:00
/// it must be same on both devices
class ConnectionInfo {
String endpointName, authenticationToken;
bool isIncomingConnection;
ConnectionInfo(
this.endpointName, this.authenticationToken, this.isIncomingConnection);
2019-05-10 06:54:05 +00:00
}
2019-05-11 20:00:07 +00:00
//TODO remove errors on failure for smooth experience
//TODO expose only relevant parts as library
//TODO publish to pub.dartlang