diff --git a/packages/health/CHANGELOG.md b/packages/health/CHANGELOG.md index 7e5a22404..fafe282ee 100644 --- a/packages/health/CHANGELOG.md +++ b/packages/health/CHANGELOG.md @@ -1,5 +1,6 @@ ## 12.1.0 +* Add delete record by UUID method. See function `deleteByUUID(required String uuid, HealthDataType? type)` * iOS: Parse metadata to remove unsupported types - PR [#1120](https://github.com/cph-cachet/flutter-plugins/pull/1120) * iOS: Add UV Index Types * Android: Add request access to historic data [#1126](https://github.com/cph-cachet/flutter-plugins/issues/1126) - PR [#1127](https://github.com/cph-cachet/flutter-plugins/pull/1127) diff --git a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt index 89f403722..a9c23af2c 100644 --- a/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt +++ b/packages/health/android/src/main/kotlin/cachet/plugins/health/HealthPlugin.kt @@ -167,6 +167,7 @@ class HealthPlugin(private var channel: MethodChannel? = null) : "writeBloodOxygen" -> writeBloodOxygen(call, result) "writeMenstruationFlow" -> writeMenstruationFlow(call, result) "writeMeal" -> writeMeal(call, result) + "deleteByUUID" -> deleteByUUID(call, result) else -> result.notImplemented() } } @@ -2395,6 +2396,43 @@ class HealthPlugin(private var channel: MethodChannel? = null) : } } + /** Delete a specific record by UUID and type */ + private fun deleteByUUID(call: MethodCall, result: Result) { + val arguments = call.arguments as? HashMap<*, *> + val dataTypeKey = (arguments?.get("dataTypeKey") as? String)!! + val uuid = (arguments?.get("uuid") as? String)!! + + if (!mapToType.containsKey(dataTypeKey)) { + Log.w("FLUTTER_HEALTH::ERROR", "Datatype $dataTypeKey not found in HC") + result.success(false) + return + } + + val classType = mapToType[dataTypeKey]!! + + scope.launch { + try { + healthConnectClient.deleteRecords( + recordType = classType, + recordIdsList = listOf(uuid), + clientRecordIdsList = emptyList() + ) + result.success(true) + Log.i( + "FLUTTER_HEALTH::SUCCESS", + "[Health Connect] Record with UUID $uuid was successfully deleted!" + ) + } catch (e: Exception) { + Log.e("FLUTTER_HEALTH::ERROR", "Error deleting record with UUID: $uuid") + Log.e("FLUTTER_HEALTH::ERROR", e.message ?: "unknown error") + Log.e("FLUTTER_HEALTH::ERROR", e.stackTraceToString()) + result.success(false) + } + } + } + + + private val mapSleepStageToType = hashMapOf( 0 to SLEEP_UNKNOWN, diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart index b04b0eb7a..ccb27e727 100644 --- a/packages/health/example/lib/main.dart +++ b/packages/health/example/lib/main.dart @@ -430,6 +430,26 @@ class HealthAppState extends State { ); } + // To delete a record by UUID - call the `health.deleteByUUID` method: + /** + List healthData = await health.getHealthDataFromTypes( + types: [HealthDataType.STEPS], + startTime: startDate, + endTime: endDate, + ); + + if (healthData.isNotEmpty) { + print("DELETING: ${healthData.first.toJson()}"); + String uuid = healthData.first.uuid; + + success &= await health.deleteByUUID( + type: HealthDataType.STEPS, + uuid: uuid, + ); + + } + */ + setState(() { _state = success ? AppState.DATA_DELETED : AppState.DATA_NOT_DELETED; }); diff --git a/packages/health/ios/Classes/SwiftHealthPlugin.swift b/packages/health/ios/Classes/SwiftHealthPlugin.swift index f9a762d06..8b4fa1bbf 100644 --- a/packages/health/ios/Classes/SwiftHealthPlugin.swift +++ b/packages/health/ios/Classes/SwiftHealthPlugin.swift @@ -301,6 +301,11 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { else if call.method.elementsEqual("delete") { try! delete(call: call, result: result) } + + /// Handle deleteByUUID data + else if call.method.elementsEqual("deleteByUUID") { + try! deleteByUUID(call: call, result: result) + } } func checkIfHealthDataAvailable(call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -766,6 +771,45 @@ public class SwiftHealthPlugin: NSObject, FlutterPlugin { HKHealthStore().execute(deleteQuery) } + + func deleteByUUID(call: FlutterMethodCall, result: @escaping FlutterResult) throws { + guard let arguments = call.arguments as? NSDictionary, + let uuidarg = arguments["uuid"] as? String, + let dataTypeKey = arguments["dataTypeKey"] as? String else { + throw PluginError(message: "Invalid Arguments - UUID or DataTypeKey invalid") + } + let dataTypeToRemove = dataTypeLookUp(key: dataTypeKey) + guard let uuid = UUID(uuidString: uuidarg) else { + result(false) + return + } + let predicate = HKQuery.predicateForObjects(with: [uuid]) + + let query = HKSampleQuery( + sampleType: dataTypeToRemove, + predicate: predicate, + limit: 1, + sortDescriptors: nil + ) { query, samplesOrNil, error in + guard let samples = samplesOrNil, !samples.isEmpty else { + DispatchQueue.main.async { + result(false) + } + return + } + + self.healthStore.delete(samples) { success, error in + if let error = error { + print("Error deleting sample with UUID \(uuid): \(error.localizedDescription)") + } + DispatchQueue.main.async { + result(success) + } + } + } + + healthStore.execute(query) + } func getData(call: FlutterMethodCall, result: @escaping FlutterResult) { let arguments = call.arguments as? NSDictionary diff --git a/packages/health/lib/src/health_plugin.dart b/packages/health/lib/src/health_plugin.dart index c734533f0..0c7bae82b 100644 --- a/packages/health/lib/src/health_plugin.dart +++ b/packages/health/lib/src/health_plugin.dart @@ -508,6 +508,38 @@ class Health { return success ?? false; } + /// Deletes a specific health record by its UUID. + /// + /// Returns true if successful, false otherwise. + /// + /// Parameters: + /// * [uuid] - The UUID of the health record to delete. + /// * [type] - The health data type of the record. Required on iOS. + /// + /// On Android, only the UUID is required. On iOS, both UUID and type are required. + Future deleteByUUID({ + required String uuid, + HealthDataType? type, + }) async { + await _checkIfHealthConnectAvailableOnAndroid(); + + if (uuid.isEmpty || uuid == "") { + throw ArgumentError("UUID must not be empty."); + } + + if (Platform.isIOS && type == null) { + throw ArgumentError("On iOS, both UUID and type are required to delete a record."); + } + + Map args = { + 'uuid': uuid, + 'dataTypeKey': type?.name, + }; + + bool? success = await _channel.invokeMethod('deleteByUUID', args); + return success ?? false; + } + /// Saves a blood pressure record. /// /// Returns true if successful, false otherwise.