question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Lifecycle handling

See original GitHub issue

I was trying to use coroutine Jobs for Android lifecycle handling together with the UI dispatcher, but I’ve stumbled upon an interesting issue where basically the following code can cause a crash:

var job: Job? = null

fun foo() {
  job = launch(UI) {
    val bar = download().await()
    // Cancel could have been called now, but the following code is posted to a Handler and executed anyway
    performSomeWorkOnTheUI(bar) // Causes a crash after onPause
  }
}

override fun onPause() {
  job?.cancel()
}

A more elaborate example that consistently crashes due to the IllegalStateException thrown in onResume can be found here. The repo contains a complete executable project.

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:28 (13 by maintainers)

github_iconTop GitHub Comments

7reactions
cbrueggcommented, May 5, 2017

Is there any problem to allow the code to be excecuted on the main thread after onDestroy per se?

Actually I believe this issue isn’t limited to the main thread or even onDestroy. I’d say no matter what thread, it is at least unintuitive that a coroutine can resume execution after the corresponding job has been cancelled.

The problem, as I see it, is with the code that tries to use that state that was already destroyed in an implementation of onDestroy.

First, I think we need to to define what a resumption is. Is it the process of posting coroutine code to a queue or is it the actual start of execution? I’d argue for the latter definition since it doesn’t depend on the internals of the dispatcher and thus may be what users intuitively think when they don’t specifically observe the behavior of the coroutine library, which isn’t very easy to grasp.

Any program that assumes that code following a suspension point of call to a cancellable function will not be executed after the job is cancelled even on the same thread is broken right now. In the following example the call to bar() will cause a crash due to state modification that happened before resumption, even though it happened on the same thread and the job was cancelled already. If I didn’t know better, I wouldn’t expect this to happen since I’d expect bar not to run if it didn’t yet, setting didBarRun = true in the process.

coroutine

I am not yet convienced that the idea of directly modifing UI state from the coroutine is a sound arhitectural pattern that we shall endorce by providing the corresponding utility functions.

Are you sure people won’t do this anyway even without proper library support? It compiles and seemingly works, except it will cause hard-to-debug crashes every once in a while, which will frustrate developers. I think the best you can do is provide appropriate tools to let them produce correct, though potentially architecturally unsound code.

Having given the above comment, I must stress that this is still an issue. It has to be solved.

Thanks for the acknowledgement and this discussion! 🙂

6reactions
cbrueggcommented, Apr 24, 2017

Thanks, I’ll give that gist a try later today!

Asynchronous actions (coroutines) that are bound to UI lifecycle is a design smell, if I may say so.

I disagree. In the commonly used MVP architecture, binding asynchronous actions to the UI lifecycle is frequently done. For example, take a look at the CountriesPresenter in this example of the popular Mosby library. It fetches something from the network and then displays it in the UI, which is fine from a UX perspective if a progress indicator was shown before. It’s also fine from a lifecycle POV since it checks isViewAttached() before accessing the UI in the callbacks.

This could be similarly achieved using RxJava, but without having to check isViewAttached():

var subscription: Subscription? = null
fun foo() {
  subscription = downloader.download()
                            .subscribeOn(Schedulers.io())
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe { downloaded -> ... }
}
fun onPause() {
  subscription?.cancel()
}

I may be biased, but I’ve seen this pattern in a lot of apps already. Replacing it with the snippet from my initial post here seemed natural, but currently does not work as expected.

MVVM is an architecture that might work better with the current coroutine implementations in this library, but that I don’t think architectures like MVP with asynchronous actions are a design smell. I’d argue that this library should ideally work well with all architectures.

E.g, when screen is rotated, then views should be destroyed and rebuilt, but all the models should continue to operate and to execute their asynchronous activities.

As mentioned before this doesn’t apply when the application is finishing / shutting down. In that case, we’re forced to either check isViewAttached() (or something similar, but after every suspending call, which is very error-prone) or have a reliable way of cancelling Jobs.

I hear your concern about channels, but I’m not familiar enough with the internals of this library to think of a solution. Thanks already for taking the time to investigate this!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Handling Lifecycles with Lifecycle-Aware Components
Best practices for lifecycle-aware components · Keep your UI controllers (activities and fragments) as lean as possible. · Try to write data- ...
Read more >
Managing your storage lifecycle - AWS Documentation
Use Amazon S3 to manage your objects so that they are stored cost effectively throughout their lifecycle.
Read more >
What is Full Lifecycle API Management? - MuleSoft
An API-first approach emphasizes the importance of the entire lifecycle by strategically treating APIs as products and managing them as such.
Read more >
Get to know the incident response lifecycle - Atlassian
The incident response lifecycle is your organization's step-by-step framework for identifying and reacting to a service outage or security threat.
Read more >
Event Handling Lifecycle | Lightning Aura Components ...
This container component locates all relevant event handlers for further processing. 2.2 Application Event. Any component can have an event handler for this ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Hashnode Post

No results found