You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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).
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
The text was updated successfully, but these errors were encountered:
It's hard for me to know what to do here without a reproducer. The stack trace also doesn't include anything from Multiplatform Settings. The first emission from getStringOrNullFlow() is getStringOrNull() so it surprises me that you would see different behavior.
One guess is maybe the issue is from commit = true which will block the calling thread. But I still wouldn't expect this to block for a significant amount of time. Do you have any metrics for how long you're blocking for when the ANR is triggered? Also, how often is this happening?
I've managed to reproduce it only once. The app has been blocked completely (was waiting for 30 sec) and crashlytics logs are from my Pixel 8 on Android 15. But I have 5 other reports in Firebase Crashlytics.
I checked the codebase before creating the ticket and didn't find anything suspicious.
I see that the flow is cold and it should emit the value on subscribe. So I don't understand how this flow could skip the initial value. I've also assumed that commit = true can be a reason, but I would expect it to freeze the app on write operation (not read).
At the moment I changed the logic to using getStringOrNull() which fixes my case.
So, if you also don't have a clue, I'll continue trying to reproduce this ANR locally and find the reason.
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
SharedPreferencesSettings
to 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
The text was updated successfully, but these errors were encountered: