Nearby Service는 블루투스, NFC 및 비콘 기술을 사용하여 장치 간 피어 투 피어(P2P) 네트워킹을 가능하게 하는 플러터 플러그인입니다. 인터넷 연결 없이 근처 장치 간에 텍스트 메시지와 파일을 직접 전송할 수 있습니다. 오프라인 환경에서 로컬 실시간 통신에 이상적이며, 안드로이드, iOS 및 macOS 플랫폼을 지원합니다. 단순성을 중시해 설계되었으며, 저수준 연결 세부 정보를 추상화하여 개발자가 앱에 근접 기반 기능을 쉽게 통합할 수 있도록 합니다.
사용 사례
장치 간 로컬 파일 공유
공공 공간에서의 오프라인 메시지
제한된 네트워크에서의 실시간 협업
거리 기반 이벤트 트리거링
사물인터넷 시나리오에서의 장치 탐지
주요 기능
다중 플랫폼 P2P 연결
텍스트 및 파일 전송 지원
블루투스/NFC/비콘 통합
장치 탐색을 위한 간단한 API
안전한 로컬 통신
적합한 대상
오프라인 앱을 개발하는 개발자
협업 도구를 만드는 팀
IoT 애플리케이션 제작자
이벤트 기반 모바일 애플리케이션
안전한 로컬 데이터 교환을 필요로 하는 앱
고려 사항
블루투스/NFC 액세스를 위해 사용자 권한이 필요합니다
성능은 기기 하드웨어에 따라 달라집니다
근처 장치 범위로 제한
인터넷 의존 없이 작동할 경우 신뢰성에 영향을 줄 수 있습니다
플랫폼별 백그라운드 작업 제한
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Nearby Service
Connecting phones in a P2P network
Nearby Service Flutter Plugin is used to create connections in a P2P network.
The plugin supports sending text messages and files. With it,
you can easily create any kind of information sharing application without Internet connection.
The package does not support communication between Android and IOS/macOS (Darwin) devices, the connection is available for
Android-Android and Darwin-Darwin relations.
A peer-to-peer (P2P) network is a decentralized network architecture in which each participant, called a peer, can act
as both a client and a server. This means that peer-to-peer networks can exchange resources such as files, data or
services directly with each other without needing a central authority or server.
About Android Plugin
For Android, Wi-fi Direct is used as a P2P network.
It is implemented through the android.net.wifi.p2p module.
It requires permissions for ACCESS_FINE_LOCATION and NEARBY_WIFI_DEVICES (for Android 13+).
It is also important that you need Wi-fi enabled on your device to use it.
To check permissions and Wi-fi status, the
nearby_service plugin includes a set of methods:
checkWifiService() (Android only): returns true if Wi-fi is enabled
requestPermissions() (Android only): requests permissions for location and nearby devices and returns true if
granted
openServicesSettings(): opens Wi-fi settings for Android and general settings for iOS and macOS
Testing the plugin is not possible on Android emulators, as they usually do not contain the Wi-fi Direct function
in general! Use physical devices for that.
About IOS and macOS Plugin
For IOS and macOS, the P2P connection is implemented through the Multipeer Connectivity framework.
This framework automatically selects the best network technology depending on the situation—using Wi-Fi
if both devices are on the same network, or using peer-to-peer Wi-Fi or Bluetooth otherwise.
The module will work even with Wi-fi turned off (via Bluetooth), but you can still use openServicesSettings() from
the nearby_service plugin to open the settings and prompt the user to turn Wi-fi on.
Features
Device Preparation
Requesting permissions to use Wi-Fi Direct (Android only)
Checking Wi-Fi status (Android only)
Opening settings to enable Wi-Fi (Android and IOS/macOS)
Role selection - Browser or Advertiser (IOS and macOS only)
Connecting to the Device from a P2P Network
Listening for discovered devices (peers)
Creating connections over the P2P network
Monitoring the status of the connected device
The Data Transmission Channel
Establishing the channel for data exchange
Monitoring the status of the channel
Typed Data Exchange
Transmitting text data over the P2P network
Receiving confirmation of successful text message delivery
Transmitting files over the P2P network
The option to confirm or deny receiving of files over the network
Setup
Android setup
All necessary Android permissions are already in the AndroidManifest.xml of the plugin,
so you don't need to add anything to work with p2p network.
IOS and macOS setup
For IOS and macOS, you need to add the following values to Info.plist:
For the nearby_service plugin, you should add the value <string>_mp-connection._tcp</string>. This key is
required for Multipeer Connectivity. It defines the Bonjour services
that your application will look for. Bonjour is Apple's implementation of zero-configuration networking, which allows
devices on a LAN to discover each other and establish connections without requiring manual configuration.
UIRequiresPersistentWiFi
This key is not strictly required for Multipeer Connectivity to work. This key is used
to indicate that your application requires a persistent Wi-Fi connection even when the screen is off. This can be
useful for maintaining a network connection for continuous data transfer.
NSLocalNetworkUsageDescription
This key is needed to inform users why your application will use the local network. You must provide a string value
that explains why your application needs LAN access.
Note that if you want to use the plugin to send files, you also need to follow the instructions about permissions
of the filesystem management package you are using.
Usage
The full example demonstrating the functionality can be viewed at
the link.
Important note: for functionality that is only available for one of the platforms, you should use the
corresponding
API element from NearbyService.
For Android:
final _nearbyService = NearbyService.getInstance();
_nearbyService.android..
For IOS and macOS:
final _nearbyService = NearbyService.getInstance();
_nearbyService.darwin..
// getInstance() returns an instance for the current platform.final _nearbyService = NearbyService.getInstance();
Initialize the plugin:
// You can change the device name on a P2P network only for iOS. // Optionally pass the [darwinDeviceName].await _nearbyService.initialize(
data: NearbyInitializeData(darwinDeviceName: darwinDeviceName),
);
Extra step for Android: ask for permissions and make sure Wi-fi is enabled:
final granted = await _nearbyService.android?.requestPermissions();
if (granted ?? false) {
// go to the checking Wi-fi step
}
final isWifiEnabled = await _nearbyService.android?.checkWifiService();
if (isWifiEnabled ?? false) {
// go to the starting discovery step
}
Extra step for IOS and macOS: ask the user to choose whether they are a Browser or Advertiser:
In IOS and macOS Multipeer Connectivity, there are 2 roles for the discovery process and connection between devices: browser
and advertiser.
Browser: This component discovers nearby devices that report their availability. It is
responsible for finding peers that advertise themselves and inviting them to join the shared session.
Advertiser: This component advertises the availability of the device to nearby peers. It is used to let know
the browser that the device is available for inviting and connecting.
The code for selecting a role:
_nearbyService.darwin?.setIsBrowser(value: isBrowser);
// go to the starting discovery step
Start to discover the P2P network.
final result = await _nearbyService.discover();
if (result) {
// go to the listening peers step
}
Each of the peers is a NearbyDevice and you can connect to it:
Remember that when used on the Android platform, you can only pass NearbyAndroidDevice to the connect() method.
Similarly for iOS and macOS, NearbyDarwinDevice. Devices automatically come from the discovery state in the correct type, so you
just need to use the received data.
final result = await _nearbyService.connect(device);
if (result) {
// go the the listening the device step
}
Once you are connected via P2P network, you can start listening to the connected device. If null comes from the
stream, it means the devices lost connection.
_connectedDeviceSubscription = _nearbyService.getConnectedDeviceStream(device).listen(
(event) async {
final wasConnected = connectedDevice?.status.isConnected ?? false;
final nowConnected = event?.status.isConnected ?? false;
if (wasConnected && !nowConnected) {
// return to the discovery state
}
connectedDevice = event;
},
);
Once you have connected over a P2P network, you still need to create a communication channel to transfer data.
For Android, this is a socket embedded in NearbyService, for iOS and macOS it's a setup to listen to messages and
resources from the desired device. There is a method startCommunicationChannel() for this purpose. You should pass
to it listeners for messages and resources received from the connected device. There can only be one communication
channel, if you create a new one, the previous one will be cancelled.
final messagesListener = NearbyServiceMessagesListener(
onData: (message) {
// handle the message from NearbyServiceMessagesListener
},
);
final filesListener = NearbyServiceFilesListener(
onData: (pack) async {
// handle the files pack from NearbyServiceFilesListener
},
);
await _nearbyService.startCommunicationChannel(
NearbyCommunicationChannelData(
connectedDevice.info.id,
messagesListener: messagesListener,
filesListener: filesListener,
),
);
I think you've already guessed that since there are listeners of messages from another device, we can send them
ourselves too :) There is a method send() for that:
This is a very important topic for the nearby_service plugin because it provides unique functionality for sharing
typed data.
First, it's essential to say that when you send a message, you are required to pass the OutgoingNearbyMessage model.
This contains the receiver - the recipient to whom the message is addressed. The receiver is someone with whom you
already have an established communication channel. Also OutgoingNearbyMessage contains a content field, it can be
one of 4 types:
NearbyMessageTextRequest
NearbyMessageTextResponse
NearbyMessageFilesRequest
NearbyMessageFilesResponse
We will talk more about them later.
Second, there is another type of message, ReceivedNearbyMessage. This is what comes to you from
the NearbyServiceMessagesListener. It contains a sender field so that you can identify who the message came from and
use that information.
Text messages
Description of the operating logic:
One of the devices sends a message using the send() method:
Another device receives the ReceivedNearbyMessage with content cast as NearbyMessageTextRequest from the
NearbyServiceMessagesListener.
// DEVICE Bfinal messagesListener = NearbyServiceMessagesListener(
onData: (message) {
// message is ReceivedNearbyMessage with content cast as NearbyMessageTextRequest here
},
);
If you want the sender to make sure the message has been received, you can send them NearbyMessageTextResponse. You
need to add the id from the received NearbyMessageTextRequest to it:
If everything is fine, the sender will receive your NearbyMessageTextResponse from
their NearbyServiceMessagesListener:
// DEVICE Afinal messagesListener = NearbyServiceMessagesListener(
onData: (message) {
// message is ReceivedNearbyMessage with content cast as NearbyMessageTextResponse here
},
);
Throughout the process, you can use the ids of the messages to identify them.
Resource messages
Description of the operating logic:
One of the devices sends a message with NearbyMessageFilesRequest using the send() method:
// DEVICE A
_nearbyService.send(
OutgoingNearbyMessage(
// files here is List<NearbyFileInfo>, it can be easily created from the file path: NearbyFileInfo(path: file.path)
content: NearbyMessageFilesRequest.create(files: files),
receiver: connectedDevice.info,
),
);
Another device receives the ReceivedNearbyMessage with content cast as NearbyMessageFilesRequest from the
NearbyServiceMessagesListener.
The file request comes from the NearbyServiceMessagesListener because it doesn't contain the transferred files, only
the request to send them! For files, confirmation by the other party before starting the data transfer is required!
// DEVICE Bfinal messagesListener = NearbyServiceMessagesListener(
onData: (message) {
// message is ReceivedNearbyMessage with content cast as NearbyMessageFilesRequest here
},
);
In order to receive the files, the other party must confirm that it wants to do so and send a message
with NearbyMessageFilesResponse content. The NearbyMessageFilesResponse contains the isAccepted field,
which will determine whether the file sending will start or not.
If you don't want to use confirmation logic to send files, just send the automatic positive responses
to NearbyMessageFilesRequest in NearbyServiceMessagesListener.
The sender will receive NearbyMessageFilesResponse in their NearbyServiceMessagesListener and can notify the
user.
// DEVICE Afinal messagesListener = NearbyServiceMessagesListener(
onData: (message) {
// message is ReceivedNearbyMessage with content cast as NearbyMessageFilesResponse here
},
);
At this time under the hood, the sending of files will begin and the recipient will receive ReceivedNearbyFilesPack in
the NearbyServiceFilesListener when sending is complete. ReceivedNearbyFilesPack holds the paths of the received
files already stored on the device's temporary directory. You can overwrite them to the desired location, delete them,
or do anything else you like.
// DEVICE Bfinal filesListener = NearbyServiceFilesListener(
onData: (pack) async {
// pack is ReceivedNearbyFilesPack here
},
);
Exceptions
NearbyService includes custom errors that you can catch in your implementation.
NearbyServiceUnsupportedPlatformException: Usage of the plugin on an unsupported platform
NearbyServiceUnsupportedDecodingException: Error decoding messages from native platform to Dart (open an issue if
this happens)
NearbyServiceInvalidMessageException: An attempt to send an invalid message on the sender's side. Add content
validation to your messages
Exceptions that can be caught from the discover(), stopDiscovery(), connect(), and disconnect() methods for
the Android platform
See here:
NearbyServiceBusyException: The Wi-Fi P2P framework is currently busy. Usually this means that you have sent a
request to some device and now one of the peers is CONNECTING
NearbyServiceP2PPUnsupportedException: Wi-Fi P2P is not supported on this device
NearbyServiceNoServiceRequestsException: No service discovery requests have been made. Ensure that you have
initiated a service discovery request before attempting to connect
NearbyServiceGenericErrorException: A generic error occurred. This could be due to various reasons such as hardware
issues, Wi-Fi being turned off, or temporary issues with the Wi-Fi P2P framework
NearbyServiceUnknownException: An unknown error occurred. Please check the device's Wi-Fi P2P settings and ensure
the device supports Wi-Fi P2P
Additional options
Each NearbyDevice contains NearbyDeviceInfo that presents different meanings depending on the platform. For
Android id is the MAC address of the device, and for iOS it is the unique identifier of its MCPeerID. In general, id
identifies the device
in its platform.
Use the getter communicationChannelState of NearbyService to know the state of the communication channel. It may
be:
enumCommunicationChannelState {
notConnected,
loading,
connected;
boolget isNotConnected => this == CommunicationChannelState.notConnected;
boolget isLoading => this == CommunicationChannelState.loading;
boolget isConnected => this == CommunicationChannelState.connected;
}
The getter is ValueListenable so you can listen to its state.
There are methods getPlatformVersion() and getPlatformModel() of NearbyService to determine the version and
model of the device respectively. You can use them to specify a
name for the IOS.
Use getCurrentDeviceInfo() of NearbyService to get information about the current device on the P2P network. Note
that the ID obtained from getCurrentDeviceInfo() for Android will always be 02:00:00:00:00:00 for privacy
issues.
For Android, it is possible to listen to the current state of the connection using
method getConnectionInfoStream of NearbyAndroidService.
When you are creating a service with getInstance(), you can define the logging level in the plugin using
field logLevel.
Possible logging levels:
Version 0.2.0 adds support for macOS but introduces breaking changes to accommodate the unified Darwin platform (iOS and macOS).
API Changes
Property Renaming
.ios property has been renamed to .darwin
// Before (v0.1.0)
_nearbyService.ios?.setIsBrowser(value: isBrowser);
// After (v0.2.0)
_nearbyService.darwin?.setIsBrowser(value: isBrowser);
Class Renaming
NearbyIOSDevice has been renamed to NearbyDarwinDevice
NearbyIOSService has been renamed to NearbyDarwinService
NearbyServiceIOSExceptionMapper has been renamed to NearbyServiceDarwinExceptionMapper
Parameter Renaming in NearbyInitializeData
iosDeviceName has been renamed to darwinDeviceName
// Before (v0.1.0)await _nearbyService.initialize(
data: NearbyInitializeData(iosDeviceName: deviceName),
);
// After (v0.2.0)await _nearbyService.initialize(
data: NearbyInitializeData(darwinDeviceName: deviceName),
);
Assert Statements
If you have assertions in your code that check for specific types, make sure to update them:
// Before (v0.1.0)assert(
device isNearbyIOSDevice,
'The Nearby IOS Service can only work with the NearbyIOSDevice'
);
// After (v0.2.0)assert(
device isNearbyDarwinDevice,
'The Nearby Darwin Service can only work with the NearbyDarwinDevice'
);
Flutter Configuration
If you're developing a plugin based on nearby_service, note that the pubspec.yaml now uses sharedDarwinSource: true to share code between iOS and macOS platforms.