A couple of months ago at Hole19, we dedicated some time to improve the overall experience of the users in our app. Since our app is meant to be used by golfers, any crash that happens during their round of golf should be considered very serious.

Our motto ❤

Unfortunately, we had recently encountered a very mysterious crash in our courses images screen which affected some particular group of users. This, as we know, confronts them with that hideous system crash dialog; something that breaks the user experience and hinders their progress. Since we’d still yet to discover the cause, we thought that we could at least improve the users’ experience by detecting the crash and try to re-open the app gracefully in the same screen (this was greatly inspired by this Pierre-Yves Ricau talk).

One of the default system crash dialogs in our “crashing” screen.

Our task had the following goals:

Remove the system crash dialog

Gracefully recover from the crash by re-entering the screen where the crash happened; however, if the same crash happened a second time we allow the system to handle it by stopping the process and showing the dialog.

Log the crash to our crash reporting tool (in our case, it is Fabric)

So, to start it off we first should handle this in our Application class with a method that sets up the crash handling:

The idea here is that we should get access to both the android default exception handler and the Crashlytics handler so that we can use it in our custom exception handler. Bear in mind that it is crucial to set a dummy exception handler before setting up Crashlytics. Why? Because when the app crashes, Crashlytics reports it first and then let the system handle the error by calling the default exception handler. We still want Crashlytics to report the crash, but don’t want the system to handle it, so we need to override the default exception handler.

In our AppExceptionHandler , we start off by registering an ActivityLifecycleCallback so that we can keep track which Activity is in foreground (if any). This will be crucial to help us to decide on how to handle each crash.

So, when a crash happens the uncaughtException method will be run. Here’s the logic:

First, we check if there is an activity in foreground or not. If there is no activity then the crash probably happened in a background component, as a service, or in the Application class. For that reason we have no other option than to log the exception to Crashlytics and then default to the Android exception handler.

If there is indeed an Activity in foreground there are three possible scenarios/outcomes:

It is the first time that crash happened in the activity — We log the crash to our reporting tool and we restart the activity with a special RESTARTED flag (so that we can identify later if this activity was previously “gracefully” restarted or not), and with the LATEST_EXCEPTION storing the exception that originated the crash (so that we can compare new exception with it). The RESTARTED flag is already set and the latest caught exception is of the same type of the new one; this way we assume that it is the second time that the crash happened in the activity (so we may be in a potential loop). When this happens our decision is to default to the Android exception handler (presenting the system crash dialog and killing the app). The RESTARTED flag is already set, but the caught exception is different from the one that caused the first restart of the app. For that reason, we follow the same logic as in the scenario 1.

Hopefully, this can help you in providing a better experience to your users; Reach me out if you think this can be improved (I’m sure it can 😅).