Flutter Side:
- ✅
WatchBridgeService- Complete method channel wrapper - ✅ Permission handling via
permission_handlerpackage - ✅ Event channel for heart rate streaming
- ✅ Auto-sync functionality
- ✅ Retry logic with exponential backoff
- ✅ Error handling and logging
Kotlin Side:
- ✅
HealthTrackingManager- Samsung Health SDK integration - ✅
WatchToPhoneSyncManager- Wearable Data Layer messaging - ✅
PhoneDataListenerService- Background service for receiving data - ✅
MainActivity- Method channel handlers - ✅ Heart rate tracking with IBI data
- ✅ Connection management
Android Manifest:
- ✅
BODY_SENSORSpermission declared - ✅ Wear OS feature declaration
- ✅
PhoneDataListenerServiceregistered - ✅ Foreground service declarations
-
Health Permissions for Android 15+ (BAKLAVA)
- Missing
android.permission.health.READ_HEART_RATEhandling - Need version-aware permission requests
- Missing
-
TrackedData Model Alignment
- Pure Kotlin uses
TrackedData(hr: Int, ibi: ArrayList<Int>) - Current Flutter uses
HeartRateData(bpm: Int?, ibiValues: List<Int>) - Need to align data structures
- Pure Kotlin uses
-
Batch Data Sending
- Pure Kotlin sends last 40 HR values in batch
- Current implementation sends single readings
- Need to implement batch collection and transmission
-
Phone-Side Flutter Integration
- Event channel registered but no Flutter UI consuming it
- Need phone-side screen to display received data
-
Connection State Management
- Need better connection state tracking
- Missing connection status UI feedback
-
Data Validation
- Need HR status validation (like pure Kotlin's
isHRValid()) - Missing IBI status filtering
- Need HR status validation (like pure Kotlin's
Goal: Support both BODY_SENSORS (Android 14-) and health.READ_HEART_RATE (Android 15+)
-
Update AndroidManifest.xml
- Add
android.permission.health.READ_HEART_RATE - Keep existing
BODY_SENSORSfor backward compatibility
- Add
-
Update MainActivity.kt Permission Handling
- Add version check for Android 15+ (Build.VERSION_CODES.BAKLAVA)
- Request appropriate permission based on Android version
- Update
checkPermission()to check correct permission
-
Update WatchBridgeService (Flutter)
- Already uses
permission_handlerwhich handles this automatically - No changes needed on Flutter side
- Already uses
Files to Modify:
android/app/src/main/AndroidManifest.xmlandroid/app/src/main/kotlin/com/example/flowfit/MainActivity.kt
Goal: Match the pure Kotlin data structure and validation logic
-
Create TrackedData Model (Kotlin)
- Create
TrackedData.ktdata class matching pure Kotlin version - Add serialization support (kotlinx.serialization)
- Create
-
Update HealthTrackingManager
- Add HR status validation (
isHRValid()) - Add IBI status filtering
- Store valid HR data in list (last 40 values)
- Return
TrackedDatainstead ofHeartRateData
- Add HR status validation (
-
Update Flutter Model
- Create
TrackedDatamodel in Flutter - Update
WatchBridgeServiceto use new model - Keep backward compatibility with existing
HeartRateData
- Create
Files to Create:
android/app/src/main/kotlin/com/example/flowfit/TrackedData.ktlib/models/tracked_data.dart
Files to Modify:
android/app/src/main/kotlin/com/example/flowfit/HealthTrackingManager.ktlib/services/watch_bridge.dart
Goal: Collect last 40 HR readings and send in batch to phone
-
Update HealthTrackingManager
- Add
validHrData: ArrayList<TrackedData>storage - Implement
trimDataList()to keep only last 40 values - Add
getValidHrData()method
- Add
-
Add Batch Send Method Channel
- Add
sendBatchToPhonemethod in MainActivity - Serialize ArrayList to JSON
- Use WatchToPhoneSyncManager to send
- Add
-
Update WatchBridgeService (Flutter)
- Add
sendBatchToPhone()method - Collect data over time
- Trigger batch send on user action or timer
- Add
-
Update WatchToPhoneSyncManager
- Already has
sendBatchToPhone()- verify it works - Ensure proper JSON encoding
- Already has
Files to Modify:
android/app/src/main/kotlin/com/example/flowfit/HealthTrackingManager.ktandroid/app/src/main/kotlin/com/example/flowfit/MainActivity.ktlib/services/watch_bridge.dart
Goal: Create phone UI to receive and display heart rate data from watch
-
Create Phone Heart Rate Screen (Flutter)
- Create
lib/screens/phone/phone_heart_rate_screen.dart - Listen to
com.flowfit.phone/heartrateevent channel - Display received HR and IBI data
- Show connection status
- Create
-
Update MainActivity for Phone
- Event channel already registered
- Verify
PhoneDataListenerService.eventSinkconnection
-
Add Navigation
- Add route to phone heart rate screen
- Add button in dashboard to access
-
Handle Background Data
- When app is closed, service launches MainActivity
- MainActivity should navigate to heart rate screen with data
Files to Create:
lib/screens/phone/phone_heart_rate_screen.dartlib/services/phone_data_receiver.dart
Files to Modify:
lib/screens/dashboard.dartandroid/app/src/main/kotlin/com/example/flowfit/MainActivity.kt
Goal: Better connection state tracking and UI feedback
-
Add Connection State Stream
- Create connection state model
- Add periodic connection checks
- Emit connection state changes
-
Update UI with Connection Status
- Show "Connected to Phone" / "Disconnected" indicator
- Show node count
- Show last sync time
-
Add Manual Sync Button
- Button to trigger batch send
- Show sync progress
- Show success/failure feedback
Files to Create:
lib/models/connection_state.dart
Files to Modify:
lib/services/watch_bridge.dartlib/screens/wear/wear_heart_rate_screen.dart
Goal: Ensure end-to-end data flow works correctly
-
Watch-Side Testing
- Test permission request flow
- Test heart rate tracking start/stop
- Test data streaming
- Test batch send
-
Phone-Side Testing
- Test data reception in background
- Test data reception when app is open
- Test UI updates
-
Integration Testing
- Test watch → phone data flow
- Test connection loss/recovery
- Test multiple data points
- Test batch transmission
-
Edge Cases
- Test with no phone connected
- Test with invalid HR data
- Test permission denial
- Test service disconnection
<!-- Add this permission for Android 15+ -->
<uses-permission android:name="android.permission.health.READ_HEART_RATE" />
<!-- Keep existing BODY_SENSORS for Android 14- -->
<uses-permission android:name="android.permission.BODY_SENSORS" />package com.example.flowfit
import kotlinx.serialization.Serializable
@Serializable
data class TrackedData(
var hr: Int = 0,
var ibi: ArrayList<Int> = ArrayList()
)// Add these properties
private val validHrData = ArrayList<TrackedData>()
private val maxDataPoints = 40
// Add validation method
private fun isHRValid(status: Int): Boolean = status == 1
// Update processDataPoint to store valid data
private fun processDataPoint(dataPoint: DataPoint) {
val hrValue = dataPoint.getValue(ValueKey.HeartRateSet.HEART_RATE) as? Int
val hrStatus = dataPoint.getValue(ValueKey.HeartRateSet.HEART_RATE_STATUS) as? Int
if (isHRValid(hrStatus ?: -1) && hrValue != null) {
val trackedData = TrackedData(hr = hrValue)
// Add IBI values
val ibiList = getValidIbiList(dataPoint)
trackedData.ibi.addAll(ibiList)
validHrData.add(trackedData)
trimDataList()
// Also send to Flutter
onHeartRateData(convertToHeartRateData(trackedData))
}
}
// Add trim method
private fun trimDataList() {
while (validHrData.size > maxDataPoints) {
validHrData.removeAt(0)
}
}
// Add getter
fun getValidHrData(): ArrayList<TrackedData> = validHrData// Update permission handling
private fun requestPermission(result: MethodChannel.Result) {
try {
val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
"android.permission.health.READ_HEART_RATE"
} else {
Manifest.permission.BODY_SENSORS
}
if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED) {
result.success(true)
} else {
pendingPermissionResult = result
ActivityCompat.requestPermissions(this, arrayOf(permission), PERMISSION_REQUEST_CODE)
}
} catch (e: Exception) {
result.error("PERMISSION_ERROR", "Failed to request permission", e.message)
}
}
// Add batch send method
private fun sendBatchToPhone(result: MethodChannel.Result) {
val manager = healthTrackingManager
if (manager == null) {
result.error("MANAGER_ERROR", "Health tracking manager not initialized", null)
return
}
val syncManager = watchToPhoneSyncManager
if (syncManager == null) {
result.error("SYNC_ERROR", "Sync manager not initialized", null)
return
}
scope.launch {
try {
val data = manager.getValidHrData()
val json = Json.encodeToString(data)
syncManager.sendBatchToPhone(json) { success ->
mainHandler.post {
result.success(success)
}
}
} catch (e: Exception) {
mainHandler.post {
result.error("BATCH_ERROR", e.message, null)
}
}
}
}class TrackedData {
final int hr;
final List<int> ibi;
TrackedData({
required this.hr,
required this.ibi,
});
factory TrackedData.fromJson(Map<String, dynamic> json) {
return TrackedData(
hr: json['hr'] ?? 0,
ibi: List<int>.from(json['ibi'] ?? []),
);
}
Map<String, dynamic> toJson() {
return {
'hr': hr,
'ibi': ibi,
};
}
}class PhoneHeartRateScreen extends StatefulWidget {
@override
_PhoneHeartRateScreenState createState() => _PhoneHeartRateScreenState();
}
class _PhoneHeartRateScreenState extends State<PhoneHeartRateScreen> {
static const eventChannel = EventChannel('com.flowfit.phone/heartrate');
List<TrackedData> _receivedData = [];
@override
void initState() {
super.initState();
_listenToWatchData();
}
void _listenToWatchData() {
eventChannel.receiveBroadcastStream().listen((data) {
final jsonData = jsonDecode(data as String);
if (jsonData is List) {
// Batch data
final batch = jsonData.map((item) => TrackedData.fromJson(item)).toList();
setState(() => _receivedData.addAll(batch));
} else {
// Single data point
final trackedData = TrackedData.fromJson(jsonData);
setState(() => _receivedData.add(trackedData));
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Watch Data')),
body: ListView.builder(
itemCount: _receivedData.length,
itemBuilder: (context, index) {
final data = _receivedData[index];
return ListTile(
title: Text('HR: ${data.hr} bpm'),
subtitle: Text('IBI: ${data.ibi.take(4).join(", ")}'),
);
},
),
);
}
}- ✅ Permissions requested correctly on Android 14 and 15+
- ✅ Heart rate tracking starts and streams data
- ✅ IBI values extracted and validated
- ✅ Last 40 readings stored in memory
- ✅ Batch send works and transmits to phone
- ✅ Connection status visible in UI
- ✅ Background service receives data
- ✅ Event channel delivers data to Flutter
- ✅ UI displays received heart rate data
- ✅ Batch data parsed and displayed
- ✅ App launches when data received in background
- ✅ End-to-end data flow: Watch → Wearable Data Layer → Phone
- ✅ Data structure matches between watch and phone
- ✅ No data loss during transmission
- ✅ Proper error handling at all layers
| Phase | Duration | Priority |
|---|---|---|
| Phase 1: Health Permissions | 30 mins | 🔴 Critical |
| Phase 2: Data Models | 45 mins | 🔴 Critical |
| Phase 3: Batch Sending | 1 hour | 🟡 High |
| Phase 4: Phone UI | 1 hour | 🟡 High |
| Phase 5: Connection Management | 45 mins | 🟢 Medium |
| Phase 6: Testing | 1 hour | 🔴 Critical |
| Total | 5 hours |
- Start with Phase 1 - Fix health permissions (critical for Android 15+)
- Move to Phase 2 - Align data models (foundation for everything else)
- Implement Phase 3 - Batch sending (core feature)
- Build Phase 4 - Phone UI (user-facing feature)
- Add Phase 5 - Connection management (polish)
- Complete Phase 6 - Testing (validation)
- Existing code is 80% complete - mostly need alignment and enhancements
- No major architectural changes needed - method channels already set up
- Focus on data model alignment - this is the key difference from pure Kotlin
- Phone-side UI is the biggest gap - need to create from scratch
- Testing is critical - watch-to-phone communication can be tricky
Generated: November 25, 2025
Status: 📋 Ready for Implementation
Estimated Completion: 5 hours of focused work