Stefan Oltmann
11/08/2021, 1:57 PMprivate val scope = CoroutineScope(Dispatchers.Default)
class PhotoSyncService {
private var jobMap: MutableMap<PhotoSource, Job> = mutableMapOf()
fun sync(photoSource: PhotoSource, updateSyncInfo: (SyncInfo) -> Unit) {
val existingJob = jobMap[photoSource]
if (existingJob != null && existingJob.isActive)
return
val job = scope.launch {
try {
updateSyncInfo(
SyncInfo(
log = "Starting...",
status = SyncStatus.SYNCING
)
)
delay(2500)
updateSyncInfo(
SyncInfo(
log = "Sync done.",
status = SyncStatus.SYNCED
)
)
} catch (ex: CancellationException) {
updateSyncInfo(
SyncInfo(
log = "Sync canceled.",
status = SyncStatus.ERROR
)
)
}
}
jobMap[photoSource] = job
}
fun cancelSync(photoSource: PhotoSource) {
val job = jobMap[photoSource]
job?.let {
job.cancel()
jobMap.remove(photoSource)
}
}
}
And I got a PhotoStore
class that uses this service like that:
private fun forceSync(
oldState: PhotoState,
photoSource: PhotoSource
): PhotoState {
photoSyncService.sync(photoSource) { syncInfo ->
/*
* This update comes from an background coroutine.
* So we need to bring it back to the main thread.
*/
launch(Dispatchers.Main) {
this@PhotoStore.dispatch(PhotoAction.SyncInfoUpdate(photoSource, syncInfo))
}
}
return oldState
}
The forceSync()
is called on the main thread.
PhotoSyncService
is also initialized on the main thread.
As soon as my updateSyncInfo
lambda is called in my launch
block it freezes the PhotoSyncService
& PhotoStore
... The line jobMap[photoSource] = job
gives me a InvalidMutabilityException
.
I first thought there were other reasons, but after tracking everything down I know that it must be the lambda call-
But why? Because of the call to "this"?Nicolas Patin
11/08/2021, 2:07 PMsync
method of PhotoSyncService
, try to add Dispatchers.Default
when you launch your coroutines :
val job = scope.launch(Dispatchers.Default) {
// ...
}
Nicolas Patin
11/08/2021, 2:08 PMdelay(2500)
with a withContext(Dispatchers.Default)
:
withContext(Dispatchers.Default) {
delay(2500)
}
Stefan Oltmann
11/08/2021, 2:26 PMval job = scope.launch(Dispatchers.Default)
the this.dispatch()
seems to capture everything.
According to the docs launch()
should be in Dispatchers.Default
since my scope is there and it will be inherited. Is there a known bug that this does not work?mbonnin
11/08/2021, 2:46 PMthis@PhotoStore
will most likely be freezed from the launch {}
Stefan Oltmann
11/08/2021, 2:46 PMStefan Oltmann
11/08/2021, 2:46 PMmbonnin
11/08/2021, 2:47 PMdispatch
outside the lambdambonnin
11/08/2021, 2:48 PMval syncInfo = photoSyncService.sync(photoSource)
this@PhotoStore.dispatch(PhotoAction.SyncInfoUpdate(photoSource, syncInfo))
mbonnin
11/08/2021, 2:48 PMSyncInfo
being immutable messagesmbonnin
11/08/2021, 2:48 PMPhotoStore
Stefan Oltmann
11/08/2021, 2:49 PMmbonnin
11/08/2021, 2:49 PMsync
suspendStefan Oltmann
11/08/2021, 2:50 PMmbonnin
11/08/2021, 2:50 PMPhotoStore
will never be referenced from PhotoSyncService
mbonnin
11/08/2021, 2:51 PMPhotoSyncService
does stuff in the background in a suspend
functionmbonnin
11/08/2021, 2:51 PMmbonnin
11/08/2021, 2:52 PMPhotoStore
through the launch {}
hoopsmbonnin
11/08/2021, 2:52 PMPhotoSyncService
sees is a Continuation
and it should workmbonnin
11/08/2021, 2:53 PMStefan Oltmann
11/08/2021, 2:53 PMprivate fun forceSync(
oldState: PhotoState,
photoSource: PhotoSource
): PhotoState {
launch(Dispatchers.Main) {
photoSyncService.sync(photoSource) { syncInfo ->
launch(Dispatchers.Main) {
this@PhotoStore.dispatch(PhotoAction.SyncInfoUpdate(photoSource, syncInfo))
}
}
}
return oldState
}
mbonnin
11/08/2021, 2:54 PMthis@PhotoStore
called from the lambda, it will be freezed I believeStefan Oltmann
11/08/2021, 2:55 PMPhotoState
in my PhotoStore
without taking the result in my lambda and calling dispatch
πStefan Oltmann
11/08/2021, 2:56 PMmbonnin
11/08/2021, 2:56 PMsuspend
is usefulmbonnin
11/08/2021, 2:56 PMsuspend
is like a lambda but it will not capture your this@PhotoStore
mbonnin
11/08/2021, 2:57 PMval syncInfo = photoSyncService.sync(photoSource)
this@PhotoStore.dispatch(PhotoAction.SyncInfoUpdate(photoSource, syncInfo))
mbonnin
11/08/2021, 2:57 PMforceSync
is now suspend toombonnin
11/08/2021, 2:57 PMGlobalScope
stuffStefan Oltmann
11/08/2021, 2:57 PMsync()
mbonnin
11/08/2021, 2:58 PMmbonnin
11/08/2021, 2:58 PMFlow<PhotoState>
mbonnin
11/08/2021, 2:58 PMfun sync(photoSource: PhotoSource): Flow<PhotoState>
Stefan Oltmann
11/08/2021, 2:58 PMmbonnin
11/08/2021, 2:59 PMphotoSyncService.sync(photoSource).collect {
this@PhotoStore.dispatch(PhotoAction.SyncInfoUpdate(photoSource, it))
}
mbonnin
11/08/2021, 2:59 PMAnd the flow won't capture like the lambda?I hope it won't π
mbonnin
11/08/2021, 2:59 PMmbonnin
11/08/2021, 3:01 PMStefan Oltmann
11/08/2021, 3:02 PMsync()
switches to Default
dispatcher to offload it from Main
.Stefan Oltmann
11/08/2021, 3:13 PMFlowCollector is not thread-safe and concurrent emissions are prohibited.
is the error now.
It says I can try to use channelFlowStefan Oltmann
11/08/2021, 3:55 PMchannelFlow
actually works. π
I don't understand why, but it prevents the capturing of my PhotoStore.
Thank you, @mbonnin π
Currently it's not an ideal solution because the Flow must be captured to go on while a lambda just gives a feedback (like onClick()
for a Button
) , but maybe I can change other stuff to make it more fitting.mbonnin
11/08/2021, 3:56 PMmbonnin
11/08/2021, 3:56 PMmbonnin
11/08/2021, 3:56 PMmbonnin
11/08/2021, 3:57 PMmbonnin
11/08/2021, 3:57 PM