Severity: LOW
Summary
The conversation screen deliberately moves the initial recent-emoji load off the main thread (with a comment citing #147: "the first access to a SharedPreferences file blocks on disk … stalls the conversation screen's first frame"). But the write path — invoked on every reaction tap — runs synchronously on the main thread, re-introducing the same anti-pattern the load fix set out to avoid.
Evidence
app/src/main/java/dev/ipf/darkmatter/ui/DarkMatterApp.kt:3983-3985
fun recordReactionEmoji(emoji: String) {
recentReactionEmojis = RecentEmojiPreferences.recordPicked(context, emoji)
}
app/src/main/java/dev/ipf/darkmatter/ui/RecentEmojiPreferences.kt — recordPicked() calls load(context) (a getSharedPreferences + getString + split + filter) and then .edit().putString(...).apply() inline on the calling (main) thread for every tap.
Impact
Low: by the time a reaction fires, the SharedPreferences instance is usually warm, so the cost is in-memory string work plus an async apply(). But it is the same main-thread-I/O class the codebase explicitly fixed for the load path (#147), and the first reaction after a cold start (warm-up not guaranteed) can touch disk on the main thread.
Suggested fix
Run the record off the main thread (mirroring the load fix) and publish the result back:
fun recordReactionEmoji(emoji: String) {
scope.launch {
val updated = withContext(Dispatchers.IO) { RecentEmojiPreferences.recordPicked(context, emoji) }
recentReactionEmojis = updated
}
}
Severity: LOW
Summary
The conversation screen deliberately moves the initial recent-emoji load off the main thread (with a comment citing #147: "the first access to a SharedPreferences file blocks on disk … stalls the conversation screen's first frame"). But the write path — invoked on every reaction tap — runs synchronously on the main thread, re-introducing the same anti-pattern the load fix set out to avoid.
Evidence
app/src/main/java/dev/ipf/darkmatter/ui/DarkMatterApp.kt:3983-3985app/src/main/java/dev/ipf/darkmatter/ui/RecentEmojiPreferences.kt—recordPicked()callsload(context)(agetSharedPreferences+getString+split+filter) and then.edit().putString(...).apply()inline on the calling (main) thread for every tap.Impact
Low: by the time a reaction fires, the
SharedPreferencesinstance is usually warm, so the cost is in-memory string work plus an asyncapply(). But it is the same main-thread-I/O class the codebase explicitly fixed for the load path (#147), and the first reaction after a cold start (warm-up not guaranteed) can touch disk on the main thread.Suggested fix
Run the record off the main thread (mirroring the load fix) and publish the result back: