Local Data Storage

SharedPreferences: Storing Simple Key-Value Data

15 min Lesson 2 of 12

SharedPreferences: Storing Simple Key-Value Data

Most apps need to persist small pieces of data between sessions: whether the user has completed onboarding, their chosen theme, a saved username, or a preference toggle. SharedPreferences is the standard Flutter solution for this use case. Backed by NSUserDefaults on iOS and SharedPreferences on Android, it offers a simple, asynchronous key-value store for primitive types.

Adding the Package

The shared_preferences package is not bundled with Flutter — you must add it to your project. Run the following command in your terminal from the project root:

flutter pub add shared_preferences

This updates pubspec.yaml and downloads the package. Then import it wherever you need it:

import 'package:shared_preferences/shared_preferences.dart';

Getting an Instance

You obtain a SharedPreferences instance by calling the static async factory SharedPreferences.getInstance(). Because it returns a Future, you must await it inside an async function. A common pattern is to load preferences inside initState of a StatefulWidget:

class SettingsPage extends StatefulWidget {
  const SettingsPage({super.key});

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool _darkMode = false;
  String _username = '';

  @override
  void initState() {
    super.initState();
    _loadPreferences();
  }

  Future<void> _loadPreferences() async {
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      _darkMode = prefs.getBool('dark_mode') ?? false;
      _username = prefs.getString('username') ?? '';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings')),
      body: Column(
        children: [
          Text('Hello, $_username'),
          Switch(
            value: _darkMode,
            onChanged: (value) async {
              final prefs = await SharedPreferences.getInstance();
              await prefs.setBool('dark_mode', value);
              setState(() => _darkMode = value);
            },
          ),
        ],
      ),
    );
  }
}
Note: SharedPreferences.getInstance() is inexpensive after the first call because the platform caches the instance. However, it is still asynchronous, so always await it. Never block the UI thread by calling it synchronously.

Reading and Writing Primitive Values

SharedPreferences supports five primitive types. Each type has a dedicated getter and setter pair:

  • StringgetString(key) / setString(key, value)
  • intgetInt(key) / setInt(key, value)
  • doublegetDouble(key) / setDouble(key, value)
  • boolgetBool(key) / getBool(key, value) / setBool(key, value)
  • List<String>getStringList(key) / setStringList(key, value)

All getters return null if the key does not exist, so always apply the null-coalescing operator (??) to supply a default value. All setters return Future<bool> indicating success — await them to be safe:

Future<void> saveUserData() async {
  final prefs = await SharedPreferences.getInstance();

  // Writing
  await prefs.setString('username', 'Edrees');
  await prefs.setInt('launch_count', 42);
  await prefs.setDouble('font_size', 16.5);
  await prefs.setBool('notifications_enabled', true);
  await prefs.setStringList('recent_searches', ['flutter', 'dart', 'firebase']);

  // Reading with null-safe defaults
  final username = prefs.getString('username') ?? 'Guest';
  final launches = prefs.getInt('launch_count') ?? 0;
  final fontSize = prefs.getDouble('font_size') ?? 14.0;
  final notifs = prefs.getBool('notifications_enabled') ?? true;
  final searches = prefs.getStringList('recent_searches') ?? [];

  print('$username has opened the app $launches times');
}

Removing Keys and Clearing All Data

You can remove a specific key or wipe the entire preferences store. Use remove(key) to delete one entry and clear() to erase everything — typically on logout:

Future<void> handleLogout() async {
  final prefs = await SharedPreferences.getInstance();

  // Remove a single key
  await prefs.remove('auth_token');

  // Or clear everything (use with caution)
  await prefs.clear();
}

// Check whether a key exists before reading
Future<void> checkOnboarding() async {
  final prefs = await SharedPreferences.getInstance();
  final hasSeenOnboarding = prefs.containsKey('onboarding_complete');
  if (!hasSeenOnboarding) {
    // Show onboarding flow
  }
}

Null-Safety Best Practices

Because every getter can return null, always guard reads with a default. For complex defaults or when the same preference is read in multiple places, encapsulate access in a dedicated service class rather than scattering ?? operators across your UI code:

class PreferencesService {
  static const _kDarkMode = 'dark_mode';
  static const _kUsername = 'username';
  static const _kLaunchCount = 'launch_count';

  Future<SharedPreferences> get _prefs => SharedPreferences.getInstance();

  Future<bool> getDarkMode() async =>
      (await _prefs).getBool(_kDarkMode) ?? false;

  Future<void> setDarkMode(bool value) async =>
      (await _prefs).setBool(_kDarkMode, value);

  Future<String> getUsername() async =>
      (await _prefs).getString(_kUsername) ?? 'Guest';

  Future<void> setUsername(String name) async =>
      (await _prefs).setString(_kUsername, name);

  Future<int> incrementLaunchCount() async {
    final p = await _prefs;
    final count = (p.getInt(_kLaunchCount) ?? 0) + 1;
    await p.setInt(_kLaunchCount, count);
    return count;
  }
}
Tip: Store all preference keys as static const strings in one place. This prevents typo-induced bugs where you write with one key name and read with a slightly different one, silently getting null back every time.
Warning: SharedPreferences is not encrypted on either platform. Never store sensitive data such as passwords, API tokens, or personally identifiable information (PII) in SharedPreferences. Use the flutter_secure_storage package for secrets that require encryption at rest.

Summary

SharedPreferences is the go-to solution for persisting small, non-sensitive user preferences and app state across sessions. Key points to remember:

  • Add the package with flutter pub add shared_preferences and import it.
  • Obtain the instance with await SharedPreferences.getInstance().
  • Use type-specific getters/setters for String, int, double, bool, and List<String>.
  • All getters return null when a key is absent — always supply a ?? default.
  • Encapsulate preferences access in a service class for cleaner, null-safe code.
  • Never store secrets; use flutter_secure_storage for sensitive data.