flutter_peerlink_plugin 0.0.1
flutter_peerlink_plugin: ^0.0.1 copied to clipboard
A Flutter plugin for peer-to-peer data transfer in android and iOS.
Flutter PeerLink Plugin #
A Flutter plugin for peer-to-peer communication between nearby devices using platform-native APIs. Supports both Android (Nearby Connections API) and iOS (MultipeerConnectivity framework) for local network discovery, connections, and data transfer.
Features #
✅ Device Discovery - Discover nearby devices on the same network ✅ Advertising - Make your device discoverable to others ✅ Peer-to-Peer Connections - Connect directly to discovered devices ✅ Data Transfer - Send bytes and streaming data between devices ✅ Connection Strategies - Support for Star, Point-to-Point, and Cluster topologies ✅ Thread-Safe - Efficient background processing for large data transfers ✅ Stream Protocol - Custom chunked streaming for large file transfers ✅ Event-Based - Reactive streams for device discovery and data reception
Platform Support #
| Platform | API | Version |
|---|---|---|
| Android | Nearby Connections | Android 5.0+ (API 21+) |
| iOS | MultipeerConnectivity | iOS 13.0+ |
⚠️ Important: Android and iOS devices cannot discover or connect to each other. They use different underlying protocols (Nearby Connections vs MultipeerConnectivity).
Installation #
Add this to your pubspec.yaml:
dependencies:
flutter_peerlink_plugin: ^0.0.1
Or install from your local repository:
dependencies:
flutter_peerlink_plugin:
path: ../flutter_peerlink_plugin
Then run:
flutter pub get
Platform Setup #
Android Setup #
1. Update AndroidManifest.xml
Add required permissions in android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- Android 12+ (API 31+) Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- WiFi permissions -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- Location permissions (required for Nearby Connections) -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- Android 10+ (API 29+) -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Android 13+ (API 33+) Nearby WiFi Devices -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<!-- Foreground service permission -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<application>
<!-- Your app content -->
<!-- Foreground service for background connections -->
<service
android:name="com.peerlink.flutter_peerlink_plugin.service.PeerlinkForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="connectedDevice" />
</application>
</manifest>
2. Update build.gradle
Ensure minimum SDK version in android/app/build.gradle:
android {
defaultConfig {
minSdkVersion 21 // Android 5.0+
targetSdkVersion 34
}
}
3. Google Play Services
The plugin uses Google Play Services Nearby Connections API. Most devices have this pre-installed, but you may need to ensure Play Services are up to date on test devices.
iOS Setup #
1. Update Info.plist
Add required permissions and configurations in ios/Runner/Info.plist:
<dict>
<!-- Bluetooth Usage Description -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to discover and connect to nearby devices</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to discover and connect to nearby devices</string>
<!-- Local Network Usage Description (iOS 14+) -->
<key>NSLocalNetworkUsageDescription</key>
<string>This app uses the local network to discover nearby devices</string>
<!-- Bonjour Services (required for MultipeerConnectivity) -->
<key>NSBonjourServices</key>
<array>
<!-- Replace with your service type (max 15 chars) -->
<string>_peerlink._tcp</string>
</array>
<!-- Background Modes (optional, for background connections) -->
<key>UIBackgroundModes</key>
<array>
<string>nearby-interaction</string>
</array>
</dict>
⚠️ Important: Update the NSBonjourServices array with your actual service type used in initialize(). iOS requires the service type to be:
- 1-15 characters
- Lowercase alphanumeric and hyphens only
- Format:
_servicename._tcp
2. Deployment Target
Ensure minimum iOS version in ios/Podfile:
platform :ios, '13.0'
3. Service Type Limitations
iOS has strict service type requirements:
- Maximum 15 characters
- Lowercase letters, numbers, and hyphens only
- The plugin automatically sanitizes Android-style service IDs
Example conversions:
my_app_service→my-app-service✅MyAppServiceWithLongName→myappservicewi✅ (truncated)com.example.app→com-example-ap✅ (sanitized)
Usage #
1. Initialize the Plugin #
import 'package:flutter_peerlink_plugin/flutter_peerlink_plugin.dart';
final plugin = FlutterPeerlinkPlugin.instance;
await plugin.initialize(
serviceId: 'my-app', // Use short, simple names (iOS: max 15 chars)
deviceName: 'My Device',
strategy: ConnectionStrategy.cluster, // or .star, .pointToPoint
autoAcceptOutgoingConnections: true,
);
2. Request Permissions #
// Check permissions
bool hasPermissions = await plugin.checkPermissions();
if (!hasPermissions) {
// Request permissions
bool granted = await plugin.requestPermissions();
if (!granted) {
print('Permissions denied');
return;
}
}
3. Start Discovery & Advertising #
// Start advertising (make this device discoverable)
await plugin.startAdvertising();
// Start discovering nearby devices
await plugin.startDiscovery();
// Listen for discovered devices
plugin.discoveryStream.listen((devices) {
print('Discovered ${devices.length} devices');
for (var device in devices) {
print('- ${device.name} (${device.id})');
}
});
4. Connect to a Device #
// Connect to a discovered device
await plugin.connect(device.id);
// Listen for connection changes
plugin.connectionStream.listen((connections) {
for (var conn in connections) {
if (conn.state == LinkedDeviceState.connected) {
print('Connected to ${conn.name}');
} else if (conn.state is LinkedDeviceStateFailed) {
print('Connection failed: ${(conn.state as LinkedDeviceStateFailed).errorCode}');
}
}
});
5. Accept/Reject Incoming Connections #
// Automatically accept outgoing connections (set in initialize)
// For incoming connections, manually accept/reject:
plugin.connectionStream.listen((connections) {
for (var conn in connections) {
if (conn.isIncoming && conn.state == LinkedDeviceState.connecting) {
// User decides to accept or reject
await plugin.acceptConnection(conn.id);
// or
await plugin.rejectConnection(conn.id);
}
}
});
6. Send Bytes (Simple Data) #
// Send small data payloads
final data = Uint8List.fromList([1, 2, 3, 4, 5]);
final payloadId = await plugin.sendBytes(device.id, data);
print('Sent bytes with payloadId: $payloadId');
// Receive bytes on the other device
plugin.onBytePayload.listen((payload) {
print('Received ${payload.chunk.length} bytes from ${payload.deviceId}');
});
7. Stream Large Data #
For large data transfers (files, media, etc.), use streaming:
// Start a stream
final payloadId = await plugin.startStream(device.id);
// Send data in chunks
const chunkSize = 8192; // 8 KB chunks
for (int i = 0; i < largeData.length; i += chunkSize) {
final end = (i + chunkSize < largeData.length) ? i + chunkSize : largeData.length;
final chunk = largeData.sublist(i, end);
await plugin.sendChunk(payloadId, chunk);
}
// Finish the stream
await plugin.finishStream(payloadId);
// Or cancel if needed
await plugin.cancelStream(payloadId);
8. Receive Streams #
plugin.onStreamPayload.listen((payload) {
if (payload.isIdle) {
print('Stream started: ${payload.id}');
// Initialize buffer, open file, etc.
} else if (payload.isActive) {
print('Received chunk: ${payload.chunk.length} bytes');
// Append to buffer, write to file, etc.
} else if (payload.isCompleted) {
print('Stream completed: ${payload.id}');
// Finalize file, close buffer, etc.
} else if (payload.isCanceled) {
print('Stream canceled: ${payload.id}');
} else if (payload.isFailed) {
print('Stream failed: ${payload.id}');
}
});
9. Cleanup #
// Stop discovery/advertising
await plugin.stopDiscovery();
await plugin.stopAdvertising();
// Disconnect from a device
await plugin.disconnect(device.id);
// Dispose the plugin
plugin.dispose();
Connection Strategies #
The plugin supports three connection strategies:
Cluster (Default) #
ConnectionStrategy.cluster
- All-to-all mesh network
- Best for: Group chat, collaborative apps
- Max peers: 8 devices
Star #
ConnectionStrategy.star
- One central hub, others connect to it
- Best for: Multiplayer games with a host
- Max peers: 8 devices
Point-to-Point #
ConnectionStrategy.pointToPoint
- Direct 1-to-1 connection
- Best for: File transfers, private chat
- Max peers: 1 device
Foreground Service (Android Only) #
To keep connections alive in the background on Android:
// Start foreground service
await plugin.startForegroundService(
title: 'PeerLink Active',
message: 'Connected to nearby devices',
);
// Stop foreground service
await plugin.stopForegroundService();
Note: Foreground service is not supported on iOS. iOS requires background modes in Info.plist, but connections may still drop when the app is backgrounded.
API Reference #
Methods #
| Method | Description | Returns |
|---|---|---|
initialize(...) |
Initialize the plugin | Future<void> |
dispose() |
Clean up resources | void |
checkPermissions() |
Check if permissions granted | Future<bool> |
requestPermissions() |
Request required permissions | Future<bool> |
startAdvertising() |
Start advertising | Future<void> |
stopAdvertising() |
Stop advertising | Future<void> |
startDiscovery() |
Start discovering devices | Future<void> |
stopDiscovery() |
Stop discovering | Future<void> |
connect(deviceId) |
Connect to device | Future<void> |
acceptConnection(deviceId) |
Accept incoming connection | Future<void> |
rejectConnection(deviceId) |
Reject incoming connection | Future<void> |
disconnect(deviceId) |
Disconnect from device | Future<void> |
sendBytes(deviceId, bytes) |
Send bytes payload | Future<int> (payloadId) |
startStream(deviceId) |
Start stream transfer | Future<int> (payloadId) |
sendChunk(payloadId, chunk) |
Send stream chunk | Future<void> |
finishStream(payloadId) |
Finish stream normally | Future<void> |
cancelStream(payloadId) |
Cancel stream transfer | Future<void> |
startForegroundService(...) |
Start foreground service (Android) | Future<void> |
stopForegroundService() |
Stop foreground service (Android) | Future<void> |
Properties #
| Property | Type | Description |
|---|---|---|
discoveredDevices |
List<PeerDevice> |
Current discovered devices |
connectedDevices |
List<LinkedDevice> |
Current connections |
discoveryStream |
Stream<List<PeerDevice>> |
Device discovery events |
connectionStream |
Stream<List<LinkedDevice>> |
Connection state events |
onBytePayload |
Stream<BytePayload> |
Incoming bytes data |
onStreamPayload |
Stream<StreamPayload> |
Incoming stream data |
Models #
PeerDevice
class PeerDevice {
final String id; // Device identifier
final String name; // Device name
}
LinkedDevice
class LinkedDevice {
final String id; // Device identifier
final String name; // Device name
final LinkedDeviceState state; // Connection state
final bool isIncoming; // True if remote initiated
}
LinkedDeviceState
enum LinkedDeviceState {
disconnected,
connecting,
connected,
failed(String errorCode),
}
BytePayload
class BytePayload {
final String deviceId; // Sender device ID
final Uint8List chunk; // Data received
}
StreamPayload
class StreamPayload {
final int id; // Stream payloadId
final String deviceId; // Sender device ID
final Uint8List chunk; // Data chunk (empty for state changes)
final bool isCompleted; // Stream finished successfully
final bool isCanceled; // Stream was canceled
final bool isFailed; // Stream failed
}
Platform Differences #
| Feature | Android | iOS |
|---|---|---|
| Discovery Protocol | Google Nearby Connections | MultipeerConnectivity |
| Service ID | Flexible string | Max 15 chars, alphanumeric + hyphen |
| Cross-Platform | ❌ No | ❌ No |
| Foreground Service | ✅ Yes | ❌ No (returns ERROR) |
| Background Connections | ✅ Reliable | ⚠️ Limited |
| Auto-Chunking | ✅ Yes | Custom implementation |
| Max Peers | 8 | 8 |
Android-Specific Notes #
- Requires Google Play Services
- Location permission required (even though not used for location)
- Foreground service keeps connections alive in background
- Works over Bluetooth and WiFi Direct
iOS-Specific Notes #
- Service type automatically sanitized from Android format
- Foreground service not supported (use background modes)
- Connections may drop when app backgrounds
- Works over Bluetooth LE and WiFi Direct
- Requires
NSBonjourServicesin Info.plist
Troubleshooting #
Android #
Problem: Discovery not working Solution:
- Ensure all permissions granted (especially location)
- Check Bluetooth and WiFi are enabled
- Verify Google Play Services are installed and up to date
Problem: Connections drop in background Solution:
- Use
startForegroundService()to keep connections alive
iOS #
Problem: "Service type invalid" error Solution:
- Service ID must be max 15 characters
- Only lowercase alphanumeric and hyphens allowed
- The plugin auto-sanitizes, but very long IDs get truncated
Problem: Permission prompts not appearing Solution:
- Add all required keys to Info.plist
- Ensure descriptions are user-friendly
- Check
NSBonjourServicesincludes your service type
Problem: Devices not discovering each other Solution:
- Ensure both devices use the same service ID
- Check WiFi and Bluetooth are enabled
- Verify both devices on same network (for WiFi Direct)
Example App #
See the example/ directory for a complete demo app that shows:
- Device discovery and advertising
- Connection management
- Bytes and stream transfers
- Permission handling
- Progress tracking for large transfers
Run the example:
cd example
flutter run
Security Considerations #
- Encryption: Both platforms use encrypted connections (TLS on Android, built-in on iOS)
- Authentication: No built-in authentication - implement your own handshake
- Validation: Always validate received data before use
- Privacy: Avoid including sensitive data in device names or service IDs
Limitations #
- No Cross-Platform: Android ↔ iOS devices cannot communicate
- Network Required: Devices must be on same network or within Bluetooth range
- Max Peers: Limited to 8 simultaneous connections
- Background (iOS): Connections may drop when app backgrounds
- Service ID (iOS): Max 15 characters
Contributing #
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Support #
- Issues: GitHub Issues
- Repository: GitHub Repository
Acknowledgments #
- Android implementation uses Google Nearby Connections API
- iOS implementation uses Apple MultipeerConnectivity Framework