shared_preferences in Flutter: Simple Settings Done Right

Flutter

shared_preferences in Flutter is the simplest way to store small pieces of non-sensitive data—things like theme mode, onboarding flags, and the last selected tab. It’s part of a broader storage toolbox alongside Hive and flutter_secure_storage, which we compare in detail in Choosing Hive, shared_preferences, or flutter_secure_storage in Flutter.

Audience: Beginner–IntermediateTested on: Flutter 3.x, Dart 3.x, Android 14 / iOS 17, Web, macOS 14

What shared_preferences is (and is not)

shared_preferences is a small key–value storage API that persists primitive data types:

  • bool, int, double
  • String, List<String>

It is perfect for:

  • Dark mode on/off
  • “Don’t show again” checkboxes
  • Last used filters or sort order
  • Remembering simple onboarding progress

It is not a database and not secure storage. Tokens, passwords, and large JSON blobs do not belong here. For those, combine it with Hive and flutter_secure_storage as outlined in the comparison article above.

Installing shared_preferences

shared_preferences is an official Flutter plugin and works on mobile, web, and desktop.

# pubspec.yaml
dependencies:
  shared_preferences: ^latest

Then run:

flutter pub get

Creating a small preferences service

Instead of scattering string keys everywhere, wrap shared_preferences in a dedicated service. That keeps your keys centralized and easier to refactor.

// lib/services/app_prefs.dart
import 'package:shared_preferences/shared_preferences.dart';

class AppPrefs {
  AppPrefs(this._prefs);
  final SharedPreferences _prefs;

  static const _keyIsDarkMode = 'isDarkMode';
  static const _keyHasSeenOnboarding = 'hasSeenOnboarding';
  static const _keyLastEmail = 'lastEmail';

  static Future&lt;AppPrefs&gt; load() async {
    final prefs = await SharedPreferences.getInstance();
    return AppPrefs(prefs);
  }

  bool get isDarkMode =&gt; _prefs.getBool(_keyIsDarkMode) ?? false;
  Future&lt;bool&gt; setDarkMode(bool value) =&gt; _prefs.setBool(_keyIsDarkMode, value);

  bool get hasSeenOnboarding =&gt; _prefs.getBool(_keyHasSeenOnboarding) ?? false;
  Future&lt;bool&gt; setHasSeenOnboarding(bool value) =&gt;
      _prefs.setBool(_keyHasSeenOnboarding, value);

  String? get lastEmail =&gt; _prefs.getString(_keyLastEmail);
  Future&lt;bool&gt; setLastEmail(String value) =&gt;
      _prefs.setString(_keyLastEmail, value);
}

Using preferences in your widgets

Load AppPrefs at startup, then inject it via your DI or state management library (Provider, Riverpod, GetIt, etc.).

// main.dart (simplified)
Future&lt;void&gt; main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await AppPrefs.load();

  runApp(MyApp(prefs: prefs));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key, required this.prefs});
  final AppPrefs prefs;

  @override
  Widget build(BuildContext context) {
    final isDark = prefs.isDarkMode;
    return MaterialApp(
      theme: ThemeData(brightness: isDark ? Brightness.dark : Brightness.light),
      home: HomeScreen(prefs: prefs),
    );
  }
}
// In a settings screen
SwitchListTile(
  title: const Text('Dark mode'),
  value: prefs.isDarkMode,
  onChanged: (value) async {
    await prefs.setDarkMode(value);
    // Then trigger a rebuild via your state management solution
  },
);

Async vs sync: when is it persisted?

All write methods on shared_preferences (setBool, setString, etc.) return Future<bool>. The data is also cached in memory, so reads are fast.

  • Reads: cheap and synchronous after the initial getInstance().
  • Writes: asynchronous; always await them in important flows (for example, finishing onboarding) so you don’t lose data if the app kills immediately.

Common patterns with shared_preferences

Onboarding and “don’t show again” flags

Future&lt;void&gt; completeOnboarding(AppPrefs prefs) async {
  await prefs.setHasSeenOnboarding(true);
}

On bootstrap, route users based on this flag.

Remember last used filters

Store IDs or enum names as strings. Avoid serializing whole filter objects.

// Example: sort order
enum SortOrder { newest, oldest }

extension SortOrderExt on SortOrder {
  String get storageKey =&gt; name;
  static SortOrder fromStorage(String? value) {
    return SortOrder.values.firstWhere(
      (e) =&gt; e.name == value,
      orElse: () =&gt; SortOrder.newest,
    );
  }
}

Clearing preferences on logout

For many apps, logging out should reset non-critical preferences that are tied to the user, such as last email and onboarding state.

Future&lt;void&gt; onLogout(AppPrefs prefs) async {
  await prefs.setLastEmail('');
  await prefs.setHasSeenOnboarding(false);
  // Keep some truly global settings, like theme, if you want.
}

For full wipes you can call prefs.clear(), but be careful not to remove global flags you still care about.

Security & pitfalls

  • Never store secrets. shared_preferences is not encrypted. Tokens, passwords, and keys belong in secure storage (see the storage comparison article for guidance).
  • Not a database. Avoid stuffing large JSON blobs or full object graphs into preferences. For structured or larger data, use Hive or another database.
  • Key sprawl. Hard-coded strings in many files make refactors painful. Keep all keys in one service or constants file.
  • Over-aggressive clearing. Calling clear() can wipe analytics opt-in flags or one-time dismissals. Consider a “per user” subset of keys to reset instead.

FAQ

Q: Can I use shared_preferences on the web?

A: Yes. On web it uses local storage under the hood. Just remember it’s still not secure and can be cleared by the browser.

Q: Is there a size limit?

A: It’s intended for small values. While there’s no strict Dart API limit, platforms may enforce practical limits. If you’re storing megabytes of data, you’re misusing it—use Hive or another database instead.

Q: Should I wrap it with a reactive layer?

A: For most apps, it’s enough to update your state management layer when you write preferences. If you need live updates from outside the app (rare), consider an abstraction that exposes a stream when you change values.

Conclusion

shared_preferences in Flutter shines when you keep it small and focused: a few booleans, strings, and simple lists that shape the user experience but don’t need strong security. Combine it with Hive for structured data and flutter_secure_storage for secrets—as described in the comparison article—to build a clean, layered storage strategy that scales with your app.

Updated: 2025-11-12

Comment

Copied title and URL