Use this file to discover all available pages before exploring further.
SharedPreferences has three distinct ANR vectors that catch many Android developers off guard: the first getSharedPreferences() call performs disk I/O, commit() is always a synchronous disk write, and apply() flushes synchronously during Activity stop via QueuedWork.waitToFinish().
1. getSharedPreferences() performs disk I/O on the first call
The first time a given preferences file is opened, Android reads the XML file from disk into memory. On slow or busy storage this can take 50–200 ms. If that call happens on the main thread — inside a lifecycle callback or onViewCreated — it blocks the UI pipeline for the full duration.
// ❌ BAD: SharedPreferences disk I/O on Mainclass SettingsFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val pref = requireContext() .getSharedPreferences("user_prefs", Context.MODE_PRIVATE) // disk read on Main .getString("token", null) render(pref) }}
commit() writes to disk synchronously on whatever thread you call it from. Calling it on the main thread is a direct ANR risk. It will also be flagged immediately by StrictMode. Always use apply() instead — with the caveats described below.
// ❌ BAD: commit() is a synchronous disk write — blocks Mainprefs.edit().putString("key", value).commit()
3. apply() flushes synchronously during Activity stop
apply() is asynchronous for the write itself, but it places a pending write into QueuedWork. Android flushes QueuedWork synchronously on the main thread inside handleStopActivity and handleServiceArgs. On slow storage or under memory pressure, this flush can take 200–500 ms and cause an ANR that shows up as waiting in framework code rather than in your own stack.
// ANR trace signature for apply() flush:"main" prio=5 tid=1 Waiting at java.lang.Object.wait(Native method) at com.android.internal.util.QueuedWork.waitToFinish(QueuedWork.java:...) at android.app.ActivityThread.handleStopActivity(ActivityThread.java:...)
Read preferences on Dispatchers.IO, cache the result in memory, and write with apply(). This eliminates the first-call disk I/O from the main thread and removes synchronous write risk.
// ✅ CORRECT: read on IO; cache in memory; write with apply()class TokenRepository @Inject constructor( @ApplicationContext context: Context) { private val prefs by lazy(LazyThreadSafetyMode.NONE) { context.getSharedPreferences("user_prefs", Context.MODE_PRIVATE) } suspend fun readToken(): String? = withContext(Dispatchers.IO) { prefs.getString("token", null) } fun writeToken(token: String) { prefs.edit().putString("token", token).apply() // async; never use commit() }}
DataStore is the modern replacement for SharedPreferences. It is fully coroutine-based, never performs synchronous disk writes, and has no QueuedWork flush at Activity stop. For any new code, start with DataStore. For existing code, migrate incrementally starting with the preferences files that are accessed most frequently on the main thread.
DataStore exposes preferences as a Flow, making reads reactive and writes explicit suspension functions. There is no commit vs apply distinction and no silent synchronous flush.
// ✅ BETTER: migrate to DataStore (fully coroutine-based, no synchronous writes)val Context.userPrefsStore by preferencesDataStore("user_prefs")