Skip to content

Repository files navigation

BLE Proximity Tracker

A Flutter + native Android (Kotlin) app for finding nearby Bluetooth Low Energy devices, tracking one of them, and getting alerted when it goes out of range — with a real GATT connection to inspect a device's services and attempt to ring it, not just passive scanning.

This project demonstrates Flutter/Kotlin Platform Channel integration and real BLE GATT communication, following Clean Architecture end to end.

Features

  • Live BLE scanning — nearby devices listed in real time, sorted by signal strength, with an estimated distance (log-distance path-loss model) next to each RSSI reading.
  • Manufacturer-name fallback — devices that don't advertise a name (common) show a friendlier "Apple Device"/"Google Device"/etc. label when the advertisement's manufacturer ID is recognized, instead of a bare "Unknown".
  • Track a device — tap any device to track it; a banner shows its live distance and turns red once it's out of range.
  • Out-of-range alerts — a local notification fires once a tracked device has stayed beyond the configured distance for a sustained period (avoids false alarms from one noisy reading), including detecting the case where the device stops advertising entirely.
  • Configurable alert settings — the out-of-range distance and sustained-duration threshold are user-adjustable and persisted.
  • Real GATT connection — long-press a device to connect to it, discover its actual Bluetooth services, and see friendly names for well-known ones (Battery Service, Device Information, etc.) alongside raw UUIDs for the rest.
  • Ring Device — if a connected device exposes the standard Bluetooth SIG "Immediate Alert" service, you can trigger an alert on it. If it doesn't, the app says so plainly rather than pretending the button does something it can't.
  • Handles the real edge cases — Bluetooth off before scanning, Bluetooth turned off while scanning, permission denied/permanently denied, no devices found, GATT connection failures/timeouts — each with its own clear UI state, not a generic error screen.

Screenshots

Ready to scan Nearby devices Tracking a device
Permission flow Live scan list Tracking banner
Device details Alert settings Bluetooth off
Real GATT services Configurable alerts Edge-case handling

Architecture

Clean Architecture on both sides of the Platform Channel boundary — the Dart side never talks to Android BLE APIs directly, and the Kotlin side never knows about Riverpod or widgets.

flowchart TB
    subgraph Flutter["Flutter (Dart)"]
        UI["Presentation<br/>ScanScreen · DeviceDetailScreen · SettingsScreen<br/>(Riverpod Notifiers)"]
        DOM["Domain<br/>BleDevice · EstimateDistance · OutOfRangeDetector<br/>LookupServiceName · Repository interfaces"]
        DATA["Data<br/>BleRepositoryImpl · GattRepositoryImpl · SettingsRepositoryImpl<br/>BleChannelDataSource"]
        UI --> DOM
        DATA --> DOM
    end
    subgraph Android["Android (Kotlin)"]
        CH["MethodChannel + EventChannel"]
        SCAN["BleScannerManager<br/>(BluetoothLeScanner, RSSI smoothing,<br/>Bluetooth-state BroadcastReceiver)"]
        GATT["BleGattManager<br/>(BluetoothGatt connect/discover/write,<br/>main-thread-confined state)"]
        CH --> SCAN
        CH --> GATT
    end
    DATA -- "MethodChannel calls /<br/>EventChannel stream" --> CH
Loading
lib/
├── core/platform_channel/     # channel name constants
├── data/
│   ├── datasources/           # BleChannelDataSource (raw MethodChannel/EventChannel calls)
│   ├── repositories/          # BleRepositoryImpl, GattRepositoryImpl, SettingsRepositoryImpl
│   └── services/              # NotificationService
├── domain/
│   ├── entities/               # BleDevice, AlertSettings, GattServiceInfo
│   ├── repositories/           # repository interfaces
│   └── usecases/               # EstimateDistance, OutOfRangeDetector, LookupServiceName, ScanDevices
└── presentation/
    ├── providers/              # Riverpod state (ScanNotifier, DeviceDetailNotifier, ...)
    ├── screens/                # ScanScreen, DeviceDetailScreen, SettingsScreen, PermissionScreen
    └── widgets/                # RadarPulse

android/app/src/main/kotlin/.../
├── ble/                        # BleScannerManager, BleGattManager, BleDevice
└── channels/                   # BleMethodChannel, BleEventChannel

Tech Stack

Layer Technology
UI / state Flutter (Dart), Riverpod
Native BLE Kotlin — BluetoothLeScanner (scanning), BluetoothGatt (connect/discover/write)
Bridge Flutter Platform Channels (MethodChannel + EventChannel)
Persistence shared_preferences (alert settings)
Notifications flutter_local_notifications
Permissions permission_handler
Architecture Clean Architecture (domain/data/presentation)

Setup & Running

Requires a physical Android device (API 21+) with Bluetooth LE — the emulator doesn't support real BLE hardware.

flutter pub get
flutter run

Grant Bluetooth and location permissions when prompted (Android needs location permission pre-Android 12, and BLUETOOTH_SCAN/BLUETOOTH_CONNECT on Android 12+, to perform BLE scans).

Testing

flutter analyze
flutter test

Unit tests cover the domain layer specifically — distance calculation, sustained out-of-range detection (with an injectable clock, so tests don't need to wait in real time), settings persistence, and GATT ring-outcome mapping (success/unsupported/failed) — the business logic that's most worth being able to explain and prove correct, rather than thin UI/plumbing code.

Known Limitations & Future Work

  • Device names: most BLE devices don't advertise a name at all (this app does passive scanning, not a persistent GATT connection to every device) — the manufacturer-ID fallback helps but doesn't fully solve this.
  • GATT connections to phones/laptops commonly fail (CONNECT_FAILED, GATT status 133/147/8/19) — most modern devices refuse unsolicited connections from strangers. This is expected Android/BLE behavior, not a bug in this app; it's exactly why the app reports connection failures clearly instead of retrying silently.
  • Not yet built (tracked as stretch goals, not claimed as done): background scanning as a foreground service, tracking multiple devices simultaneously, a map view of a device's last known location.

About

Flutter app with native Android (Kotlin) BLE integration via Platform Channels — real-time RSSI-based proximity tracking and out-of-range alerts for nearby Bluetooth devices.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages