skip to content

In Flutter, what is shared_preferences meant to store, which value types does it accept, and what should never go into it?

level: juniorimportance: must knowfreq 62%

answer

  1. small, non-critical settings
  2. int, double, bool, String, List<String>
  3. NSUserDefaults, DataStore, localStorage
  4. plain text on disk
  5. writes are not guaranteed durable

basics

~20 s

shared_preferences holds small, non-critical settings as int, double, bool, String or List<String>, backed by NSUserDefaults, Android DataStore or SharedPreferences, and localStorage on web. Never store secrets, large data or records you cannot afford to lose.

solid answer

~40 s

`shared_preferences` wraps each platform's simple key-value store — `NSUserDefaults` on iOS and macOS, DataStore Preferences or Android `SharedPreferences`, `localStorage` on the web, a file on Linux and Windows. It accepts only `int`, `double`, `bool`, `String` and `List<String>`. It is right for things like the chosen language code or an "onboarding done" flag. It is wrong for tokens or passwords, because values are stored unencrypted — that is `flutter_secure_storage`'s job; wrong for lists of records or big JSON blobs, which belong in SQLite or Drift; and wrong for anything critical, because the package itself says a write is not guaranteed to be on disk when its Future completes.

code

dart · 20 lines
dart
import 'package:shared_preferences/shared_preferences.dart';

class AppSettings {
  AppSettings(this._prefs);

  final SharedPreferencesAsync _prefs;

  static const _languageKey = 'languageCode';
  static const _onboardedKey = 'onboardingDone';

  Future<String?> languageCode() => _prefs.getString(_languageKey);

  Future<void> setLanguageCode(String code) =>
      _prefs.setString(_languageKey, code);

  Future<bool> onboardingDone() async =>
      await _prefs.getBool(_onboardedKey) ?? false;

  Future<void> markOnboarded() => _prefs.setBool(_onboardedKey, true);
}

go deeper

for a junior

Recall the five supported types and that the store is for small settings such as language or an onboarding flag, never tokens.

for a middle

Explain the backing store per platform, nullable getters with defaults, TypeError on the wrong getter, and why durability is not guaranteed.

for a senior

Show the judgement of placing each piece of app data — settings, secrets, records, blobs — in the right store and the cost of getting it wrong.

for a principal

Frame storage choice as a data-classification policy for the team, so sensitive or critical data never lands in a convenience store by default.

## What the plugin is `shared_preferences` is a Flutter plugin maintained in the flutter/packages repository (2.5 at the time of writing). It does not implement storage itself: it forwards to whatever simple key-value store each platform already has, through a federated set of platform packages. | Platform | Backing store | |---|---| | iOS, macOS | `NSUserDefaults` | | Android | DataStore Preferences by default for the newer APIs; Android `SharedPreferences` for the legacy API or when configured | | Web | `localStorage` | | Linux | a file in the XDG data directory | | Windows | a file in the roaming AppData directory | ## The supported types The API accepts exactly five value types, each with its own getter and setter: - `int` — `setInt` / `getInt` - `double` — `setDouble` / `getDouble`; on platforms without doubles the value is stored as a float, so do not expect full precision back - `bool` — `setBool` / `getBool` - `String` — `setString` / `getString` - `List<String>` — `setStringList` / `getStringList` Getters return a nullable type and give `null` for a missing key, so a default is supplied at the call site: `await prefs.getBool('onboardingDone') ?? false`. Reading a key with the wrong typed getter throws a `TypeError`. There is no support for maps, dates or custom objects; you can serialise them into a `String`, but needing to do that is often a sign the data belongs elsewhere. ## What belongs there The sweet spot is small, user-level settings that are cheap to lose and easy to recompute: - the user's chosen language, for example `'pt'` - whether onboarding has been completed - theme mode, a units preference, a dismissed-tip flag - the last selected tab or filter If a value vanishes, the worst outcome should be that the user sees onboarding again or picks a language twice. ## What does not belong there 1. **Secrets.** Access tokens, refresh tokens, passwords and API keys. The stores above are not encrypted by the plugin; on a rooted or jailbroken device, in a backup, or in the browser's storage inspector, the values are readable. Secrets go to `flutter_secure_storage`, which uses the Keychain and Android's keystore-backed encryption. 2. **Large data.** The Flutter cookbook says the store is not designed for large amounts of data. The legacy and cached APIs load their keys into memory up front, and every value crosses a platform channel, so a megabyte of JSON costs you on every start. 3. **Records and relational data.** A list of saved forms, a message history or anything you query belongs in `sqflite` or Drift, with transactions and migrations. 4. **Critical data.** The README states that data may be persisted asynchronously and that a completed write is no guarantee it reached disk, so the plugin must not be used for critical data. A crash right after a write can lose it. 5. **Files and blobs.** Images or downloads go to files in an app directory, not into a `String` preference. ## A concrete example An app remembers the language the user picked and whether they finished onboarding. Both are tiny, both are recoverable, and both are read at startup: a textbook fit. Their sign-in token, by contrast, is a secret and goes into secure storage even though it is "just a string" — the type fits, the protection does not. ## How interviewers probe it The junior-level answer lists the types and says "settings". The follow-ups test judgement: why not the token (not encrypted), why not the cart or the offline form queue (records need a database), and what happens if the app is killed right after a write (it may not have persisted). Knowing the backing store per platform helps explain each answer rather than reciting it.

  • Why not put the access token in shared_preferences if it is just a String?
    The type fits but the protection does not: the plugin stores values unencrypted in NSUserDefaults, DataStore or SharedPreferences files, or localStorage, where backups, rooted devices or dev tools can read them. Tokens belong in `flutter_secure_storage`, which uses the Keychain and keystore-backed encryption.
  • What does getBool return for a key that was never written, and how do you handle it?
    It returns `null` — every getter is nullable, and on `SharedPreferencesAsync` it is a `Future<bool?>`. Supply the default at the call site, for example `await prefs.getBool('onboardingDone') ?? false`, so the absence of a value has one clear meaning.

shared_preferences is the sticky note on the fridge: perfect for "language: Portuguese" or "tour done", wrong for your bank PIN, and no place to keep the family photo album.

saying these in an interview costs you the question

  • shared_preferences encrypts values, so it is fine for auth tokens.
  • You can store any object; the plugin serialises maps automatically.
  • Once setString's Future completes, the value is guaranteed to be on disk.
  • It is a good place for the app's list of saved records.
  • On iOS it writes to the Keychain.