Exerosis
02/24/2022, 3:20 PMStephen Edwards
02/24/2022, 3:42 PMrunBlocking
claims it blocks the thread from which it is called (interuptibly). But I have observed behaviour where other coroutines waiting for dispatch on that thread (e.g. on the main thread) will be dispatched by the event loop because runBlocking
falls back to using the calling thread's eventLoop
even when a specific other dispatcher (which may be busy) is specified for that which to run the coroutine launched by runBlocking
on. Why is this and is this a bug? If not what is the explanation for this choice of behaviour? Code reproduction example in thread:allan.conda
02/24/2022, 7:23 PMDavid W
02/25/2022, 3:26 AMStateFlow
into a different one?
This is what I've done, not sure if it's the best or even working...
val newStateFlow = originalStateFlow
.map { it.doTransform() }
.stateIn(scope = CoroutineScope(Job()), started = SharingStarted.Eagerly, initialValue = false)
antrax
02/25/2022, 4:52 PMtheapache64
02/28/2022, 7:49 AMCoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>).launch {
launch {
println("A")
delay(5000)
println("B")
}
launch(context = Job()) {
delay(1000)
throw IOException()
}
}
The above snippet will crash the android app (obviously), but the app prints B
5 seconds after the crash.
Note that removing Job()
gives expected result. Any idea why? 🤔theapache64
02/28/2022, 8:12 AMjob.join()
here? With delay(1300)
we’re already waiting, right? 🤔Marco Righini
02/28/2022, 4:06 PMoverride fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.uiEvent
.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
.onEach { event ->
when (event) {
// code there
}
}.launchIn(lifecycleScope)
}
and we have a problem when we add the fragment to the back stack: we end up having coroutines when we press back and go back to this fragment (onViewCreated is called again)
The right approach seems to be to change lifecycleScope
with viewLifecycleOwner.lifecycleScope
.
Is it ok? If yes, could be worth to have a lint rule?
Thanks!eygraber
02/28/2022, 9:36 PMlaunch
on a CoroutineScope
that has been cancelled? Does it throw an exception, or does it just get swallowed?mzgreen
03/01/2022, 11:23 AMCompletableDeferred
and suspendCancellableCoroutine
when wrapping a callback based API into a coroutines API?
Here is an example of an API wrapped using `CompletableDeferred`: https://handstandsam.com/2022/02/28/install-referrer-kotlin-extension/
And here are examples of APIs wrapped using `suspendCancellableCoroutine`: https://chris.banes.dev/suspending-views/
When is the one approach preferable over the other one?mcpiroman
03/02/2022, 10:14 AMsuspend fun processAll(items: List<Item>) = coroutineScope {
val channel = Channel<Item>(Channel.UNLIMITED)
items.forEach { channel.trySendBlocking(it).getOrThrow() }
repeat(4) {
launch {
for (item in channel) // process
}
}
}
Bonus point - is there a facility to automatically pick an optimal level of parallelism, instead of, in this example, hardcoded 4?khairil.ushan
03/02/2022, 7:06 PM@Throws(
CancellationException::class,
UnauthorizedError::class
)
suspend fun login() {
runBlocking {
val fakeWaitingTime = 300L
delay(fakeWaitingTime)
}
}
Then if I try to execute that login function inside Swift concurrency context like this
Task {
try? await Auth().login()
}
It will crash with this error:
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared
I am using this version of coroutines
const val coroutines = "1.6.0-native-mt"
Is there any work around for this? Thank you.Sourabh Rawat
03/03/2022, 4:29 AMreservation
parameter in debugger at the debug point at line 26. It says This variable is inaccessible because it isn't used after the last suspension point
Using coroutines 1.6.0
Sourabh Rawat
03/03/2022, 8:58 AMallowBlockingCallsInside
does not work if I don't run the test in debug mode?
I am thinking its a stack trace recovery issue during normal tests? I tried following to replicate courotines debug mode during normal test runs
tasks.withType<Test> {
useJUnitPlatform()
jvmArgs = listOf("-XX:+AllowRedefinitionToAddDeleteMethods", "-ea", "-Dkotlinx.coroutines.debug=on", "-Dkotlinx.coroutines.stacktrace.recovery=true")
}
but it didnt work
Any help is apprecitated!jean
03/03/2022, 2:28 PMFlow
?Tower Guidev2
03/03/2022, 2:34 PMtseisel
03/03/2022, 6:43 PMZoltan Demant
03/04/2022, 6:25 AMFlow<T>
which queries data from an SQL database, initially emitting the current version of the data, and then emitting again anytime it changes. Is it possible to collect the flow in a way such that the initial query isnt re-ran everytime I restart collecting it?
Consider a component that can be started/stopped, if the initial query has finished it wouldnt make sense to re-run it the next time the component is started, Id much prefer to just receive any further changes to it. The natural approach seems to be collecting the flow in start, cancelling the resulting job in stop - as expected that restarts the collection everytime though.Alexander Maryanovsky
03/04/2022, 8:14 AMCoroutineContext
is basically the coroutines’ equivalent of ThreadLocal
. Why don’t the tutorials say this? Would make understanding so much faster…theapache64
03/04/2022, 10:25 AMbogdoll
03/05/2022, 1:40 PMbogdoll
03/06/2022, 7:18 AM<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-nio</artifactId>
<version>0.26.1-eap13</version>
</dependency>
But could not find it on the official kotlin pages. Additional the version numbers looks suspect.yschimke
03/06/2022, 9:57 AMursus
03/06/2022, 9:59 PMsuspend fun <T> ???(millis: Long, action: suspend () -> T): T {
val start = System.currentMillis()
val value = action()
val end = System.currentMillis()
val took = end - start
if (took < millis) {
delay(millis - took)
}
return value
}
does this have a name already? it makes sure the suspend function takes atleast N millis, so its not too fast
its not debounce since its calcualtes the delay …so I guess I need my own nameursus
03/06/2022, 11:51 PMflow.first { .. }
to suspend till a value I want is emitted and then continue in “suspend functions world” with my logic more idiomatic than take(1)
(and keeping the logic reactive)?
or rather is there some technical difference between flow.filter { … }
and flow.first { .. }
? I mean they both “wait” by suspending the coroutine, right?Aditya Wasan
03/09/2022, 11:34 AMpublic suspend fun saveAuthToken(authToken: String): Result<String, Throwable> {
return runCatching {
require(authToken.isNotEmpty()) { "authToken cannot be empty" }
val authTokenKey = stringPreferencesKey(AUTH_TOKEN_KEY)
// Use the external scope here to save the auth token
externalScope
.launch(ioDispatcher) { authDataStore.edit { store -> store[authTokenKey] = authToken } }
.join()
authToken
}
}
This is the code that launches a new coroutine in an external scope which will can live longer than the suspend function. Now, to test this behavior I’m assuming that I would have to pause the external coroutine and then cancel the scope in which the suspend function is called. After that, I can resume the external coroutine and then check the authDataStore to verify. However, I couldn’t find any standard way to do this. Does anyone know any resource or samples where I could look at a similar behavior. Thanks in advance.brabo-hi
03/09/2022, 9:44 PMIO dispatcher
package kotlinx.coroutines
import kotlin.coroutines.*
public actual object Dispatchers {
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher = JsMainDispatcher(Default, false)
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
}
Exerosis
03/15/2022, 4:49 AMclass Test {
val continuation = Continuation<Unit>(EmptyCoroutineContext) {
}
suspend fun testFire() = suspendCoroutineUninterceptedOrReturn<Unit> { cont ->
println(continuation)
println(cont)
println(continuation == cont)
}
fun testMain() {
val action: suspend () -> (Unit) = {
testFire()
}
action.startCoroutineUninterceptedOrReturn(continuation)
}
}
Why does this not not print the same thing twice and then true? IK it's significantly stupid but it still wasn't what I was expecting.
And more generally is there a way to (safely) inject some sort of identifier into a continuation that can be picked up later? I assume Context is the right thing to use but I don't want to like overwrite any other context like job.ziv kesten
03/15/2022, 3:45 PMCoroutineScope
and is testing a flow emitted from a fake implementation of another class.
However, the flow does not seem to emit the data i assign it to stream.
What would you think the problem might be?
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
@Before
fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
}
@After
fun tearDown() {
Dispatchers.resetMain() // reset the main dispatcher to the original Main dispatcher
mainThreadSurrogate.close()
}
@Test
fun testTest(): Unit = runBlocking {
launch(Dispatchers.Main) {
val recorder = FakeRecordingsHandler()
val sut = SomeClass(recorder, this.coroutineContext)
recorder.currentTrack = flow { emit(DEMO_TRACK) }
val isCurrentTrack = sut.isCurrentTrack(DEMO_TRACK.id)
assertTrue(isCurrentTrack)
}
}
// class SomeClass(
// private val recorder: RecordingsHandler,
// override val coroutineContext: CoroutineContext
// ) : VoiceRecorder, CoroutineScope {
//
// private var currentTrackId: String? by mutableStateOf(null)
//
// init {
// launch {
// recorder.currentTrack.distinctUntilChanged().collect { demoTrack ->
// currentTrackId = demoTrack?.id
// }
// }
//
// override fun isCurrentTrack(id: String) = currentTrackId == id
// }
Marin Tolić
03/15/2022, 6:39 PMpublic val channel: LiveData<Channel> = channelState.channelData.combine(channelState.members) { _, _ ->
channelState.toChannel()
}.asLiveData()
We do in fact know that this does work when the app is run, but want to write a simple test to ensure this doesn't break in the future.
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `When channel members update should update channel`() = runBlockingTest {
val messageListHeaderViewModel = MessageListHeaderViewModel(CID, chatClient = chatClient)
val mockObserver: Observer<Channel> = spy()
messageListHeaderViewModel.channel.observeForever(mockObserver)
val memberList = createMembers { createMember(User(name = "user")) }
membersMock.emit(memberList)
advanceUntilIdle()
verify(mockObserver, times(2)).onChanged(any())
}
The issue is that the observer will update only once which is the first emission of Channel
triggered by SharingStarted.Eagerly
.
Curiously enough, if you collect the Flow in tests without the .asLiveData()
transformation, you will collect all of it including the latest update. However then transforming to LiveData
no interactions with the mock can be observed.
Even more curiously the same .asLiveData()
transformed variable is testable if no combine
is called. So if we just had channelState.members.asLiveData()
we could observe all of the emissions from it without any issue.
I've been trying to wrap my head around this for a while and can't understand what gives.
Thanks in advance for any assistance 😄Marin Tolić
03/15/2022, 6:39 PMpublic val channel: LiveData<Channel> = channelState.channelData.combine(channelState.members) { _, _ ->
channelState.toChannel()
}.asLiveData()
We do in fact know that this does work when the app is run, but want to write a simple test to ensure this doesn't break in the future.
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `When channel members update should update channel`() = runBlockingTest {
val messageListHeaderViewModel = MessageListHeaderViewModel(CID, chatClient = chatClient)
val mockObserver: Observer<Channel> = spy()
messageListHeaderViewModel.channel.observeForever(mockObserver)
val memberList = createMembers { createMember(User(name = "user")) }
membersMock.emit(memberList)
advanceUntilIdle()
verify(mockObserver, times(2)).onChanged(any())
}
The issue is that the observer will update only once which is the first emission of Channel
triggered by SharingStarted.Eagerly
.
Curiously enough, if you collect the Flow in tests without the .asLiveData()
transformation, you will collect all of it including the latest update. However then transforming to LiveData
no interactions with the mock can be observed.
Even more curiously the same .asLiveData()
transformed variable is testable if no combine
is called. So if we just had channelState.members.asLiveData()
we could observe all of the emissions from it without any issue.
I've been trying to wrap my head around this for a while and can't understand what gives.
Thanks in advance for any assistance 😄tseisel
03/15/2022, 10:47 PMInstantTaskExecutorRule
when testing code using LiveData
.