Hi, I'm encountering unexpected behaviour with getStringOrNullFlow() that can lead to an ANR (Application Not Responding) when calling firstOrNull() from the main thread.
In our case, we use SharedPreferencesSettings to wrap Android's SharedPreferences, created like this:
SharedPreferencesSettings(
context.getSharedPreferences(name, Context.MODE_PRIVATE),
commit = true
)
We expect getStringOrNullFlow() to emit the current cached value immediately, which is important for certain use cases — especially when we want to synchronously grab a value on the main thread to skip loading states (e.g., showing a UI screen directly if a value already exists).
However, calling:
settings.getStringOrNullFlow("some_key").firstOrNull()
on the main thread can hang and eventually trigger an ANR, despite the fact that the flow should emit immediately via callbackFlow.
Crashlytics stacktrace:
main (runnable):tid=1 systid=16833
at kotlin.coroutines.CombinedContext.get(CoroutineContextImpl.kt:120)
at kotlinx.coroutines.AbstractCoroutine.<init>(AbstractCoroutine.kt:50)
at kotlinx.coroutines.internal.ScopeCoroutine.<init>(Scopes.kt:14)
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:285)
at kotlinx.coroutines.flow.internal.ChannelFlow.collect$suspendImpl(ChannelFlow.kt:118)
at kotlinx.coroutines.flow.internal.ChannelFlow.collect(ChannelFlow.kt:6)
at kotlinx.coroutines.flow.DistinctFlowImpl.collect(Distinct.kt:68)
at kotlinx.coroutines.flow.FlowKt__ReduceKt.firstOrNull(FlowKt__Reduce.kt:230)
at kotlinx.coroutines.flow.FlowKt.firstOrNull(Flow.kt:1)
at com.token.data.TokenRepositoryImpl.getSelectedToken(TokenRepositoryImpl.kt:64)
at com.token.data.TokenRepository$DefaultImpls.getSelectedToken$default(TokenRepository.java:21)
at com.transfer.domain.TransferChannelInteractorImpl.loadChannelWithDetailsForCountry(TransferChannelInteractorImpl.kt:42)
at com.transfer.create.TopupCreateViewModel.getChannel(TopupCreateViewModel.kt:148)
at com.transfer.create.TopupCreateViewModel.access$getChannel(TopupCreateViewModel.kt:38)
at com.transfer.create.TopupCreateViewModel$observeQuote$1$invokeSuspend$$inlined$flatMapLatest$1.invokeSuspend(Merge.kt:196)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:232)
at android.os.Handler.handleCallback(Handler.java:991)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loopOnce(Looper.java:232)
at android.os.Looper.loop(Looper.java:317)
at android.app.ActivityThread.main(ActivityThread.java:8934)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:591)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
Of course we can use settings.getStringOrNull("some_key") and it solves an issue, but it's not always convenient.
⚙️ Versions used
Kotlin: 2.1.10
Coroutines: 1.10.2
Settings: 1.3.0
Hi, I'm encountering unexpected behaviour with
getStringOrNullFlow()that can lead to an ANR (Application Not Responding) when callingfirstOrNull()from the main thread.In our case, we use
SharedPreferencesSettingsto wrap Android'sSharedPreferences, created like this:We expect
getStringOrNullFlow()to emit the current cached value immediately, which is important for certain use cases — especially when we want to synchronously grab a value on the main thread to skip loading states (e.g., showing a UI screen directly if a value already exists).However, calling:
on the main thread can hang and eventually trigger an ANR, despite the fact that the flow should emit immediately via callbackFlow.
Crashlytics stacktrace:
Of course we can use
settings.getStringOrNull("some_key")and it solves an issue, but it's not always convenient.⚙️ Versions used
Kotlin: 2.1.10
Coroutines: 1.10.2
Settings: 1.3.0