Why Flutter SharedPreferences Is Not Persisting Data on Android (and How to Fix It)
The most common reason flutter sharedpreferences is not persisting data on Android is failing to await asynchronous operations, causing writes to be lost when the app closes before the native background thread completes the flush to disk.
When building Flutter apps that rely on local storage, developers often encounter situations where settings or user tokens disappear after restarting the application. This issue specifically affects the shared_preferences plugin within the flutter/flutter repository, which uses Android's native SharedPreferences system behind the scenes. Understanding the underlying implementation in packages/shared_preferences/shared_preferences_android is essential to diagnosing why your flutter sharedpreferences is not persisting data across sessions.
How SharedPreferences Works on Android
The plugin stores key-value pairs in an XML file located at /data/data/<package_name>/shared_prefs/<package_name>_preferences.xml. Communication between Dart and Android happens through a MethodChannel implemented in SharedPreferencesAndroid (packages/shared_preferences/shared_preferences_android/lib/src/shared_preferences_android.dart), which delegates to the Kotlin plugin class in SharedPreferencesPlugin.kt (packages/shared_preferences/shared_preferences_android/android/src/main/kotlin/io/flutter/plugins/sharedpreferences/SharedPreferencesPlugin.kt).
The Android implementation uses apply() rather than commit() for writes, meaning changes are committed asynchronously to an in-memory cache first, then flushed to disk on a background thread. If your app process terminates before this flush completes, the data is lost.
Common Causes of Flutter SharedPreferences Not Persisting on Android
1. Not Awaiting SharedPreferences.getInstance()
The plugin must initialize the native store before any read or write operations occur. If you use the instance before the Future completes, operations may be silently dropped.
// WRONG: Using instance before await
void saveData() {
final prefs = SharedPreferences.getInstance(); // Returns Future, not instance
prefs.setString('key', 'value'); // This will fail or be ignored
}
// CORRECT: Awaiting initialization
Future<void> saveData() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('key', 'value');
}
2. Ignoring the Future Returned by setString (and Other Setters)
Every setter (setString, setInt, setBool, etc.) returns a Future<bool> indicating whether the write succeeded. If you do not await this Future and the app closes immediately after the call, the native apply() operation may not complete.
// WRONG: Fire-and-forget
void quickSave() {
prefs.setString('token', 'abc123'); // Future ignored
SystemNavigator.pop(); // App may close before write completes
}
// CORRECT: Await the commit
Future<void> safeSave() async {
final success = await prefs.setString('token', 'abc123');
if (success) {
print('Data persisted to disk');
}
}
3. ProGuard or R8 Stripping the Plugin Code
In release builds, code shrinking can remove the generated MethodChannel classes that connect Dart to the Kotlin implementation in SharedPreferencesPlugin.kt. When this happens, calls become no-ops and data appears to persist (no errors) but actually never reaches the native store.
Add this to android/app/proguard-rules.pro:
-keep class io.flutter.plugins.sharedpreferences.** { *; }
4. Multiple Processes and android:process
If your AndroidManifest.xml declares android:process for activities or services, each process receives its own instance of the Android SharedPreferences cache. Writes in one process may not be visible to another, and if the UI process restarts, it may load stale or empty data.
Avoid custom process declarations for components that interact with preferences, or ensure all preference operations occur within the same process.
5. Unintentional remove() or clear() Calls
A common debugging mistake is leaving prefs.clear() in initState or calling remove() with dynamic keys that accidentally match existing entries. Search your codebase for these method calls to ensure they are not executed during normal app startup.
6. Emulator and CI Environment Data Wipes
Some CI pipelines or emulator configurations (especially with --wipe-data flags) clear the /data/data/<package> directory between test runs. Verify that your test runner is not invoking adb shell pm clear or similar commands that delete the shared_prefs directory.
Correct Implementation Pattern for Persistent Storage
Following the implementation details from packages/shared_preferences/shared_preferences/lib/shared_preferences.dart, here is a robust pattern that ensures flutter sharedpreferences persists data correctly across Android sessions:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class PreferenceService {
static SharedPreferences? _prefs;
static Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance();
}
static Future<bool> setUsername(String name) async {
if (_prefs == null) throw StateError('Preferences not initialized');
return await _prefs!.setString('username', name);
}
static String? getUsername() {
if (_prefs == null) throw StateError('Preferences not initialized');
return _prefs!.getString('username');
}
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PreferenceService.initialize();
runApp(const MyApp());
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? _username;
@override
void initState() {
super.initState();
_loadUsername();
}
void _loadUsername() {
setState(() {
_username = PreferenceService.getUsername();
});
}
Future<void> _saveUsername() async {
await PreferenceService.setUsername('FlutterDev_${DateTime.now().second}');
_loadUsername();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SharedPreferences Persistence')),
body: Center(child: Text('Current user: ${_username ?? "none"}')),
floatingActionButton: FloatingActionButton(
onPressed: _saveUsername,
child: const Icon(Icons.save),
),
);
}
}
Critical implementation details from the source code:
SharedPreferences.getInstance()inshared_preferences.dartreturns aFuture<SharedPreferences>that must be awaited before any operations.- The
setString,setInt, and other setter methods returnFuture<bool>indicating whether the nativeapply()succeeded. - The Android implementation in
shared_preferences_android.dartuses aMethodChannelto invoke Kotlin code inSharedPreferencesPlugin.kt, which ultimately calls Android'sSharedPreferences.Editor.apply().
Summary
- Always await
SharedPreferences.getInstance()before reading or writing to ensure the native store is initialized. - Await every setter (
setString,setBool, etc.) to guarantee the background flush to/data/data/<package>/shared_prefs/completes before the app terminates. - Check ProGuard rules in release builds to prevent R8 from stripping the
io.flutter.plugins.sharedpreferencesclasses that bridge Dart and Android. - Avoid multiple processes;
android:processdeclarations create isolated preference caches that do not share data. - Cache the instance in a service or provider to prevent stale channel references during hot reload.
Frequently Asked Questions
Why does SharedPreferences work on iOS but not on Android?
iOS uses NSUserDefaults which has different synchronization semantics than Android's SharedPreferences. While iOS often appears more forgiving with unawaited writes, Android strictly requires awaiting the Future returned by setString and other methods because it uses asynchronous apply() to disk. Additionally, Android release builds are more susceptible to ProGuard stripping the plugin classes, which does not affect iOS.
How do I verify that SharedPreferences data is actually written to disk on Android?
You can inspect the XML file directly using Android Debug Bridge (ADB). Run:
adb shell run-as com.your.package cat /data/data/com.your.package/shared_prefs/com.your.package_preferences.xml
Replace com.your.package with your actual application ID. If the file contains your key-value pairs, the plugin is working correctly. If the file is empty or missing, the write operation was either not awaited or was blocked by ProGuard.
Does SharedPreferences persist data if the app crashes immediately after a write?
Because the Android implementation uses apply() (asynchronous) rather than commit() (synchronous), there is a small window where a crash immediately following a write can prevent the data from reaching disk. To minimize this risk, always await the Dart Future returned by the setter method, which ensures the native apply() has at least been queued to the background thread before your code continues.
Is it safe to store sensitive data like passwords in SharedPreferences?
No. While shared_preferences provides convenient persistence, it stores data in plain XML files within the app's private directory (/data/data/<package>/shared_prefs/). On rooted devices, these files are easily accessible. For sensitive information such as authentication tokens or passwords, use the flutter_secure_storage plugin which leverages Android's Keystore system and iOS Keychain instead.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →