bible-tracker/lib/services/tracker_storage.dart
2026-04-20 15:13:58 -04:00

54 lines
1.9 KiB
Dart

import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/tracker.dart';
/// Persists the list of trackers to [SharedPreferences] as JSON.
///
/// A repository is justified here because storage is asynchronous, has
/// specialized queries (save/load all), and is consumed both by the service
/// layer and by tests with fake implementations.
class TrackerStorage {
TrackerStorage(this._prefs);
static const String _storageKey = 'bible_tracker.trackers.v1';
final SharedPreferences _prefs;
/// Synchronous because the underlying [SharedPreferences] getter already
/// works against an in-memory cache. Keeping this sync lets the service
/// start up with real data on the first frame, with no async-load race.
List<Tracker> loadAll() {
final String? raw = _prefs.getString(_storageKey);
if (raw == null || raw.isEmpty) {
return <Tracker>[];
}
final List<dynamic> decoded = jsonDecode(raw) as List<dynamic>;
return decoded
.cast<Map<String, dynamic>>()
.map(Tracker.fromJson)
.toList(growable: false);
}
Future<void> saveAll(List<Tracker> trackers) async {
final List<Map<String, dynamic>> json = trackers
.map((Tracker tracker) => tracker.toJson())
.toList(growable: false);
await _prefs.setString(_storageKey, jsonEncode(json));
}
}
/// Must be overridden at startup with an initialized [SharedPreferences].
final Provider<SharedPreferences> sharedPreferencesProvider =
Provider<SharedPreferences>(
(Ref ref) => throw UnimplementedError(
'sharedPreferencesProvider must be overridden before use',
),
);
final Provider<TrackerStorage> trackerStorageProvider =
Provider<TrackerStorage>(
(Ref ref) => TrackerStorage(ref.read(sharedPreferencesProvider)),
);