bearound_flutter_sdk 3.4.2
bearound_flutter_sdk: ^3.4.2 copied to clipboard
Bearound Flutter SDK - Beacon detection and proximity tracking
π» Bearound Flutter SDK #
Official Flutter plugin for the Bearound native SDKs (Android/iOS) version 3.3.1.
Features #
- Native beacon scanning on Android and iOS
- Real-time beacon stream with metadata (battery, firmware, temperature)
- Sync lifecycle events (sync started/completed callbacks)
- Background detection events (beacons detected in background)
- Optional Android foreground service (
connectedDevice) for resilient background scanning - Scanning state and error streams
- User properties support for enriched beacon events
- Native permission handling - iOS uses
requestAlwaysAuthorization()directly (no blue GPS indicator) - Business token authentication with automatic app ID detection
- Automatic Bluetooth metadata collection and periodic scanning
Installation #
Add to your pubspec.yaml:
dependencies:
bearound_flutter_sdk: ^3.3.1
Run:
flutter pub get
Platform Setup #
Android #
Add JitPack to your root settings.gradle or build.gradle:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
You normally don't need to declare any permissions β the SDK declares them all and the Android manifest merger injects them into your app automatically. The SDK uses the connectedDevice foreground-service model (reading data from Bluetooth devices), not location.
β οΈ If you redeclare
BLUETOOTH_SCANin your own manifest, you must keep theneverForLocationflag. If any declaration omits it, the flag is dropped from the merged manifest and Google treats the app as deriving location:<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" tools:targetApi="s" />
For reference, the SDK declares:
<!-- Bluetooth -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" tools:targetApi="s" />
<!-- Location: legacy only β required for a BLE scan on API <= 30; not requested on API 31+ -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
<!-- Foreground service: connectedDevice (BLE) on Android 14+ -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<!-- Misc -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Minimum Android SDK: 23.
iOS #
Add the following to ios/Runner/Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>processing</string>
<string>bluetooth-central</string>
</array>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to detect nearby beacons.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs location access to detect nearby beacons.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>This app needs location access to detect nearby beacons in background.</string>
Important for terminated app detection:
fetchmode allows iOS to wake the app when beacons are detected- User must grant "Always" location permission (not "When In Use")
- User must enable "Background App Refresh" in device Settings
βΉοΈ The iOS SDK uses a two-eyes model (CoreLocation + CoreBluetooth), so location is used on iOS by design. The
neverForLocationstrategy applies to Android only.
Minimum iOS version: 13.0.
Scan modes (Android) #
On iOS scanning is always system-managed (region monitoring +
BGTaskScheduler). These modes are Android-only.
The SDK ships two background-scan strategies, and you pick per app β right at startScanning(), with no native changes. Both already exist in the native SDK; you're just choosing which one to turn on.
At a glance β what you gain #
| πͺΆ Opportunistic (default) | π‘οΈ Foreground service | |
|---|---|---|
| Best for | casual presence, battery-first apps | real-time footfall, mission-critical presence |
| You gain | zero setup Β· no Play video Β· lowest battery | reliable detection that survives app-kill & aggressive OEMs |
| You accept | unpredictable latency Β· misses in deep background | persistent notification + Play demo video |
Quick pick #
- Can't (or won't) submit a Play demo video, and occasional detection is fine β πͺΆ Opportunistic
- Need real-time presence that survives the app being killed on Xiaomi/Huawei/Samsung β π‘οΈ Foreground service
- In between β Foreground service if you can submit the video; otherwise Opportunistic
Mode 1 β Opportunistic (no foreground service) Β· default #
What you gain: no FOREGROUND_SERVICE_CONNECTED_DEVICE permission, no Play demonstration video, and the lowest battery footprint.
await BearoundFlutterSdk.startScanning();
The OS delivers beacons through a PendingIntent scan (BluetoothScanReceiver), re-armed by AlarmManager (ScanWatchdogReceiver) β it keeps working with the app killed, but the system decides when (opportunistic, throttled).
To fully drop the Play video, also remove the FGS permission the native SDK injects via manifest merge:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
tools:node="remove" />
Mode 2 β Foreground service (connectedDevice) #
What you gain: continuous, low-latency detection that survives app-kill and aggressive OEMs (Xiaomi/Huawei/Samsung) β the reliable path for footfall/presence analytics.
// By default the notification shows the host app's own name (localized by the
// device) + a generic, localized subtitle ("Atualizando conteΓΊdo" / "Updating
// content") β nothing about Bluetooth or reading data. Just enable it:
await BearoundFlutterSdk.startScanning(
foregroundScanConfig: const ForegroundScanConfig(),
);
// Want a custom title/subtitle instead? Pass them explicitly:
// await BearoundFlutterSdk.startScanning(
// foregroundScanConfig: const ForegroundScanConfig(
// notificationTitle: 'My App', notificationText: 'Bluetooth active',
// ),
// );
await BearoundFlutterSdk.disableForegroundScanning(); // stop the service
β οΈ Google Play: the
connectedDeviceforeground service requires a Play Console declaration + demonstration video. In the video/declaration, frame the feature as reading data from external Bluetooth devices β never as location or proximity (stays consistent withneverForLocation). The persistent notification itself just shows the app name, which is enough to satisfy the perceptibility requirement.
Trade-off #
| πͺΆ Opportunistic | π‘οΈ Foreground service | |
|---|---|---|
| App in foreground | continuous | continuous |
| App in background | opportunistic, throttled | continuous |
| App killed / swiped away | relaunched by OS (PendingIntent) | process kept alive |
| Aggressive OEM (Xiaomi/Huawei) | β killed | β survives |
| Detection latency | unpredictable (s β min) | low (per ScanPrecision) |
| Presence accuracy | low / medium | high |
| Battery | lower | higher |
| Persistent notification | none | yes (mandatory) |
| Extra permission | none | FOREGROUND_SERVICE_CONNECTED_DEVICE |
| Google Play video | β not required | β required |
Scan windows (detection cadence) #
ScanPrecision governs the duty cycle while the scan is active (foreground or FGS):
| Strategy | Typical detection window | Predictable? | Battery |
|---|---|---|---|
FGS + ScanPrecision.high |
~1β5 s, continuous | yes | high |
FGS + ScanPrecision.medium |
~10β20 s (10 s scan + 10 s pause Γ3 / 60 s) | yes | medium |
FGS + ScanPrecision.low |
~60 s (10 s scan + 50 s pause / 60 s) | yes | low |
| Opportunistic (no FGS) | seconds β several minutes (OS-decided) | no | low |
| WorkManager (client-side, external) | β₯ 15 min (WorkManager minimum) | yes | very low |
Windows are approximate, derived from the
ScanPrecisionduty cycle. In opportunistic modeScanPrecisiononly governs cadence while the app is in the foreground; in the background the OS throttles thePendingIntentscan.
Advanced: WorkManager (client-side) #
The SDK does not bundle WorkManager. For a predictable low-frequency sweep without a foreground service, schedule your own PeriodicWorkRequest (minimum interval 15 min) that calls startScanning() for a short window and then stopScanning(). This trades latency for battery and avoids the Play video β presence lags by up to the chosen period.
Quick Start #
import 'package:bearound_flutter_sdk/bearound_flutter_sdk.dart';
Future<void> setupSdk() async {
final permissionsOk = await BearoundFlutterSdk.requestPermissions();
if (!permissionsOk) {
return;
}
await BearoundFlutterSdk.configure(
businessToken: 'your-business-token-here',
scanPrecision: ScanPrecision.high, // high | medium | low (default: high)
maxQueuedPayloads: MaxQueuedPayloads.medium, // small | medium | large | xlarge
);
// Note: appId is automatically extracted from the app's package/bundle identifier
// Bluetooth metadata and periodic scanning are automatic
// Listen to beacons
BearoundFlutterSdk.beaconsStream.listen((beacons) {
for (final beacon in beacons) {
print('${beacon.major}.${beacon.minor} - RSSI ${beacon.rssi}');
}
});
// Listen to sync lifecycle
BearoundFlutterSdk.syncLifecycleStream.listen((event) {
if (event.isStarted) {
print('Sync started with ${event.beaconCount} beacons');
} else if (event.isCompleted) {
print('Sync completed: ${event.success ? "success" : "failed"}');
}
});
// Listen to background detections
BearoundFlutterSdk.backgroundDetectionStream.listen((event) {
print('Background: ${event.beaconCount} beacons detected');
});
// Other streams
BearoundFlutterSdk.scanningStream.listen((isScanning) {
print('Scanning: $isScanning');
});
BearoundFlutterSdk.errorStream.listen((error) {
print('SDK error: ${error.message}');
});
await BearoundFlutterSdk.startScanning();
}
User Properties #
await BearoundFlutterSdk.setUserProperties(
const UserProperties(
internalId: 'user-123',
email: 'user@example.com',
name: 'John Doe',
customProperties: {'plan': 'premium'},
),
);
Push Token #
Forward the device push token (FCM on Android, APNs on iOS) to the native SDK so
the backend can target the device. The native SDK associates it with the stable
deviceId and sends it on the next sync.
// Android: obtain the FCM token (e.g. via firebase_messaging) and forward it.
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
await BearoundFlutterSdk.setPushToken(token);
}
- iOS: the native SDK captures the APNs token automatically via an
AppDelegate swizzle, so calling
setPushTokenis usually unnecessary. Call it only if you opted out (BearoundAppDelegateProxyEnabled = NOinInfo.plist) and forward the token fromapplication(_:didRegisterForRemoteNotificationsWithDeviceToken:). - Android: the native SDK does not yet expose a push-token setter, so
setPushTokenis a silent no-op there today (kept for API parity).
API Summary #
Methods #
configure({businessToken, scanPrecision, maxQueuedPayloads})startScanning() / stopScanning() / isScanning()enableForegroundScanning([ForegroundScanConfig]) / disableForegroundScanning() / isForegroundScanningEnabled()β Android-onlysetForegroundNotificationContent(NotificationContent)β Android-onlysetUserProperties(UserProperties) / clearUserProperties()setPushToken(String token)β forward FCM/APNs token (iOS-only at the native layer)
Streams #
beaconsStream- Detected beacons with metadatasyncLifecycleStream- Sync started/completed eventsbackgroundDetectionStream- Background beacon detectionsscanningStream- Scanning state changeserrorStream- SDK errors
Configuration Enums #
- ScanPrecision:
high(continuous scan, sync every 15s),medium(10s scan + 10s pause per 60s),low(10s scan + 50s pause per 60s) - MaxQueuedPayloads:
small(50),medium(100),large(200),xlarge(500)
Migration Guide #
From 2.4.x to 3.3.x #
foregroundScanInterval/backgroundScanIntervalwere replaced by a singlescanPrecision(ScanPrecision.high/medium/low).- Native SDKs bumped to 3.3.1 (Android + iOS).
- Android now uses the
connectedDeviceforeground-service model withBLUETOOTH_SCAN(neverForLocation) β no background-location permission.
Before:
await BearoundFlutterSdk.configure(
businessToken: 'token',
foregroundScanInterval: ForegroundScanInterval.seconds30, // β removed
backgroundScanInterval: BackgroundScanInterval.seconds90, // β removed
);
After:
await BearoundFlutterSdk.configure(
businessToken: 'token',
scanPrecision: ScanPrecision.high,
maxQueuedPayloads: MaxQueuedPayloads.medium,
);
From 2.1.0 to 2.2.0 #
Simplified API: Bluetooth metadata and periodic scanning are now automatic.
await BearoundFlutterSdk.configure(
businessToken: 'token',
// β
Bluetooth metadata: always enabled
// β
Periodic scanning: automatic (FG: enabled, BG: continuous)
);
// Listen to sync lifecycle events
BearoundFlutterSdk.syncLifecycleStream.listen((event) {
if (event.isStarted) print('Sync started');
if (event.isCompleted) print('Sync completed: ${event.success}');
});
// Listen to background detections
BearoundFlutterSdk.backgroundDetectionStream.listen((event) {
print('${event.beaconCount} beacons detected in background');
});
From 1.x to 2.x #
startScan(clientToken)was replaced byconfigure()+startScanning().- Backup size, region events, and legacy sync success/error events were removed.
- Use
syncLifecycleStreamfor sync events anderrorStreamfor failures.