Flutter SharedPreferences Best Practices: A Complete Implementation Guide

Cache the SharedPreferences instance once via await SharedPreferences.getInstance(), use typed getters with default values, and centralize keys in a constants class to prevent runtime errors and unnecessary I/O.

The shared_preferences plugin provides a simple key-value store for persisting small configuration data in Flutter applications. According to the flutter/flutter repository, proper implementation requires careful handling of instance lifecycle, platform-specific plugin registration, and type safety to ensure consistent behavior across mobile, desktop, and web targets.

Architectural Best Practices for Flutter SharedPreferences

Lazy Initialization and Singleton Pattern

Repeated calls to SharedPreferences.getInstance() create new instances each time, incurring unnecessary disk I/O and risking race conditions. Initialize the plugin once and cache the instance.

class AppPreferences {
  AppPreferences._();
  static final AppPreferences instance = AppPreferences._();
  
  SharedPreferences? _prefs;
  
  Future<void> _init() async {
    _prefs ??= await SharedPreferences.getInstance();
  }
  
  // Access methods go here...
}

Typed API Usage and Null Safety

Always use the typed getters (getBool, getInt, getDouble, getString, getStringList) with corresponding setters. Provide default values using the null-coalescing operator to handle absent keys gracefully.

Future<bool> getDarkMode() async {
  await _init();
  return _prefs!.getBool(PrefKeys.darkMode) ?? false;
}

Centralized Key Management

Define all preference keys as const strings in a single class to prevent typos and ease refactoring.

class PrefKeys {
  static const darkMode = 'pref_dark_mode';
  static const launchCount = 'pref_launch_count';
  static const userToken = 'pref_user_token';
}

Batch Writes and Performance Optimization

Each setX call flushes to disk asynchronously. When updating multiple values, execute writes sequentially or wrap them in a transaction-style helper that calls await prefs.reload() after the last write to ensure consistency.

Future<void> updateUserSettings(bool darkMode, int fontSize) async {
  await _init();
  await _prefs!.setBool(PrefKeys.darkMode, darkMode);
  await _prefs!.setInt(PrefKeys.fontSize, fontSize);
  // Optional: await _prefs!.reload();
}

Avoid storing large blobs; shared_preferences writes to a small XML/JSON file on Android/iOS, and large payloads degrade launch time and may hit platform limits.

Platform-Specific Considerations

Web Support and Plugin Registration

When targeting web, the shared_preferences_web implementation is automatically included. The flutter/flutter repository verifies this behavior in /packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart, which confirms that the web plugin registrant correctly imports package:shared_preferences_web/shared_preferences_web.dart【/packages/flutter_tools/test/integration.shard/web_plugin_registrant_test.dart#L78-L79】.

No manual configuration is required, but ensure your pubspec.yaml includes shared_preferences: ^2.0.0 or higher.

Testing with Mock Values

Use SharedPreferences.setMockInitialValues in unit tests to avoid filesystem access, or utilize the shared_preferences_mocks package.

import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  test('dark mode defaults to false', () async {
    SharedPreferences.setMockInitialValues({});
    // Test your preferences wrapper...
  });
}

Complete Implementation Example

The following wrapper demonstrates singleton pattern, typed accessors, and centralized key management:

// preferences.dart
import 'package:shared_preferences/shared_preferences.dart';

class AppPreferences {
  AppPreferences._();
  static final AppPreferences instance = AppPreferences._();

  SharedPreferences? _prefs;

  Future<void> _init() async {
    _prefs ??= await SharedPreferences.getInstance();
  }

  Future<bool> getDarkMode() async {
    await _init();
    return _prefs!.getBool(PrefKeys.darkMode) ?? false;
  }

  Future<void> setDarkMode(bool value) async {
    await _init();
    await _prefs!.setBool(PrefKeys.darkMode, value);
  }

  Future<int> getLaunchCount() async {
    await _init();
    return _prefs!.getInt(PrefKeys.launchCount) ?? 0;
  }

  Future<void> incrementLaunchCount() async {
    await _init();
    final count = await getLaunchCount();
    await _prefs!.setInt(PrefKeys.launchCount, count + 1);
  }
}

class PrefKeys {
  static const darkMode = 'pref_dark_mode';
  static const launchCount = 'pref_launch_count';
}

Using the wrapper in UI:

import 'package:flutter/material.dart';
import 'preferences.dart';

class SettingsPage extends StatefulWidget {
  const SettingsPage({Key? key}) : super(key: key);
  
  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

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

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

  Future<void> _loadPrefs() async {
    final dark = await AppPreferences.instance.getDarkMode();
    setState(() => _darkMode = dark);
  }

  Future<void> _toggleDarkMode(bool value) async {
    await AppPreferences.instance.setDarkMode(value);
    setState(() => _darkMode = value);
  }

  @override
  Widget build(BuildContext context) {
    return SwitchListTile(
      title: const Text('Dark mode'),
      value: _darkMode,
      onChanged: _toggleDarkMode,
    );
  }
}

Summary

  • Cache the instance: Call SharedPreferences.getInstance() once and reuse the object to prevent redundant I/O and race conditions.
  • Use typed APIs: Always use getBool, setInt, etc., with null-coalescing defaults (??) to handle missing keys safely.
  • Centralize keys: Define all preference keys as const strings in a single class to eliminate typos and simplify refactoring.
  • Batch operations: Group multiple writes together to reduce disk access, and avoid storing large blobs that degrade performance.
  • Test with mocks: Use SharedPreferences.setMockInitialValues in unit tests to avoid filesystem dependencies.

Frequently Asked Questions

How do I prevent multiple SharedPreferences instances from causing race conditions?

Cache the instance returned by await SharedPreferences.getInstance() in a singleton or service class. Repeatedly calling getInstance() creates distinct objects that may conflict during concurrent writes, whereas a single cached instance ensures all operations reference the same in-memory state and underlying file.

What is the best way to handle default values in Flutter SharedPreferences?

Always provide default values using the null-coalescing operator when calling typed getters. For example, use prefs.getBool('darkMode') ?? false to ensure your application receives a valid boolean even when the key does not exist, preventing null pointer exceptions during fresh installs or data migrations.

Can I use SharedPreferences for large data storage in Flutter?

No, shared_preferences is designed for small primitive values only. The plugin persists data to a small XML file on Android and a plist on iOS; storing large blobs or complex objects degrades app launch time and may exceed platform storage limits. For large datasets, use SQLite, Hive, or file-based storage instead.

How do I test Flutter SharedPreferences without accessing the file system?

Use the static method SharedPreferences.setMockInitialValues(Map<String, Object> values) before running your tests. This injects an in-memory store that mimics the platform implementation, allowing you to verify read and write operations in unit tests without requiring device storage or filesystem permissions.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →