flutter_mobile_diagnostics
A Flutter plugin that answers: how healthy is this device right now, and what context should I attach to a support ticket or crash report?
Collect a typed snapshot of device identity plus seven runtime domains (battery, memory, storage, network, Bluetooth, security, performance). Each domain is scored as healthy, warning, critical, or unknown. The full report serializes to JSON for Sentry, Crashlytics, or your backend.
| Android | iOS |
|---|---|
| ✅ | ✅ |
Screenshots
Example app overview on Android and iOS:
| Android | iOS |
|---|---|
![]() |
![]() |
Features
- One call, full picture —
getFullReport()returns identity + all health domains + overall score. - Granular getters — fetch only battery, memory, storage, and so on.
- Actionable status — Dart scoring (
HealthInterpreter), not raw maps in the public API. - JSON-ready — every model has
toJson();toJsonReport()is a one-liner for crash SDKs. - Configurable — thresholds and opt-in security / reachability via
DiagnosticsOptions. - Live updates —
watch()polls battery, memory, and network (v1; EventChannel planned later). - Fail gracefully — missing permissions or APIs become
unknown, not a crash. - Android & iOS — native collectors on both mobile platforms.
This package is not a full APM suite, Bluetooth pairing library, or tamper-proof fraud detector.
Install
dependencies:
flutter_mobile_diagnostics: ^0.2.0
flutter pub get
Quick start
import 'package:flutter_mobile_diagnostics/flutter_mobile_diagnostics.dart';
final diagnostics = MobileDiagnostics();
final report = await diagnostics.getFullReport();
debugPrint('${report.overallStatus} ${report.overallScore}');
debugPrint('Battery ${report.battery.levelPercent}%');
debugPrint('${report.device.manufacturer} ${report.device.model}');
Attach to a crash or support payload:
final json = await diagnostics.toJsonReport();
// crashReporter.setContext('device_health', json);
API overview
MobileDiagnostics is cheap to construct. Native work starts on the first method call.
| Method | Returns | Notes |
|---|---|---|
deviceIdentity |
DeviceIdentity |
Cached for the session (model, OS, app version, screen, locale, …). |
getFullReport({options}) |
DiagnosticsReport |
Parallel collection + scoring. Short TTL cache (default 5s). |
getBatteryHealth() |
BatteryHealth |
Level, charging state, power-save. |
getMemoryHealth() |
MemoryHealth |
Total / used RAM, low-memory flag. |
getStorageHealth() |
StorageHealth |
Total / free disk. |
getNetworkHealth({options}) |
NetworkHealth |
Connectivity type, metered, optional reachability. |
getBluetoothHealth() |
BluetoothHealth |
Adapter supported / on / unauthorized. |
getSecurityHealth({options}) |
SecurityHealth |
Lock, emulator, optional root/jailbreak. |
getPerformanceHealth() |
PerformanceHealth |
Thermal state, CPU cores. |
watch({domains, interval}) |
Stream<DiagnosticsSnapshot> |
Polls battery / memory / network. |
toJsonReport({options}) |
Map<String, dynamic> |
getFullReport() + toJson(). |
MobileDiagnostics.clearCache() |
void | Tests or force refresh. |
Options
final report = await diagnostics.getFullReport(
options: const DiagnosticsOptions(
includeSecurityChecks: true,
includeRootJailbreakCheck: false, // advisory; off by default
includeNetworkReachability: false, // extra latency; off by default
batteryWarningThresholdPercent: 20,
batteryCriticalThresholdPercent: 10,
memoryWarningThresholdPercent: 75,
memoryCriticalThresholdPercent: 90,
storageWarningThresholdMb: 500,
storageCriticalThresholdMb: 100,
collectionTimeout: Duration(seconds: 5),
),
);
Single domain
final memory = await diagnostics.getMemoryHealth();
final perf = await diagnostics.getPerformanceHealth();
final canRunHeavyWork = memory.status != HealthStatus.critical &&
perf.thermalState != ThermalState.critical;
Watch battery / network
final sub = diagnostics
.watch(domains: DiagnosticsDomain.watchableDefaults)
.listen((snapshot) {
final level = snapshot.battery?.levelPercent;
if (level != null && level < 15) {
// show low-battery UX
}
});
// later: await sub.cancel();
Health scoring
Native code returns raw metrics. Scoring is pure Dart so Android and iOS share the same rules.
| Domain | Healthy | Warning | Critical |
|---|---|---|---|
| Battery | ≥ warning % and not in saver | Below warning or power saver | Below critical and discharging |
| Memory | Below warning % used | Warning–critical band | ≥ critical % or OS low-memory |
| Storage | Free above warning MB | Between critical and warning | Free below critical MB |
| Network | Connected | Metered-only or reachability failed | No connectivity |
| Bluetooth | Supported and on | Supported but off | — (unknown if unsupported / no permission) |
| Security | Lock set, not emulator, dev mode off | No lock, emulator, or dev mode | Rooted / jailbroken if that check is enabled |
| Performance | Thermal nominal | Fair / serious | Thermal critical |
Overall status = worst domain (critical > warning > unknown > healthy).
Overall score (0–100) = weighted average: memory 25%, storage 20%, battery 15%, performance 15%, network 10%, security 10%, Bluetooth 5%.
unknown contributes a neutral 50 to the score.
Example JSON
{
"device": {
"manufacturer": "Samsung",
"model": "SM-G991B",
"os_name": "Android",
"os_version": "14",
"app_version": "1.2.3",
"is_physical_device": true
},
"battery": {
"level_percent": 78,
"state": "discharging",
"status": "healthy"
},
"overall_status": "healthy",
"overall_score": 92.5,
"collected_at": "2026-08-29T17:14:00.000Z",
"collection_duration_ms": 145
}
Permissions
Android
Merged from the plugin manifest (host apps inherit these):
| Permission | Why |
|---|---|
ACCESS_NETWORK_STATE |
Network type / metered |
ACCESS_WIFI_STATE |
Wi-Fi related signals |
BLUETOOTH (API ≤ 30) |
Adapter state |
BLUETOOTH_CONNECT (API 31+) |
Adapter state without scanning |
INTERNET |
Optional reachability probe |
If Bluetooth permission is denied, Bluetooth health is unauthorized / unknown. The rest of the report still succeeds.
iOS
Add a usage string if you query Bluetooth adapter state (the example app already does):
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth adapter state is used only for device health diagnostics. The app does not scan or pair accessories.</string>
Platform notes
Supported on Android and iOS only. Availability varies by OS version; missing APIs are omitted or marked unknown.
- Android / iOS — battery, memory, storage, network, Bluetooth adapter state, security heuristics, thermal / performance signals.
- Root / jailbreak checks are heuristic and opt-in. They are not forensic or anti-tamper guarantees.
Example app
The example/ project is a full demo:
- Overview — overall score, domain grid, and live monitor
- Domain detail — per-getter drill-down
- Live —
watch() - Report —
toJsonReport() - Settings —
DiagnosticsOptionstoggles
cd example
flutter run
Testing
flutter test --coverage
cd example && flutter test --coverage
Swap the platform in unit tests:
FlutterMobileDiagnosticsPlatform.instance = MyFakePlatform();
MobileDiagnostics.clearCache();
Further reading
- doc/ANALYSIS_AND_DESIGN.md — requirements, landscape, domain mapping
- doc/DESIGN.md — architecture, models, native collectors, scoring
License
BSD 3-Clause. See LICENSE.
Libraries
- flutter_mobile_diagnostics
- Device health diagnostics for Flutter.
- flutter_mobile_diagnostics_method_channel
- Native MethodChannel implementation of FlutterMobileDiagnosticsPlatform.
- flutter_mobile_diagnostics_platform_interface
- Platform interface for device health collectors.

