|
| 1 | +# ht_preferences_repository |
| 2 | + |
| 3 | + |
| 4 | +[](https://pub.dev/packages/very_good_analysis) |
| 5 | +[](https://polyformproject.org/licenses/free-trial/1.0.0) |
| 6 | + |
| 7 | +Repository for managing user preferences. This repository acts as an intermediary between the application's business logic (BLoCs) and the data source client (`HtPreferencesClient`). It handles potential client-level exceptions and enforces additional business rules, such as limiting the size of the headline reading history. |
| 8 | + |
| 9 | +## Overview |
| 10 | + |
| 11 | +This package provides a `HtPreferencesRepository` class designed to abstract the storage and retrieval of user preferences within a Flutter application. It sits on top of a `HtPreferencesClient` implementation (which handles the actual data persistence, e.g., using Firestore, local storage, etc.) and provides a clean, consistent API for managing various user settings and data. |
| 12 | + |
| 13 | +## Features |
| 14 | + |
| 15 | +* **Settings Management:** Get and set application, article, theme, feed, and notification settings. |
| 16 | +* **Bookmark Management:** Add, remove, and retrieve bookmarked headlines. |
| 17 | +* **Followed Items:** Manage lists of followed sources, categories, and event countries. |
| 18 | +* **Reading History:** Track headline reading history, automatically managing its size (defaults to 25 items). |
| 19 | +* **Error Handling:** Propagates specific exceptions (`PreferenceNotFoundException`, `PreferenceUpdateException`) from the client and wraps unexpected errors. |
| 20 | +* **Client Agnostic:** Works with any implementation of the `HtPreferencesClient` interface. |
| 21 | + |
| 22 | +## Getting Started |
| 23 | + |
| 24 | +Add the necessary packages to your `pubspec.yaml` file under dependencies: |
| 25 | + |
| 26 | +```yaml |
| 27 | +dependencies: |
| 28 | + flutter: |
| 29 | + sdk: flutter |
| 30 | + # This repository package |
| 31 | + ht_preferences_repository: |
| 32 | + git: |
| 33 | + url: https://github.com/headlines-toolkit/ht-preferences-repository.git |
| 34 | + # Optionally specify a ref (branch, tag, commit hash): |
| 35 | + # ref: main |
| 36 | + |
| 37 | + # The client interface (required by the repository) |
| 38 | + ht_preferences_client: |
| 39 | + git: |
| 40 | + url: https://github.com/headlines-toolkit/ht-preferences-client.git |
| 41 | + # ref: main |
| 42 | + |
| 43 | + # A client implementation (e.g., Firestore) |
| 44 | + ht_preferences_firestore: |
| 45 | + git: |
| 46 | + url: https://github.com/headlines-toolkit/ht-preferences-firestore.git |
| 47 | + # ref: main |
| 48 | + |
| 49 | + # Other dependencies your client might need (e.g., cloud_firestore) |
| 50 | + cloud_firestore: ^4.0.0 # Example version |
| 51 | +``` |
| 52 | +
|
| 53 | +Then run `flutter pub get`. |
| 54 | + |
| 55 | +## Usage |
| 56 | + |
| 57 | +Import the necessary packages and instantiate the repository with your chosen `HtPreferencesClient` implementation (e.g., `HtPreferencesFirestore`). You'll typically do this where you provide dependencies to your application (like in `main.dart` or using a dependency injection framework). |
| 58 | + |
| 59 | +```dart |
| 60 | +import 'package:flutter/material.dart'; // For ThemeMode example |
| 61 | +import 'package:cloud_firestore/cloud_firestore.dart'; // Firestore dependency |
| 62 | +import 'package:ht_preferences_client/ht_preferences_client.dart'; |
| 63 | +import 'package:ht_preferences_repository/ht_preferences_repository.dart'; |
| 64 | +import 'package:ht_preferences_firestore/ht_preferences_firestore.dart'; // Import the client implementation |
| 65 | +
|
| 66 | +// Assume firestoreInstance and userId are available in your app context |
| 67 | +// FirebaseFirestore firestoreInstance = FirebaseFirestore.instance; |
| 68 | +// String userId = 'some_user_id'; |
| 69 | +
|
| 70 | +void main() async { |
| 71 | + // --- Dependency Setup (Example) --- |
| 72 | + // In a real app, initialize Firebase, get user ID, etc. |
| 73 | + WidgetsFlutterBinding.ensureInitialized(); |
| 74 | + // await Firebase.initializeApp(...); |
| 75 | + final firestoreInstance = FirebaseFirestore.instance; // Replace with your actual instance |
| 76 | + const userId = 'test_user_123'; // Replace with actual user ID |
| 77 | +
|
| 78 | + // Instantiate the Firestore client |
| 79 | + final preferencesClient = HtPreferencesFirestore( |
| 80 | + firestore: firestoreInstance, |
| 81 | + userId: userId, |
| 82 | + ); |
| 83 | +
|
| 84 | + // Create the repository instance, injecting the client |
| 85 | + final preferencesRepository = HtPreferencesRepository( |
| 86 | + preferencesClient: preferencesClient, |
| 87 | + maxHistorySize: 50, // Optionally override default history size (25) |
| 88 | + ); |
| 89 | + // --- End Dependency Setup --- |
| 90 | +
|
| 91 | +
|
| 92 | + // --- Using the Repository --- |
| 93 | + try { |
| 94 | + // --- Settings --- |
| 95 | + // Get current app settings |
| 96 | + AppSettings currentAppSettings = await preferencesRepository.getAppSettings(); |
| 97 | + print('Current App Font Size: ${currentAppSettings.appFontSize}'); |
| 98 | + print('Current App Font Type: ${currentAppSettings.appFontType}'); |
| 99 | +
|
| 100 | + // Update app settings |
| 101 | + final newAppSettings = AppSettings( |
| 102 | + appFontSize: FontSize.large, |
| 103 | + appFontType: AppFontType.lato, |
| 104 | + ); |
| 105 | + await preferencesRepository.setAppSettings(newAppSettings); |
| 106 | + print('App settings updated.'); |
| 107 | +
|
| 108 | + // Get theme settings |
| 109 | + ThemeSettings currentThemeSettings = await preferencesRepository.getThemeSettings(); |
| 110 | + print('Current Theme Mode: ${currentThemeSettings.themeMode}'); |
| 111 | +
|
| 112 | + // Update theme settings |
| 113 | + final newThemeSettings = ThemeSettings( |
| 114 | + themeMode: AppThemeMode.dark, // Use enum from ht_preferences_client |
| 115 | + themeName: AppThemeName.blue, // Use enum from ht_preferences_client |
| 116 | + ); |
| 117 | + await preferencesRepository.setThemeSettings(newThemeSettings); |
| 118 | + print('Theme settings updated.'); |
| 119 | +
|
| 120 | + // --- Bookmarks --- |
| 121 | + final headline1 = Headline(id: 'h1', title: 'Example Headline 1', description: 'Desc 1'); |
| 122 | + final headline2 = Headline(id: 'h2', title: 'Example Headline 2', url: 'http://example.com'); |
| 123 | +
|
| 124 | + await preferencesRepository.addBookmarkedHeadline(headline1); |
| 125 | + print('Headline 1 bookmarked.'); |
| 126 | + await preferencesRepository.addBookmarkedHeadline(headline2); |
| 127 | + print('Headline 2 bookmarked.'); |
| 128 | +
|
| 129 | + List<Headline> bookmarks = await preferencesRepository.getBookmarkedHeadlines(); |
| 130 | + print('Current Bookmarks (${bookmarks.length}): ${bookmarks.map((h) => h.title).toList()}'); |
| 131 | +
|
| 132 | + await preferencesRepository.removeBookmarkedHeadline('h1'); |
| 133 | + print('Headline 1 removed from bookmarks.'); |
| 134 | + bookmarks = await preferencesRepository.getBookmarkedHeadlines(); |
| 135 | + print('Updated Bookmarks (${bookmarks.length}): ${bookmarks.map((h) => h.title).toList()}'); |
| 136 | +
|
| 137 | + // --- Followed Items --- |
| 138 | + final categoryTech = Category(id: 'cat-tech', name: 'Technology'); |
| 139 | + final categorySports = Category(id: 'cat-sports', name: 'Sports'); |
| 140 | + await preferencesRepository.setFollowedCategories([categoryTech, categorySports]); |
| 141 | + print('Followed categories set.'); |
| 142 | +
|
| 143 | + List<Category> followedCategories = await preferencesRepository.getFollowedCategories(); |
| 144 | + print('Followed Categories: ${followedCategories.map((c) => c.name).toList()}'); |
| 145 | +
|
| 146 | + // --- History --- |
| 147 | + final headline3 = Headline(id: 'h3', title: 'History Headline 3'); |
| 148 | + await preferencesRepository.addHeadlineToHistory(headline1); // Add h1 back for history |
| 149 | + await preferencesRepository.addHeadlineToHistory(headline2); |
| 150 | + await preferencesRepository.addHeadlineToHistory(headline3); |
| 151 | + print('Headlines added to history.'); |
| 152 | +
|
| 153 | + List<Headline> history = await preferencesRepository.getHeadlineReadingHistory(); |
| 154 | + print('Current History (${history.length} items): ${history.map((h) => h.title).toList()}'); |
| 155 | +
|
| 156 | + // Adding more items might prune the history based on maxHistorySize |
| 157 | + for (int i = 4; i < 60; i++) { |
| 158 | + await preferencesRepository.addHeadlineToHistory(Headline(id: 'h$i', title: 'History Headline $i')); |
| 159 | + } |
| 160 | + history = await preferencesRepository.getHeadlineReadingHistory(); |
| 161 | + print('History after adding more (${history.length} items - potentially pruned): ${history.map((h) => h.title).toList()}'); |
| 162 | +
|
| 163 | + await preferencesRepository.removeHeadlineToHistory('h2'); |
| 164 | + print('Headline 2 removed from history.'); |
| 165 | + history = await preferencesRepository.getHeadlineReadingHistory(); |
| 166 | + print('Updated History (${history.length} items): ${history.map((h) => h.title).toList()}'); |
| 167 | +
|
| 168 | +
|
| 169 | + } on PreferenceNotFoundException catch (e) { |
| 170 | + // Handle cases where settings/data haven't been set yet |
| 171 | + print('Error: Preference not found - ${e.message}'); |
| 172 | + } on PreferenceUpdateException catch (e) { |
| 173 | + // Handle errors during data fetching/saving |
| 174 | + print('Error: Failed to update preference - ${e.message}'); |
| 175 | + } catch (e) { |
| 176 | + print('An unexpected error occurred: $e'); |
| 177 | + } |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +## Error Handling |
| 182 | + |
| 183 | +The repository methods may throw the following exceptions originating from the `ht_preferences_client`: |
| 184 | + |
| 185 | +* `PreferenceNotFoundException`: Thrown when a requested preference or data item (like settings or history) cannot be found. |
| 186 | +* `PreferenceUpdateException`: Thrown when an operation to fetch or update a preference fails (e.g., network error, database error). |
| 187 | + |
| 188 | +It's recommended to wrap calls to the repository methods in `try-catch` blocks to handle these potential errors gracefully in your application's business logic layer (e.g., BLoCs). |
| 189 | + |
| 190 | +## Dependencies |
| 191 | + |
| 192 | +* **ht_preferences_client:** This package relies heavily on the `ht_preferences_client` interface package, which defines the contract for interacting with the underlying preference data source. You will need to provide an implementation of `HtPreferencesClient` when creating the `HtPreferencesRepository`. |
| 193 | + |
| 194 | +## License |
| 195 | + |
| 196 | +This package is licensed under the [PolyForm Free Trial License 1.0.0](LICENSE). See the [LICENSE](LICENSE) file for details. |
0 commit comments