{"id":28089148,"url":"https://github.com/jozzzzep/prf","last_synced_at":"2025-05-13T12:54:13.129Z","repository":{"id":288350960,"uuid":"967759578","full_name":"jozzzzep/prf","owner":"jozzzzep","description":"Easily save and load values locally. Effortless local persistence with type safety and zero boilerplate. Just get, set, and go.","archived":false,"fork":false,"pushed_at":"2025-05-12T14:35:39.000Z","size":273,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-12T15:00:09.853Z","etag":null,"topics":["dart","flutter","localstorage","persistence","persistent-storage","sharedpreferences"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/prf","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jozzzzep.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-17T00:44:33.000Z","updated_at":"2025-05-12T14:07:11.000Z","dependencies_parsed_at":"2025-04-17T13:34:57.362Z","dependency_job_id":"bc1393b8-5eae-40ed-a781-e68c018dc50c","html_url":"https://github.com/jozzzzep/prf","commit_stats":null,"previous_names":["jozzzzep/prf"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jozzzzep%2Fprf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jozzzzep%2Fprf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jozzzzep%2Fprf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jozzzzep%2Fprf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jozzzzep","download_url":"https://codeload.github.com/jozzzzep/prf/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253948328,"owners_count":21988953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","flutter","localstorage","persistence","persistent-storage","sharedpreferences"],"created_at":"2025-05-13T12:54:12.566Z","updated_at":"2025-05-13T12:54:13.105Z","avatar_url":"https://github.com/jozzzzep.png","language":"Dart","funding_links":["https://buymeacoffee.com/yosefd99v"],"categories":[],"sub_categories":[],"readme":"![img](https://i.imgur.com/pAUltto.png)\n\n\u003ch3 align=\"center\"\u003e\u003ci\u003eDefine. Get. Set. Done.\u003c/i\u003e\u003c/h3\u003e\n\u003cp align=\"center\"\u003e\n        \u003cimg src=\"https://img.shields.io/codefactor/grade/github/jozzzzep/prf/main?style=flat-square\"\u003e\n        \u003cimg src=\"https://img.shields.io/github/license/jozzzzep/prf?style=flat-square\"\u003e\n        \u003cimg src=\"https://img.shields.io/pub/points/prf?style=flat-square\"\u003e\n        \u003cimg src=\"https://img.shields.io/pub/v/prf?style=flat-square\"\u003e\n        \n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://buymeacoffee.com/yosefd99v\" target=\"https://buymeacoffee.com/yosefd99v\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Buy%20me%20a%20coffee-Support (:-blue?logo=buymeacoffee\u0026style=flat-square\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nNo boilerplate. No repeated strings. No setup. Define your variables once, then `get()` and `set()` them anywhere with zero friction. `prf` makes local persistence faster, simpler, and easier to scale, with 20+ built-in types and a clean, type-safe API. Designed to fully replace raw use of `SharedPreferences`.\n\n#### Table of Contents\n\n- [**Introduction**](#-define--get--set--done)\n- [Why Use `prf`?](#-why-use-prf)\n- [**SharedPreferences** vs `prf`](#-sharedpreferences-vs-prf)\n- [Setup \u0026 Basic Usage (Step-by-Step)](#-setup--basic-usage-step-by-step)\n- [Available Methods and Supported Types](#-available-methods-and-supported-types)\n- [Accessing `prf` Without async](#-accessing-prf-without-async)\n- [Migrating from _SharedPreferences_ to `prf`](#-migrating-from-sharedpreferences-to-prf)\n- [Recommended Companion Packages](#-recommended-companion-packages)\n- [Why `prf` Wins in Real Apps](#-why-prf-wins-in-real-apps)\n- [Adding Custom (Advanced)](#️-how-to-add-custom-prf-types-advanced)\n\n# ⚡ Define → Get → Set → Done\n\nJust define your variable once — no strings, no boilerplate:\n\n```dart\nfinal username = Prf\u003cString\u003e('username');\n```\n\nThen get it:\n\n```dart\nfinal value = await username.get();\n```\n\nOr set it:\n\n```dart\nawait username.set('Joey');\n```\n\nThat’s it. You're done. Works out of the box with all of these:\n\n- `bool` `int` `double` `String` `num` `Duration` `DateTime` `BigInt` `Uri` `Uint8List` (binary)\n- Also lists `List\u003cString\u003e` `List\u003cint\u003e` `List\u003c***\u003e` of all supported types!\n- [JSON \u0026 enums](#-available-methods-and-supported-types)\n\n\u003e All supported types use efficient binary encoding under the hood for optimal performance and minimal storage footprint — no setup required. Just use `Prf\u003cT\u003e` with any listed type, and everything works seamlessly.\n\n---\n\n### 🔥 Why Use `prf`\n\nWorking with `SharedPreferences` often leads to:\n\n- Repeated string keys\n- Manual casting and null handling\n- Verbose async boilerplate\n- Scattered, hard-to-maintain logic\n\n`prf` solves all of that with a **one-line variable definition** that’s **type-safe**, **cached**, and **instantly usable** throughout your app. No key management, no setup, no boilerplate, no `.getString(...)` everywhere.\n\n---\n\n### What Sets `prf` Apart?\n\n- ✅ **Single definition** — just one line to define, then reuse anywhere\n- ✅ **Type-safe** — no casting, no runtime surprises\n- ✅ **Automatic caching** — with `Prf\u003cT\u003e` for fast access\n- ✅ **Easy isolate safety** — with `.isolated`\n- ✅ **Lazy initialization** — no need to call `SharedPreferences.getInstance()` or anything.\n- ✅ **Supports more than just primitives** — [20+ types](#-available-methods-and-supported-types), `Enums` \u0026 `JSON`\n- ✅ **Built for testing** — easily reset, override, or mock storage\n- ✅ **Cleaner codebase** — no more scattered `prefs.get...()` or typo-prone string keys\n\n---\n\n### 🔁 `SharedPreferences` vs `prf`\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\n| Feature                         | `SharedPreferences` (raw)                                            | `prf`                                                                                                 |\n| ------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |\n| **Define Once, Reuse Anywhere** | ❌ Manual strings everywhere                                         | ✅ One-line variable definition                                                                       |\n| **Type Safety**                 | ❌ Requires manual casting                                           | ✅ Fully typed, no casting needed                                                                     |\n| **Supports Advanced Types**     | ❌ No - only **5** types.                                            | ✅ Built-in support for `20+ types` and supports `enums` \u0026 `JSON`                                     |\n| **Readability**                 | ❌ Repetitive and verbose                                            | ✅ Clear, concise, expressive                                                                         |\n| **Centralized Keys**            | ❌ You manage key strings                                            | ✅ Keys are defined as variables                                                                      |\n| **Lazy Initialization**         | ❌ Must await `getInstance()` manually                               | ✅ Internally managed                                                                                 |\n| **Supports Primitives**         | ✅ Yes                                                               | ✅ Yes                                                                                                |\n| **Isolate \u0026 Caching**           | ⚠️ Partial — must manually choose between caching or no-caching APIs | ✅ Just `.isolate` for full isolate-safety\u003cbr\u003e✅ `Prf\u003cT\u003e` for faster cached access (not isolate-safe) |\n\n### 📌 Code Comparison\n\n**Using `SharedPreferences`:**\n\n```dart\nfinal prefs = await SharedPreferences.getInstance();\nawait prefs.setString('username', 'Joey');\nfinal username = prefs.getString('username') ?? '';\n```\n\n**Using `prf` with cached access (`Prf\u003cT\u003e`):**\n\n```dart\nfinal username = Prf\u003cString\u003e('username');\nawait username.set('Joey');\nfinal name = await username.get();\n```\n\n**Using `prf` with isolate-safe access (`PrfIso\u003cT\u003e`):**\n\n```dart\nfinal username = Prf\u003cString\u003e('username').isolated;\nawait username.set('Joey');\nfinal name = await username.get();\n```\n\nIf you're tired of:\n\n- Duplicated string keys\n- Manual casting and null handling\n- Scattered async boilerplate\n\nThen `prf` is your drop-in solution for **fast, safe, scalable, and elegant local persistence**.\n\n# 🚀 Setup \u0026 Basic Usage (Step-by-Step)\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\n### Step 1: Add `prf` to your `pubspec.yaml`\n\n```yaml\ndependencies:\n  prf: ^latest\n```\n\nThen run:\n\n```bash\nflutter pub get\n```\n\n---\n\n### Step 2: Define Your Variable\n\nYou only need **one line** to create a saved variable.  \nFor example, to save how many coins a player has:\n\n```dart\nfinal playerCoins = Prf\u003cint\u003e('player_coins');\n```\n\n\u003e This means:\n\u003e\n\u003e - You're saving an `int` (number)\n\u003e - The key is `'player_coins'`\n\n---\n\n### Step 3: Save a Value\n\nTo give the player 100 coins:\n\n```dart\nawait playerCoins.set(100);\n```\n\n---\n\n### Step 4: Read the Value\n\nTo read how many coins the player has:\n\n```dart\nfinal coins = await playerCoins.get();\n```\n\n```dart\nprint('Coins: $coins'); // 100\n```\n\nThat’s it! 🎉 You don’t need to manage string keys or setup anything. Just define once, then use anywhere in your app.\n\n---\n\n### Step 5 (Optional): Use `.prf\u003cT\u003e()` Shortcut\n\nInstead of defining the key explicitly, you can use the `.prf\u003cT\u003e()` extension on a string:\n\n```dart\nfinal playerCoins = 'player_coins'.prf\u003cint\u003e();\n```\n\nFrom there it behave the same as defining using `Prf\u003cT\u003e`\n\n```dart\nawait playerCoins.set(100);\nfinal coins = await playerCoins.get();\n```\n\n```dart\nprint('Coins: $coins');\n```\n\nThis works exactly the same — just a stylistic preference if you like chaining on string keys.\n\n# 📖 Available Methods and Supported Types\n\n\u003e [⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\n---\n\n### ✅ All `Prf\u003cT\u003e` types support these `methods` out of the box\n\n- **`get()`** → returns the current value (cached or from disk)\n- **`set(value)`** → saves the value and updates the cache (if applicable)\n- **`remove()`** → deletes the value from storage (and cache if applicable)\n- **`isNull()`** → returns `true` if the value is `null`\n- **`getOrFallback(fallback)`** → returns the value or a fallback if `null`\n- **`existsOnPrefs()`** → checks if the key exists in storage\n- **`getOrDefault()`** → returns the value, or throws if no value exists and no default is defined (safe alternative to assuming non-null values)\n\n---\n\n### 📦 Supported `Types`:\n\n```dart\nfinal someData = Prf\u003cT\u003e('key');\n```\n\nAll of these work automatically **(practically every type)**:\n\n- `bool`, `int`, `double`, `num`, `String`, `Duration`, `DateTime`, `Uri`, `BigInt`, `Uint8List` (binary)\n- `List\u003cbool\u003e`, `List\u003cint\u003e`, `List\u003cString\u003e`, `List\u003cdouble\u003e`, `List\u003cnum\u003e`, `List\u003cDateTime\u003e`, `List\u003cDuration\u003e`, `List\u003cUint8List\u003e`, `List\u003cUri\u003e`, `List\u003cBigInt\u003e`\n\n\u003e All supported types use efficient binary encoding under the hood for optimal performance and minimal storage footprint — no setup required. Just use `Prf\u003cT\u003e` and everything works seamlessly.\n\n---\n\n### 🔧 Specialized Types - `Enums` \u0026 `JSON`\n\nFor enums and custom models, use the built-in factory helpers:\n\n- `Prf.enumerated\u003cT\u003e()` → enum value\n- `Prf.enumeratedList\u003cT\u003e()` → list of enum values\n- `Prf.json\u003cT\u003e()` → custom model object\n- `Prf.jsonList\u003cT\u003e()` → list of custom model objects\n\n---\n\n#### 🛰 Need Isolate Safety?\n\nEvery `Prf` object supports the `.isolated` getter — no matter the type (enums, bytes, JSON, lists, etc).  \nIt returns a `PrfIso` that works safely across isolates (no caching, always reads from disk).\n\nThese are practically the same:\n\n```dart\nfinal safeUser = Prf\u003cString\u003e('username').isolated; // Same\nfinal safeUser = PrfIso\u003cString\u003e('username');       // Same\n```\n\n---\n\n### 🎯 Example: Persisting an `Enum`\n\nDefine your enum:\n\n```dart\nenum AppTheme { light, dark, system }\n```\n\nStore it using `Prf.enumerated` (cached) or `PrfIso.enumerated` (isolate-safe):\n\n```dart\nfinal appTheme = Prf.enumerated\u003cAppTheme\u003e(\n  'app_theme',\n  values: AppTheme.values,\n);\n```\n\nUsage:\n\n```dart\nfinal currentTheme = await appTheme.get(); // AppTheme.light / dark / system\nawait appTheme.set(AppTheme.dark);\n```\n\n### 📚 Persisting a `List` of `Enums`\n\nDefine your enum:\n\n```dart\nenum Permission { read, write, delete }\n```\n\nStore a list using `Prf.enumeratedList` (cached) or `PrfIso.enumeratedList` (isolate-safe):\n\n```dart\nfinal permissions = Prf.enumeratedList\u003cPermission\u003e(\n  'user_permissions',\n  values: Permission.values,\n);\n```\n\nUsage:\n\n```dart\nfinal current = await permissions.get(); // [Permission.read, Permission.write]\nawait permissions.set([Permission.read, Permission.delete]);\n```\n\n---\n\n### 🧠 Custom Types? No Problem\n\nWant to persist something more complex?  \nUse `Prf.json\u003cT\u003e()` or `PrfIso.json\u003cT\u003e()` with any model that supports `toJson` and `fromJson`:\n\n```dart\nfinal userData = Prf.json\u003cUser\u003e(\n  'user',\n  fromJson: (json) =\u003e User.fromJson(json),\n  toJson: (user) =\u003e user.toJson(),\n);\n\n```\n\n### 🧠 Complex Lists? Just Use `jsonList`\n\nFor model lists, use `Prf.jsonList\u003cT\u003e()` or `PrfIso.jsonList\u003cT\u003e()`:\n\n```dart\nfinal favoriteBooks = Prf.jsonList\u003cBook\u003e(\n  'favorite_books',\n  fromJson: (json) =\u003e Book.fromJson(json),\n  toJson: (book) =\u003e book.toJson(),\n);\n```\n\nUsage:\n\n```dart\nawait favoriteBooks.set([book1, book2]);\nfinal list = await favoriteBooks.get(); // List\u003cBook\u003e\n```\n\nNeed full control? You can create fully custom persistent types by:\n\n- Extending `CachedPrfObject\u003cT\u003e` (for cached access)\n- Or extending `BasePrfObject\u003cT\u003e` (for isolate-safe direct access)\n- And defining your own `PrfEncodedAdapter\u003cT\u003e` for custom serialization, compression, or encryption.\n\n# ⚡ Accessing `prf` Without Async\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\nIf you want instant, non-async access to a stored value, you can pre-load it into memory.\nUse `Prf.value\u003cT\u003e()` to create a `prf` object that automatically initializes and caches the value.\n\nExample:\n\n```dart\nfinal userScore = await Prf.value\u003cint\u003e('user_score');\n\n// Later, anywhere — no async needed:\nprint(userScore.cachedValue); // e.g., 42\n```\n\n- `Prf.value\u003cT\u003e()` reads the stored value once and caches it.\n- You can access `.cachedValue` instantly after initialization.\n- If no value was stored yet, `.cachedValue` will be the `defaultValue` or `null`.\n\n✅ Best for fast access inside UI widgets, settings screens, and forms.  \n⚠️ Not suitable for use across isolates — use `.isolated` or `PrfIso\u003cT\u003e` for isolate safety.\n\n### 🚀 Quick Summary\n\n- `await Prf.value\u003cT\u003e()` → loads and caches the value.\n- `.cachedValue` → direct, instant access afterward.\n- No async needed for future reads!\n\n---\n\n### 💡 Altervative - `.prf()` from String Keys\n\n```dart\nfinal username = 'username'.prf\u003cString\u003e();\nawait username.set('Joey');\nfinal name = await username.get();\n```\n\nIsolate-safe version:\n\n```dart\nfinal username = 'username'.prf\u003cString\u003e().isolated;\nawait username.set('Joey');\nfinal name = await username.get();\n```\n\n# 🔁 Migrating from SharedPreferences to `prf`\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\nWhether you're using the modern `SharedPreferencesAsync` or the legacy `SharedPreferences`, migrating to `prf` is simple and gives you cleaner, type-safe, and scalable persistence — without losing any existing data.\n\nIn fact, you can use `prf` with your current keys and values out of the box, preserving all previously stored data. But while backwards compatibility is supported, we recommend reviewing [all built-in types and usage](#-available-methods-and-supported-types) that `prf` provide — which may offer a cleaner, more powerful way to structure your logic going forward, without relying on legacy patterns or custom code.\n\n---\n\n### ✅ If you're already using `SharedPreferencesAsync`\n\nYou can switch to `prf` with **zero configuration** — just use the same keys.\n\n#### Before (`SharedPreferencesAsync`):\n\n```dart\nfinal prefs = SharedPreferencesAsync();\nawait prefs.setBool('dark_mode', true);\nfinal isDark = await prefs.getBool('dark_mode');\n```\n\n#### After (`prf`):\n\n```dart\nfinal darkMode = Prf\u003cbool\u003e('dark_mode');\nawait darkMode.set(true);\nfinal isDark = await darkMode.get();\n```\n\n- ✅ **As long as you're using the same keys and types, your data will still be there. No migration needed.**\n- 🧼 **Or — if you don't care about previously stored values**, you can start fresh and use `prf` types right away. They’re ready to go with clean APIs and built-in caching for all dart types, `enums`, `JSONs`, and more.\n\n---\n\n### ✅ If you're using the legacy `SharedPreferences` class\n\nYou can still switch to `prf` using the same keys:\n\n#### Before (`SharedPreferences`):\n\n```dart\nfinal prefs = await SharedPreferences.getInstance();\nawait prefs.setString('username', 'Joey');\nfinal name = prefs.getString('username');\n```\n\n#### After (`prf`):\n\n```dart\nfinal username = Prf\u003cString\u003e('username');\nawait username.set('Joey');\nfinal name = await username.get();\n```\n\n- ⚠️ `prf` uses **SharedPreferencesAsync**, which is isolate-safe, more robust — and **does not share data with the legacy `SharedPreferences` API**. The legacy API is **already planned for deprecation**, so [migrating](#️-if-your-app-is-already-in-production-using-sharedpreferences) away from it is strongly recommended.\n- ✅ If you're still in development, you can safely switch to `prf` now — saved values from before will not be accessible, but that's usually fine while iterating.\n\n\u003e The migration bellow automatically migrates old values into the new backend if needed.\n\u003e Safe to call multiple times — it only runs once.\n\n---\n\n### ⚠️ If your app is already in production using `SharedPreferences`\n\nIf your app previously used `SharedPreferences` (the legacy API), and you're now using `prf` (which defaults to `SharedPreferencesAsync`):\n\n- You **must run a one-time migration** to move your data into the new backend (especially on Android, where the storage backend switches to DataStore).\n\nRun this **before any reads or writes**, ideally at app startup:\n\n```dart\nawait PrfService.migrateFromLegacyPrefsIfNeeded();\n```\n\n\u003e This ensures your old values are migrated into the new system.\n\u003e It is safe to call multiple times — migration will only occur once.\n\n---\n\n### Summary\n\n| Case                                   | Do you need to migrate?     | Do your keys stay the same? |\n| -------------------------------------- | --------------------------- | --------------------------- |\n| Using `SharedPreferencesAsync`         | ❌ No migration needed      | ✅ Yes                      |\n| Using `SharedPreferences` (dev only)   | ❌ No migration needed      | ✅ Yes                      |\n| Using `SharedPreferences` (production) | ✅ Yes — run migration once | ✅ Yes                      |\n| Starting fresh                         | ❌ No migration, no legacy  | 🔄 You can pick new keys    |\n\nWith `prf`, you get:\n\n- 🚀 **Type-safe, reusable variables**\n- 🧠 **Cleaner architecture**\n- 🔄 **Built-in in-memory caching**\n- 🔐 **Isolate-safe behavior** with `SharedPreferencesAsync`\n- 📦 **Out-of-the-box support** for `20+ types`, `enums`, full `JSON` models and more\n\n# 🌟 Recommended Companion Packages\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\nIn addition to typed variables, `prf` connects seamlessly with **additional persistence power tools** — packages built specifically to extend the capabilities of `prf` into advanced real-world use cases.  \nThese tools offer plug-and-play solutions that carry over the same caching, async-safety, and persistence guarantees you expect from `prf`.\n\nPackages:  \n**`limit` package** → https://pub.dev/packages/limit  \n**`track` package** → https://pub.dev/packages/track\n\n- ⏲ **[`limit`](https://pub.dev/packages/limit)** — manage cooldowns and rate limits across sessions and isolates. Includes:\n\n  - **Cooldown** (fixed-time delays, e.g. daily rewards, retry timers)\n  - **RateLimiter** (token bucket rate limiting, e.g. 1000 actions per 15 minutes)\n\n- 🔥 **[`track`](https://pub.dev/packages/track)** — track progress, activity, and usage over time. Includes:\n\n  - **StreakTracker** (aligned streak tracking, e.g. daily habits)\n  - **HistoryTracker** (rolling lists of recent items with optional deduplication)\n  - **PeriodicCounter** (auto-reset counters per period, e.g. daily tasks)\n  - **RolloverCounter** (sliding-window counters, e.g. attempts per hour)\n  - **ActivityCounter** (detailed time-based activity stats)\n  - **BestRecord** (coming soon: track best performances or highscores)\n\n# 🔍 Why `prf` Wins in Real Apps\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\nWorking with `SharedPreferences` directly can quickly become **verbose, error-prone, and difficult to scale**. Whether you’re building a simple prototype or a production-ready app, clean persistence matters.\n\n### ❌ The Problem with Raw SharedPreferences\n\nEven in basic use cases, you're forced to:\n\n- Reuse raw string keys (risk of typos and duplication)\n- Manually cast and fallback every read\n- Handle async boilerplate (`getInstance`) everywhere\n- Encode/decode complex types manually\n- Spread key logic across multiple files\n\nLet’s see how this unfolds in practice.\n\n---\n\n### 👎 Example: Saving and Reading Multiple Values\n\n**Goal**: Save and retrieve a `username`, `isFirstLaunch`, and a `signupDate`.\n\n### SharedPreferences (verbose and repetitive)\n\n```dart\nfinal prefs = await SharedPreferences.getInstance();\n\n// Save values\nawait prefs.setString('username', 'Joey');\nawait prefs.setBool('is_first_launch', false);\nawait prefs.setString(\n  'signup_date',\n  DateTime.now().toIso8601String(),\n);\n\n// Read values\nfinal username = prefs.getString('username') ?? '';\nfinal isFirstLaunch = prefs.getBool('is_first_launch') ?? true;\nfinal signupDateStr = prefs.getString('signup_date');\nfinal signupDate = signupDateStr != null\n  ? DateTime.tryParse(signupDateStr)\n  : null;\n```\n\n🔻 **Issues:**\n\n- Repeated string keys — no compile-time safety\n- Manual fallback handling and parsing\n- No caching — every `.get` hits disk\n- Boilerplate increases exponentially with more values\n\n---\n\n### ✅ Example: Same Logic with `prf`\n\n```dart\nfinal username = Prf\u003cString\u003e('username');\nfinal isFirstLaunch = Prf\u003cbool\u003e('is_first_launch', defaultValue: true);\nfinal signupDate = Prf\u003cDateTime\u003e('signup_date');\n\n// Save\nawait username.set('Joey');\nawait isFirstLaunch.set(false);\nawait signupDate.set(DateTime.now());\n\n// Read\nfinal name = await username.get();         // 'Joey'\nfinal first = await isFirstLaunch.get();   // false\nfinal date = await signupDate.get();       // DateTime instance\n```\n\n💡 Defined once, used anywhere — fully typed, cached, and clean.\n\n---\n\n### 🤯 It Gets Worse with Models\n\nStoring a `User` model in raw `SharedPreferences` requires:\n\n1. Manual `jsonEncode` / `jsonDecode`\n2. Validation on read\n3. String-based key tracking\n\n### SharedPreferences with Model:\n\n```dart\n// Get SharedPreferences\nfinal prefs = await SharedPreferences.getInstance();\n// Encode to JSON\nfinal json = jsonEncode(user.toJson());\n// Set value\nawait prefs.setString('user_data', json);\n\n// Read\nfinal raw = prefs.getString('user_data');\nUser? user;\nif (raw != null) {\n  try {\n    // Decode JSON\n    final decoded = jsonDecode(raw);\n    // Convert to User\n    user = User.fromJson(decoded);\n  } catch (_) {\n    // fallback or error\n  }\n}\n```\n\n---\n\n### ✅ Same Logic with `prf`\n\n```dart\n// Define once\nfinal userData = Prf.json\u003cUser\u003e(\n  'user_data',\n  fromJson: User.fromJson,\n  toJson: (u) =\u003e u.toJson(),\n);\n\n// Save\nawait userData.set(user);\n\n// Read\nfinal savedUser = await userData.get(); // User?\n```\n\nFully typed. Automatically parsed. Fallback-safe. Reusable across your app.\n\n---\n\n### ⚙️ Built for Real Apps\n\n`prf` was built to eliminate the day-to-day pain of using SharedPreferences in production codebases:\n\n- ✅ Define once — reuse anywhere\n- ✅ Clean API — `get()`, `set()`, `remove()`, `isNull()` for all types\n- ✅ Supports `20+ types`, `enum`, `JSON`\n- ✅ Automatic caching — fast access after first read\n- ✅ Test-friendly — easily reset, mock, or inspect values\n\n---\n\n# 🛠️ How to Add Custom `prf` Types (Advanced)\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\nFor most use cases, you can simply use the built-in 20+ types or `Prf.enumerated\u003cT\u003e()`, `Prf.json\u003cT\u003e()` factories to persist enums and custom models easily. This guide is for advanced scenarios where you need full control over how a type is stored — such as custom encoding, compression, or special storage behavior.\n\nExpanding `prf` is simple:  \nJust create a custom adapter and treat your new type like any other!\n\n## 1. Create Your Class\n\n```dart\nclass Color {\n  final int r, g, b;\n  const Color(this.r, this.g, this.b);\n\n  Map\u003cString, dynamic\u003e toJson() =\u003e {'r': r, 'g': g, 'b': b};\n  factory Color.fromJson(Map\u003cString, dynamic\u003e json) =\u003e Color(\n    json['r'] ?? 0, json['g'] ?? 0, json['b'] ?? 0,\n  );\n}\n```\n\n### 2. Create an Adapter\n\n```dart\nimport 'dart:convert';\nimport 'package:prf/prf.dart';\n\nclass ColorAdapter extends PrfEncodedAdapter\u003cColor, String\u003e {\n  @override\n  Color? decode(String? stored) =\u003e\n      stored == null ? null : Color.fromJson(jsonDecode(stored));\n\n  @override\n  String encode(Color value) =\u003e jsonEncode(value.toJson());\n}\n```\n\n### 3. Use It with `.prf()`\n\n\u003e 💡 **Hint:** When calling `.prf('key')` on an adapter, you **don’t need to specify `\u003cT\u003e`** — the type is already known from the adapter itself. This makes your key setup simple and type-safe without repetition.\n\n```dart\nfinal favoriteColor = ColorAdapter().prf('favorite_color');\n```\n\n```dart // Cached\nawait favoriteColor.set(Color(255, 0, 0));\nfinal color = await favoriteColor.get();\n\nprint(color?.r); // 255\n```\n\nFor isolate-safe persistence use `.prfIsolated()` or `.isolated`:\n\n```dart\nfinal safeColor = ColorAdapter().prfIsolated('favorite_color');  // Isolate-safe\nfinal safeColor = ColorAdapter().prf('favorite_color').isolated; // Isolate-safe       // Same\n```\n\n## Summary\n\n- Create your class.\n- Create a `PrfEncodedAdapter`.\n- Use `.prf()`.\n\n[⤴️ Back](#table-of-contents) -\u003e Table of Contents\n\n---\n\n## 🔗 License MIT © Jozz\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://buymeacoffee.com/yosefd99v\" target=\"https://buymeacoffee.com/yosefd99v\"\u003e\n    ☕ Enjoying this package? You can support it here.\n  \u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjozzzzep%2Fprf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjozzzzep%2Fprf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjozzzzep%2Fprf/lists"}