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 PMPhotoStoreStefan 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 PhotoSyncServicembonnin
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@PhotoStorembonnin
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