Hi, I have a callback when you click cancel in an...
# android
f
Hi, I have a callback when you click cancel in an AlertDialog. I'm trying to figure out why the exception is not caught in this case. Can anyone shed some light on it? https://gist.github.com/frel/38aaa8a05790a401dd45317c527b85eb I assume it's because of the stack trace, but I'm not sure what the alternative would be. This is the simplest version I could think of where I'm able to reproduce the issue. I'm actually trying to make an rx stream error, but instead the app crashes.
c
It’s all due to the scoping. At the point that the
onCancel
callback is fired, it’s being dispatched from the Android Looper, not from your
MainActivity
. The exception bubbles up to the Looper which sent the click event and triggered your callback, not the
onCreate()
which declared the callback. Even though the exception looks like it’s in the scope of that try-catch, it’s not because it’s inside a callback that’s passed to some other scope and invoked asynchronously. Generally-speaking, all async callbacks will have strange effects like this, and that’s where coroutines and RxJava starts to play a role, by making this kind of async data flow more natural to how we think of variable scoping.
👍 2
f
Thank you, that makes a lot of sense. The next question is how could you do something like this then? I have a stream, that starts a download and opens a dialog (that observes on the progress etc). My thought was to throw CancellationException to cancel it, but do you have any other suggestions?
c
You can throw a CancellationException to cancel, but you’d need to handle it from the
onError
callback when you
subscribe
to the stream, wherever that is
f
But that is my problem. It doesn't trigger onError, it crashes the app.
So it's the same issue as this, where the try catch doesn't work
I guess this becomes a bit off topic, so I'll see if I can implement this differently. Thanks for the input
c
Then the callback isn’t being fired as part of the stream. You would have to set up the dialog as a part of the stream (inside
Observable.create { ... }
), catch the exception from the callback, and pass that into the stream’s Emitter
Copy code
Observable.create { emitter ->
        AlertDialog.Builder(requireActivity())
            .setTitle("Downloading...")
            .setView(view)
            .setNegativeButton("Cancel") {
                try {
                    onCancel()
                }
                catch(e: CancellationException) {
                    emitter.onError(e)
                }
            }
        }
        .create()
    }
d
You should never, ever rely on (Dialog)Fragments keeping your callback references around btw, one stop-start cycle is enough to get everything completely bonkers. Better let the calling component (
Activity
or
Fragment
) implement an interface that the
DialogFragment
attaches to in
onActivityCreated
. This also means that you have the stream on the outside of the dialog and under your control. If you want to keep the stream inside of the
AlertDialog
, you must put it into a
ViewModel
(which naturally survives restarts) or any other retaining (headless) fragment.
f
Thanks for the feedback. I'm aware of the lifecycle, and in this case it's actually not supported (instance state is set to null so the whole activity would be created from a clean state) but your solution would potentially fix my issue! Thank you. I guess I would need to use a Subject/Processor to push an error into the stream then.