SkillAgentSearch skills...

Cardoteka

The best type-safe wrapper over SharedPreferences. ⭐ Why so? -> strongly typed cards for access to storage -> don't think about type, use get|set -> can work with nullable values -> callback based updates

Install / Use

/learn @PackRuble/Cardoteka
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<a href="https://github.com/PackRuble/cardoteka/"><img src="https://github.com/PackRuble/cardoteka/blob/dev/res/cardoteka_banner.png?raw=true"/></a>

Cardoteka

[![telegram_badge]][telegram_link] [![pub_badge]][pub_link] [![pub_likes]][pub_link] [![codecov_badge]][codecov_link] [![license_badge]][license_link] [![code_size_badge]][repo_link] [![repo_star_badge]][repo_link]

⭐️ The best type-safe wrapper over SharedPreferences.

Put a ![][pub_like_icon] on [Pub][pub_link] and favorite ⭐ on [Github][repo_link] to keep up with changes and not miss new releases!

Advantages

Why should I prefer to use cardoteka instead of the original shared_preferences? The reasons are as follows:

  • 🎈 Easy data retrieval synchronously (based on pre-caching) or asynchronously using Cardoteka and CardotekaAsync.
  • 🧭 Your keys and default values are stored in a systematic and organized manner. You don't have to think about where to stick them.
  • 🎼 Use get or set instead of a heap of getBool, setDouble, getInt, getStringList, setString... Think about the business logic of entities, not how to store or retrieve them.
  • 📞 Update state as soon as new data arrives in storage. No to code duplication - use Watcher.
  • 🧯 Have to frequently check the value for null before saving? Use the getOrNull and setOrNull methods and don't worry about anything!
  • 🚪 Do you still need access to dynamic methods or an SP instance from the original library? Just add the import package:cardoteka/access_to_sp.dart.

Table of contents

<!-- TOC --> <!-- TOC -->

How to use?

  1. Define your cards: specify the type to be stored and the default value (for default values with nullable support, be sure to specify generic type). Additionally, specify converters if the value type cannot be represented in the existing DataType enumeration:
import 'package:cardoteka/cardoteka.dart';
import 'package:flutter/material.dart' show ThemeMode;

enum AppSettings<T extends Object?> implements Card<T> {
  themeMode(DataType.string, ThemeMode.system),
  recentActivityList(DataType.stringList, <String>[]),
  isPremium(DataType.bool, false),
  feedCatAtAppointedTime<DateTime?>(DataType.int, null),
  ;

  const AppSettings(this.type, this.defaultValue);

  @override
  final DataType type;

  @override
  final T defaultValue;

  @override
  String get key => name;

  static const converters = <Card, Converter>{
    themeMode: EnumAsStringConverter(ThemeMode.values),
    feedCatAtAppointedTime: Converters.dateTimeAsInt,
  };
}
  1. Select the cardoteka class required in your case - Cardoteka (based on pre-caching) or CardotekaAsync for asynchronous data retrieval (see Sync or Async storage). For the Cardoteka class, perform initialization via Cardoteka.init and take advantage of all the features of your cardoteka: save, read, delete, listen to your saved data using typed cards:
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Cardoteka.init();
  final cardoteka = Cardoteka(
    config: const CardotekaConfig(
      name: 'settings',
      cards: SettingsCards.values,
      converters: SettingsCards.converters,
    ),
  );

  ThemeMode themeMode = cardoteka.get(SettingsCards.themeMode);
  print(themeMode); // will return default value -> ThemeMode.light

  await cardoteka.set(SettingsCards.themeMode, ThemeMode.dark);
  themeMode = cardoteka.get(SettingsCards.themeMode);
  print(themeMode); // ThemeMode.dark

  // you can use generic type to prevent possible errors when passing arguments
  // of different types
  await cardoteka.set<bool>(SettingsCards.isPremium, true);
  await cardoteka.set<Color>(SettingsCards.userColor, Colors.deepOrange);

  await cardoteka.remove(SettingsCards.themeMode);
  Map<Card<Object?>, Object> storedEntries = cardoteka.getStoredEntries();
  print(storedEntries);
  // {
  //   SettingsCards.userColor: Color(0xffff5722),
  //   SettingsCards.isPremium: true
  // }

  await cardoteka.removeAll();
  storedEntries = cardoteka.getStoredEntries();
  print(storedEntries); // {}
}

Don't worry! If you do something wrong, you will receive a detailed correction message in the console.

Materials

List of resources to learn more about the capabilities of this library:

Apps

Applications that use this library:

Analogy in SharedPreferencesWithCache and SharedPreferencesAsync

| SharedPreferencesWithCache or SharedPreferencesAsync | Method \ return signature | Cardoteka | CardotekaAsync | |----------------------------------------------------------|---------------------------|---------------------|-----------------------------| | get* | get | V | Future<V> | | — | getOrNull | V? | Future<V?> | | set* | set | Future<bool> | Future<bool> | | — | setOrNull | Future<bool> | Future<bool> | | remove | remove | Future<bool> | Future<bool> | | clear | removeAll | Future<bool> | Future<bool> | | containsKey | containsCard | bool | Future<bool> | | keys and getKeys | getStoredCards | Set<Card> | Future<Set<Card>> | | — | getStoredEntries | Map<Card, Object> | Future<Map<Card, Object>> | | reloadCache | reloadCache | Future<void> | — |

Sync or Async storage

The biggest difference between Cardoteka and CardotekaAsync is where the data is stored when the application is running. In the synchronous case, all data for all cards are loaded once into the device RAM after calling Cardoteka.init. This is why you can use methods such as get, getOrNull, containsCard, getStoredCards, getStoredEntries synchronously. It is also important to understand that if another service on the platform changes your data, you need to call Cardoteka.reloadCache to update it before retrieving it via a Cardoteka instance.

Things are different for CardotekaAsync because data is asynchronously requested from disk when any method is called. This is why initialization is not required in advance. And because of this, you get the most up-to-date data for any query.

But which one to use when? It's simple:

  • if your data is updated by another service (and you can't track it)
  • OR your data is too heavy (lists with instances of classes with a large number of fields are serialized)
  • OR synchronous reading is not that important to you

then feel free to use CardotekaAsync. Otherwise, use Cardoteka.

Saving null values

If your card can contain a null value, then use the getOrNull and setOrNull methods. It works like this:

  • getOrNull - if pair is absent in storage, we will get null
  • setOrNull - if we save null, the pair will be deleted from storage

Below is a table showing the compatibility of methods with cards:

| method | Card<Object> | Card<Object?> | |:-----------:|:-------------:|:-------------:| | get | ✅ |

View on GitHub
GitHub Stars4
CategoryData
Updated1mo ago
Forks0

Languages

Dart

Security Score

90/100

Audited on Feb 28, 2026

No findings