Is there a way to block on a scope to finish it’s ...
# coroutines
r
Is there a way to block on a scope to finish it’s jobs while not causing a deadlock, if they’re on the same thread? I have cleanup code in the finally block that needs to run on the same thread before onStop completes.
e
Idk if you can without potentially blocking in
onStop
. The question is: do you really need these fragment transactions to happen? It might be the case that you depend too tightly on the lifecycles of your components to execute certain stuff. I'd look into moving that dependency elsewhere, so that these transactions can happen, or not, at the Android system's will
r
I was hoping that this could be a usable pattern for creating and destroying fragments while the activity is visible. But as usual, saveInstanceState and activity lifecycle causes a horrible mess when trying to do asynchronous code.
The deadlock here is caused because join() doesn't resume the coroutine with a CancellationException until the current event is finished with the main thread. I thought there might be a way to join() while still processing other coroutines in the blocked thread.
I suppose I could just have a SharedStateFlow<Boolean> in a ViewModel that is set when the LoaderFragment should be visible.
Well, now that I think about it. That's just punting the problem to the ViewModel without fixing it. There is no way to ensure the finally block has finished before onSaveInstanceState() is called. I guess I'll just do .commitAllowingStateLoss() in the finally block and hope the system never decides to kill the app if the state wasn't saved. Thanks Android SDK, you're the best :-D
m
this is a terrible idea. Try to avoid this and work around it
r
The problem is that if you do a fragment transaction from a coroutine, there is no way to guarantee that the fragments were left in a consistent state since a coroutine can be canceled by onStop then process death. The work around ends up being tightly coupled code in different methods. The only work around that doesn’t require splitting up coupled code, is to discard the onSaveInstanceState bundle in onCreate() if the activity was recreated from process death. This way the fragment state will be known. To handle configuration changes the coroutine can be moved to the ViewModel.
The other work around is just use compose.
e
Or use a fragmetnless pattern
r
I think the same issue will come up with views if the view is responsible for saving it’s state in onSaveInstanceState()
Maybe using the Elm architecture where coroutines don’t modify state but just produce events that update the ViewModel/StateHandle. And the Activity/Fragment listens to the ViewModel to generate it’s view using compose.
e
I've also adopted a fully VM-based state source. There is also a VM variant that handles saved instance state, but idk the details as I haven't needed it yet
Also consider persistable UI state that could be restored after process death
r
I wanted to have UI effects in my coroutine so that the business logic would be clear. Show dialgo1 then show dialog2 then show dialog3 etc… But having to model everything in a VM just to support serialization is tedious.
Since I don’t care if the UI state is restored after process death, I think I’m just going to discard onSaveInstance state after process death for easier writing of business logic.
Whatever the user was doing was probably already forgotten by them anyway.
I was testing the play store app and flutter gallery app to see if they fully restore their view state when their activity is recreated and they don't. Also pocket casts gets glitchy when I restore it. Seems like no app really handles restoring their view state correctly in all cases. Funny.
m
This is not a solution tho, but would using Dispatchers.Main.immediate instead help? Might solve your problem.
r
Yes. That solves the original issue. The finally block gets runs before .cancel() returns. Interesting.
m
yes. because Dispatchers.Main will get delayed in the event queue. Immediate is like in the same frame whenever possible.
👍 1
r
This will come in handy for finally blocks that need to run immediately. Is there a way to just use Dispatchers.Main.immediate only when doing .cancel()? It's fine if there isn't. I think I'm going to be using Main.immediate for all my coroutines that run on the UI thread.
m
not sure about first one. About second one, yes use immediate whenever possible. Be careful tho, it’s not just simply change the dispatchers. You need to try it out, there might be side effects
r
For sure. This at least might make cleanup of UI effects using fragments/views work with onSaveInstanceState.
Switching context to Dispatchers.IO in the try block will not run the finally block in Dispatchers.Main.immediate before .cancel() returns. I guess this will not work for what I was intending to use it for. I've given up on using onSaveInstance state for the time being.