Skip to content

Commit 88823dc

Browse files
committed
* Let cache contain null to prevent fetching dates that does not exists
1 parent 81307cd commit 88823dc

File tree

5 files changed

+114
-85
lines changed

5 files changed

+114
-85
lines changed

lib/services/fitbit_service.dart

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class FitbitService extends IService {
122122
}
123123

124124
final String date = metrics.dateAsString;
125-
Map<String, Metrics> cacheMap = {};
125+
Map<String, Metrics?> cacheMap = {};
126126

127127
try {
128128
cacheMap = await storage.readCache(StorageKeys.cache);
@@ -131,7 +131,7 @@ class FitbitService extends IService {
131131
return cacheMap[date]!;
132132
}
133133
} catch (e) {
134-
_logger.e("Error reading cache: $e");
134+
_logger.e("Fetch - Error reading cache: $e for date: $date");
135135
}
136136

137137
_logger.d("Cache miss for date: $date");
@@ -151,31 +151,38 @@ class FitbitService extends IService {
151151
if (response.statusCode == 200) {
152152
final Map<String, dynamic> data = jsonDecode(response.body);
153153

154-
if (data.containsKey("weight") && data["weight"].isNotEmpty) {
155-
final List<Metrics> metricsList =
156-
(data['weight'] as List<dynamic>)
157-
.where((ret) => ret != null)
158-
.map<Metrics?>((ret) {
159-
try {
160-
final FitBitObject obj = toFitBitObject(ret);
161-
return obj.metrics;
162-
} catch (_) {
163-
return null;
164-
}
165-
})
166-
.where((ret) => ret != null)
167-
.map((ret) => ret!)
168-
.toList();
169-
170-
final Map<String, Metrics> metricsMap = {
171-
for (var metric in metricsList) metric.dateAsString: metric,
172-
};
154+
DateTime current = metrics.date;
155+
DateTime cutoff = DateTime(
156+
current.year,
157+
current.month - 1,
158+
current.day,
159+
);
173160

161+
Map<String, Metrics?> metricsMap = {};
162+
if (data.containsKey("weight") && data["weight"].isNotEmpty) {
163+
Metrics last = metrics;
164+
for (var ret in data['weight'] as List<dynamic>) {
165+
try {
166+
final FitBitObject obj = toFitBitObject(ret);
167+
metricsMap.putIfAbsent(
168+
obj.metrics.dateAsString,
169+
() => obj.metrics,
170+
);
171+
last = obj.metrics;
172+
} catch (_) {
173+
_logger.e("Error parsing FitBitObject: $ret");
174+
}
175+
}
176+
177+
while (current.isAfter(cutoff)) {
178+
final curDate = current.toIso8601String().split('T').first;
179+
// Placeholder for "failed to parse" or "no data"
180+
metricsMap.putIfAbsent(curDate, () => null);
181+
current = current.subtract(const Duration(days: 1));
182+
}
174183
cacheMap.addEntries(metricsMap.entries);
175184
await storage.writeCache(StorageKeys.cache, cacheMap);
176-
177-
// Weight data: {bmi: 21.95, date: 2025-03-10, fat: 15.17300033569336, logId: 1741594747000, source: Aria, time: 08:19:07, weight: 80.9}
178-
return metricsList.last;
185+
return last;
179186
}
180187
} // Else if token is expired
181188
else if (response.statusCode == 401) {
@@ -209,45 +216,56 @@ class FitbitService extends IService {
209216
return ratioPoints;
210217
}
211218

212-
final String date = Metrics.defaultMetrics().dateAsString;
213-
Map<String, Metrics> cacheMap = {};
219+
Metrics m = Metrics.defaultMetrics();
220+
final String date = m.dateAsString;
214221

215222
try {
216-
cacheMap = await storage.readCache(StorageKeys.cache);
217-
_logger.d("Cache hit for date: $date");
218-
219223
final int offset = switch (period) {
220224
MonthPeriod.one => 1,
221225
MonthPeriod.two => 2,
222226
MonthPeriod.three => 3,
223227
};
224228

225-
final DateTime cutoff = DateTime(
226-
DateTime.now().year,
227-
DateTime.now().month - offset,
228-
DateTime.now().day,
229+
DateTime current = m.date;
230+
DateTime cutoff = DateTime(
231+
current.year,
232+
current.month - offset,
233+
current.day,
229234
);
230-
final filtered = Map<String, Metrics>.from(cacheMap)
231-
..removeWhere((k, v) => DateTime.parse(k).isBefore(cutoff));
232235

233-
// Get newest Metrics from filtered map
234-
final end =
235-
filtered.entries
236-
.map((entry) => MapEntry(DateTime.parse(entry.key), entry.value))
237-
.reduce((a, b) => a.key.isAfter(b.key) ? a : b)
238-
.value;
236+
Map<String, Metrics?> cacheMap = await storage.readCache(
237+
StorageKeys.cache,
238+
);
239+
final Map<String, Metrics> filtered = {};
240+
while (current.isAfter(cutoff)) {
241+
final curDate = current.toIso8601String().split('T').first;
242+
if (cacheMap.containsKey(curDate)) {
243+
if (cacheMap[curDate] != null) {
244+
filtered[curDate] = cacheMap[curDate]!;
245+
}
246+
current = current.subtract(const Duration(days: 1));
247+
} else {
248+
await fetchMetrics(m.copyWith(date: current));
249+
cacheMap = await storage.readCache(StorageKeys.cache);
250+
}
251+
}
252+
253+
// Get the latest metrics from filtered cacheMap
254+
final Metrics latestDate =
255+
(filtered.values.toList()..sort((a, b) => a.date.compareTo(b.date)))
256+
.last;
239257

240258
BfpCalculator bfpCalculator = BfpCalculator();
241259
ratioPoints =
242260
filtered.entries.map((entry) {
243-
final change = end.difference(entry.value);
261+
final change = latestDate.difference(entry.value);
244262
final ratio = bfpCalculator.getRatio(change);
245263
return RatioPoint(DateTime.parse(entry.key), ratio);
246264
}).toList();
247265

248266
return ratioPoints;
249267
} catch (e) {
250-
_logger.e("Error reading cache: $e");
268+
_logger.e("Ratio - Error reading cache: $e for date: $date");
251269
}
252270

253271
_logger.d("Cache miss for date: $date");

lib/storage/prefs_storage.dart

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,25 @@ class PrefsStorage implements Storage {
2020
}
2121

2222
@override
23-
Future<void> writeCache(StorageKeys key, Map<String, Metrics> value) async {
24-
final DateTime cutoff = DateTime.now().subtract(Duration(days: 999));
25-
26-
// Create a mutable copy of the original map
27-
final filtered = Map<String, Metrics>.from(value)
28-
..removeWhere((k, v) => DateTime.parse(k).isBefore(cutoff));
29-
23+
Future<void> writeCache(StorageKeys key, Map<String, Metrics?> value) async {
3024
final prefs = await SharedPreferences.getInstance();
3125

32-
final encoded = jsonEncode(filtered.map((k, v) => MapEntry(k, v.toJson())));
26+
final DateTime cutoff = DateTime.now().subtract(Duration(days: 999));
27+
final filtered = Map<String, Metrics?>.from(value)
28+
..removeWhere((k, v) => DateTime.parse(k).isBefore(cutoff));
3329

34-
await prefs.setString(
35-
"${StorageKeys.cache.name}Count",
36-
filtered.length.toString(),
30+
final encoded = jsonEncode(
31+
filtered.map((k, v) {
32+
if (v != null) {
33+
return MapEntry(k, v.toJson());
34+
} else {
35+
return MapEntry(k, {"d": k, "x": true}); // Placeholder
36+
}
37+
}),
3738
);
3839

3940
await prefs.setString(key.name, encoded);
41+
await prefs.setInt("${StorageKeys.cache.name}Count", filtered.length);
4042
}
4143

4244
@override
@@ -53,21 +55,27 @@ class PrefsStorage implements Storage {
5355
}
5456

5557
@override
56-
Future<Map<String, Metrics>> readCache(StorageKeys key) async {
58+
Future<Map<String, Metrics?>> readCache(StorageKeys key) async {
5759
final prefs = await SharedPreferences.getInstance();
58-
String cacheMap = prefs.getString(key.name) ?? "";
59-
60-
if (cacheMap.isNotEmpty) {
61-
// Decode the JSON string into a Map<String, dynamic>
62-
Map<String, dynamic> jsonMap = jsonDecode(cacheMap);
6360

64-
// Convert the dynamic map to a Map<String, Metrics>
65-
return jsonMap.map((k, v) => MapEntry(k, Metrics.fromJson(v)));
61+
final str = prefs.getString(key.name);
62+
if (str == null || str.isEmpty) {
63+
return {};
6664
}
6765

68-
await prefs.setString("${StorageKeys.cache.name}Count", "0");
69-
70-
return {};
66+
try {
67+
final decoded = jsonDecode(str) as Map<String, dynamic>;
68+
69+
return decoded.map((k, v) {
70+
if (v is Map<String, dynamic> && v['x'] == true) {
71+
return MapEntry(k, null);
72+
} else {
73+
return MapEntry(k, Metrics.fromJson(v as Map<String, dynamic>));
74+
}
75+
});
76+
} catch (e) {
77+
return {};
78+
}
7179
}
7280

7381
@override

lib/storage/secure_storage.dart

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,25 @@ class SecureStorage implements Storage {
1919
}
2020

2121
@override
22-
Future<void> writeCache(StorageKeys key, Map<String, Metrics> value) async {
23-
// Ensure the data is encoded into JSON
22+
Future<void> writeCache(StorageKeys key, Map<String, Metrics?> value) async {
2423
final encoded = jsonEncode(
25-
value.map(
26-
(k, v) => MapEntry(k, v.toJson()),
27-
), // Convert each Metrics object to JSON
24+
value.map((k, v) {
25+
if (v != null) {
26+
return MapEntry(k, v.toJson());
27+
} else {
28+
return MapEntry(k, {
29+
"d": k,
30+
"x": true,
31+
}); // Use a raw map, not a string
32+
}
33+
}),
2834
);
2935

3036
await _storage.write(
3137
key: "${StorageKeys.cache.name}Count",
3238
value: value.length.toString(),
3339
);
3440

35-
// Store the encoded data in secure storage
3641
await _storage.write(key: key.name, value: encoded);
3742
}
3843

@@ -48,24 +53,24 @@ class SecureStorage implements Storage {
4853
}
4954

5055
@override
51-
Future<Map<String, Metrics>> readCache(StorageKeys key) async {
52-
// Read the raw string from storage
56+
Future<Map<String, Metrics?>> readCache(StorageKeys key) async {
5357
final str = await _storage.read(key: key.name);
5458
if (str == null || str.isEmpty) {
55-
return {}; // Return an empty map if no data found
59+
return {};
5660
}
5761

5862
try {
59-
// Decode the JSON string into a Map<String, dynamic>
6063
final decoded = jsonDecode(str) as Map<String, dynamic>;
6164

62-
// Convert each value in the map from a Map<String, dynamic> to a Metrics object
63-
return decoded.map(
64-
(k, v) => MapEntry(k, Metrics.fromJson(v as Map<String, dynamic>)),
65-
);
65+
return decoded.map((k, v) {
66+
if (v is Map<String, dynamic> && v['x'] == true) {
67+
return MapEntry(k, null); // Placeholder for "no data"
68+
} else {
69+
return MapEntry(k, Metrics.fromJson(v as Map<String, dynamic>));
70+
}
71+
});
6672
} catch (e) {
67-
// If there is an error during decoding, log it or handle the failure
68-
return {}; // Return an empty map in case of an error
73+
return {};
6974
}
7075
}
7176

lib/storage/storage.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ enum StorageKeys {
1414
abstract class Storage {
1515
Future<void> write(StorageKeys key, String value);
1616
Future<void> writeBool(StorageKeys key, bool value);
17-
Future<void> writeCache(StorageKeys key, Map<String, Metrics> value);
17+
Future<void> writeCache(StorageKeys key, Map<String, Metrics?> value);
1818
Future<String> read(StorageKeys key);
1919
Future<bool> readBool(StorageKeys key);
20-
Future<Map<String, Metrics>> readCache(StorageKeys key);
20+
Future<Map<String, Metrics?>> readCache(StorageKeys key);
2121
Future<void> delete(StorageKeys key);
2222
Future<void> clear();
2323
}

todo.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
* Let cache contain null to prevent fetching dates that does not exists
2-
* Be able to ignore cache
31
* Implement Kofi
42
* Implement Ads
53
* Move full auth flow to Firebase

0 commit comments

Comments
 (0)