ihealth_hr 0.0.6
ihealth_hr: ^0.0.6 copied to clipboard
A Flutter plugin to integrate the iHealth KN-550BT blood pressure monitor.
example/lib/main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:ihealth_hr/ihealth_hr.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'iHealth HR Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: const IHealthHomePage(),
);
}
}
class IHealthHomePage extends StatefulWidget {
const IHealthHomePage({super.key});
@override
State<IHealthHomePage> createState() => _IHealthHomePageState();
}
class _IHealthHomePageState extends State<IHealthHomePage> {
String connectionStatus = "Waiting for device status...";
String batteryLevel = "--";
int offlineHistoryCount = 0;
String message = "";
int? latestHeartRate;
int? latestSys;
int? latestDia;
String latestTime = "";
String deviceTime = "";
bool? isBackLightOn;
bool? isClockOn;
final List<Map<String, dynamic>> historyData = [];
StreamSubscription? _deviceStatusSubscription;
@override
void initState() {
super.initState();
requestPermissionsAndStartScan();
}
Future<void> requestPermissionsAndStartScan() async {
// Request all necessary permissions
Map<Permission, PermissionStatus> statuses =
await [
Permission.bluetooth,
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.locationWhenInUse,
].request();
// Check if all permissions are granted
bool allGranted = statuses.values.every((status) => status.isGranted);
if (allGranted) {
// Start scan automatically
IhealthHrPlugin.startScan();
// Listen to device status events
_deviceStatusSubscription = IhealthHrPlugin.deviceStatusStream.listen(
(event) {
if (event is Map) {
final Map<dynamic, dynamic> data = Map<dynamic, dynamic>.from(
event,
);
final String eventType = data['event'] ?? '';
switch (eventType) {
case 'connectionStateChanged':
final String status = data['status'] ?? 'unknown';
setState(() {
connectionStatus =
status.isNotEmpty
? status[0].toUpperCase() + status.substring(1)
: "Unknown";
});
break;
case 'offlineHistoryCount':
final int count = data['count'] ?? 0;
final String msg = data['message'] ?? '';
setState(() {
offlineHistoryCount = count;
message = msg;
});
break;
case 'historyData':
setState(() {
latestHeartRate = data['heartRate'] as int?;
latestSys = data['sys'] as int?;
latestDia = data['dia'] as int?;
latestTime = data['time'] ?? "";
final newEntry = {
"time": latestTime,
"sys": latestSys,
"dia": latestDia,
"heartRate": latestHeartRate,
};
if (!historyData.any((e) => e['time'] == latestTime)) {
historyData.add(newEntry);
}
});
break;
case 'displayStatus':
setState(() {
isBackLightOn = data['backlightOn'] as bool?;
isClockOn = data['clockOn'] as bool?;
});
break;
case 'deviceTime':
setState(() {
deviceTime = data['time'] ?? "";
});
break;
case 'batteryLevel':
setState(() {
batteryLevel = data['level']?.toString() ?? "--";
});
break;
default:
print("Unknown event type: $eventType");
}
} else {
// Fallback: show raw event
setState(() {
message = event.toString();
});
}
},
onError: (error) {
setState(() {
message = "Error receiving data: $error";
});
},
);
} else {
setState(() {
message =
"Permissions not granted. Please enable Bluetooth and Location permissions in settings.";
});
}
}
@override
void dispose() {
_deviceStatusSubscription?.cancel();
super.dispose();
}
Widget buildHistoryList(double maxHeight) {
if (historyData.isEmpty) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text("No historical data yet.", style: TextStyle(fontSize: 16)),
);
}
return Container(
constraints: BoxConstraints(maxHeight: maxHeight),
child: ListView.separated(
shrinkWrap: true,
itemCount: historyData.length,
separatorBuilder: (_, __) => Divider(),
itemBuilder: (context, index) {
final item = historyData[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Text(
"Time: ${item['time']}, Sys: ${item['sys']}, Dia: ${item['dia']}, HR: ${item['heartRate']}",
style: TextStyle(fontSize: 16),
),
);
},
),
);
}
Widget buildStatusText(String title, String value, {Color? color}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: RichText(
text: TextSpan(
text: '$title ',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black,
),
children: [
TextSpan(
text: value,
style: TextStyle(
fontWeight: FontWeight.normal,
color: color ?? Colors.black,
fontSize: 18,
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final maxHistoryHeight =
screenHeight * 0.3 > 300 ? 300.0 : screenHeight * 0.3;
return Scaffold(
appBar: AppBar(title: const Text('iHealth HR Plugin Demo')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildStatusText(
'Connection Status:',
connectionStatus,
color:
connectionStatus.toLowerCase() == 'connected'
? Colors.green
: Colors.red,
),
buildStatusText(
'Battery Level:',
batteryLevel,
color: Colors.green,
),
buildStatusText('Offline History Count:', '$offlineHistoryCount'),
buildStatusText(
'Latest Heart Rate (BPM):',
latestHeartRate != null ? '$latestHeartRate' : '--',
color: Colors.red,
),
buildStatusText(
'Latest Blood Pressure:',
(latestSys != null && latestDia != null)
? 'High: $latestSys mmHg, Low: $latestDia mmHg'
: '--',
color: Colors.blue,
),
if (latestTime.isNotEmpty)
buildStatusText('Measurement Time:', latestTime),
if (deviceTime.isNotEmpty)
buildStatusText('Device Reported Time:', deviceTime),
buildStatusText(
'Backlight:',
isBackLightOn == null ? 'N/A' : (isBackLightOn! ? 'ON' : 'OFF'),
color: isBackLightOn == true ? Colors.green : Colors.grey,
),
buildStatusText(
'Clock Display:',
isClockOn == null ? 'N/A' : (isClockOn! ? 'ON' : 'OFF'),
color: isClockOn == true ? Colors.green : Colors.grey,
),
const Divider(height: 32, thickness: 2),
const Text(
'Historical Data:',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
buildHistoryList(maxHistoryHeight),
const SizedBox(height: 32),
if (message.isNotEmpty)
Text(
'Message: $message',
style: const TextStyle(fontSize: 16, color: Colors.blueGrey),
textAlign: TextAlign.center,
),
],
),
),
),
);
}
}