From 554285d6159cf5b303ecf526f3dbdd21b0a2d994 Mon Sep 17 00:00:00 2001 From: rtokun Date: Tue, 19 Dec 2023 17:29:02 +0200 Subject: [PATCH] LMN-14303 | Client v2 - Run integrations queries in a small chunks --- packages/health/example/lib/main.dart | 7 +- packages/health/lib/src/health_factory.dart | 129 +++++++++++++++++--- 2 files changed, 116 insertions(+), 20 deletions(-) diff --git a/packages/health/example/lib/main.dart b/packages/health/example/lib/main.dart index 301799833..05d12f720 100644 --- a/packages/health/example/lib/main.dart +++ b/packages/health/example/lib/main.dart @@ -99,7 +99,12 @@ class _HealthAppState extends State { try { // fetch health data - List healthData = await health.getHealthDataFromTypes(yesterday, now, types); + List healthData = await health.getHealthDataFromTypes( + startTime: yesterday, + endTime: now, + types: types, + isPriorityQueue: false, + ); // save all the new data points (only the first 100) _healthDataList.addAll((healthData.length < 100) ? healthData : healthData.sublist(0, 100)); } catch (error) { diff --git a/packages/health/lib/src/health_factory.dart b/packages/health/lib/src/health_factory.dart index 42f31a12f..e45d6492e 100644 --- a/packages/health/lib/src/health_factory.dart +++ b/packages/health/lib/src/health_factory.dart @@ -1,5 +1,27 @@ part of health; +class Pair { + final A first; + final B second; + + const Pair(this.first, this.second); + + @override + String toString() => '$runtimeType: $first, $second'; +} + +class DataPointInput { + final HealthDataType type; + final Completer> completer; + final List> dateRanges; + + DataPointInput({ + required this.type, + required this.completer, + required this.dateRanges, + }); +} + /// Main class for the Plugin. /// /// The plugin supports: @@ -321,15 +343,16 @@ class HealthFactory { } /// Fetch a list of health data points based on [types]. - Future> getHealthDataFromTypes( - DateTime startTime, - DateTime endTime, - List types, - ) async { + Future> getHealthDataFromTypes({ + required DateTime startTime, + required DateTime endTime, + required List types, + required bool isPriorityQueue, + }) async { List dataPoints = []; for (var type in types) { - final result = await _prepareQuery(startTime, endTime, type); + final result = await _prepareQuery(startTime, endTime, type, isPriorityQueue); dataPoints.addAll(result); } @@ -337,7 +360,12 @@ class HealthFactory { } /// Prepares a query, i.e. checks if the types are available, etc. - Future> _prepareQuery(DateTime startTime, DateTime endTime, HealthDataType dataType) async { + Future> _prepareQuery( + DateTime startTime, + DateTime endTime, + HealthDataType dataType, + bool isPriorityQueue, + ) async { // Ask for device ID only once _deviceId ??= _platformType == PlatformType.ANDROID ? (await _deviceInfo.androidInfo).id @@ -348,23 +376,86 @@ class HealthFactory { throw HealthException(dataType, 'Not available on platform $_platformType'); } - return await _dataQuery(startTime, endTime, dataType); + return await _dataQuery(startTime, endTime, dataType, isPriorityQueue); } + List requests = []; + /// The main function for fetching health data - Future> _dataQuery(DateTime startTime, DateTime endTime, HealthDataType dataType) async { - final args = { - 'dataTypeKey': dataType.name, - 'dataUnitKey': _dataTypeToUnit[dataType]!.name, - 'startTimeSec': startTime.millisecondsSinceEpoch ~/ 1000, - 'endTimeSec': endTime.millisecondsSinceEpoch ~/ 1000, - }; - List? fetchedDataPoints = await _channel.invokeListMethod('getData', args); - if (fetchedDataPoints != null) { - return _parse(dataType: dataType, dataPoints: fetchedDataPoints); + Future> _dataQuery( + DateTime startTime, DateTime endTime, HealthDataType dataType, bool isPriorityQueue) async { + final _completer = new Completer>(); + final hours = _divideDateRangeIntoHours(startTime, endTime); + final dataPointInput = DataPointInput( + completer: _completer, + dateRanges: hours, + type: dataType, + ); + if (isPriorityQueue) { + requests.insert(0, dataPointInput); } else { - return []; + requests.add(dataPointInput); + } + + _processRequests(); + + return _completer.future; + } + + bool processing = false; + + Future _processRequests() async { + if (processing) { + return; + } + processing = true; + if (requests.isNotEmpty) { + final List result = []; + + final requestToProcess = requests.first; + + try { + for (var dates in requestToProcess.dateRanges) { + final args = { + 'dataTypeKey': requestToProcess.type.name, + 'dataUnitKey': _dataTypeToUnit[requestToProcess.type]!.name, + 'startTimeSec': dates.first.millisecondsSinceEpoch ~/ 1000, + 'endTimeSec': dates.second.millisecondsSinceEpoch ~/ 1000, + }; + + List? fetchedDataPoints = await _channel.invokeListMethod('getData', args); + if (fetchedDataPoints != null) { + result.addAll(_parse(dataType: requestToProcess.type, dataPoints: fetchedDataPoints)); + } else { + result.addAll([]); + } + } + + requestToProcess.completer.complete(result); + } catch (e, st) { + requestToProcess.completer.completeError(e, st); + } + } + processing = false; + await _processRequests(); + } + + /// Method accepting range of times, and returns a pairs of start and end times for each hour in the range. + /// For example, if the range is from 2021-01-01 10:00:00 to 2021-01-01 12:00:00, the method will return: + /// [ [2021-01-01 10:00:00, 2021-01-01 11:00:00], [2021-01-01 11:00:00, 2021-01-01 12:00:00] ] + /// If the difference between start time and end time is less than an hour, the method will return a single + List> _divideDateRangeIntoHours(DateTime startTime, DateTime endTime) { + final List> dateHours = []; + final int differenceInHours = endTime.difference(startTime).inHours; + for (int i = 0; i < differenceInHours; i++) { + final DateTime startHour = startTime.add(Duration(hours: i)); + final DateTime endHour = startTime.add(Duration(hours: i + 1)); + dateHours.add(Pair(startHour, endHour)); + } + if (dateHours.isEmpty) { + dateHours.add(Pair(startTime, endTime)); } + return dateHours; } List _parse({required HealthDataType dataType, required List dataPoints}) {