Add more information using Crashlytics with custom Errors

Project code:

Level:

Easy

When using crashlytics with custom errors not much information appears on the dashboard. In this post we are going to see how to expand the information received in Crashlytics when our application suffers a custom error.

Let’s assume that Crashlytics has already been configured in the project, otherwise you can follow the official documentation.

Default crash

We start by creating a custom error, with just an example case. Throughout the post we are going to edit this object to cover all cases.

enum MyCustomError: Error {
    case loginError
}

We will use the default view controller to force an error. We add a button to the view, and on the click action, we’re going to call a method that can return an error.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func onClickButton(_ sender: Any) {
        self.someMethodWithCompletionHandlerAndError { error in
            if let error = error {
                Crashlytics.crashlytics().record(error: error)
            }
        }
    }
}

The someMethodWithCompletionHandlerAndError method is only for testing, it has the following structure:

private func someMethodWithCompletionHandlerAndError(completion: (MyCustomError?) -> Void)

When we run the app and press the button, the method returns the error and we log that error in Crashlytics. After this, when we check for the crash in Crashlytics, there is no information about what happened, just the line of code where the app crashed and the name of the enum we created earlier.

How can we improve the information received to crashlytics?

There are two protocols that we can implement to describe an error. These protocols are the ones that crashlytics uses to display information about the error received, so we are going to implement the protocols LocalizedError and CustomNSError.

enum MyCustomError: Error, LocalizedError, CustomNSError {
    case loginError
    
    public var errorDescription: String? {
        switch self {
        case .loginError:
            return "\(self)"
        }
    }
    
    public var errorUserInfo: [String: Any] {
        return [NSLocalizedDescriptionKey: self.errorDescription ?? ""]
    }
}

We run the application again and press the button to generate another error. When we have already generated the crash, we will consult the crashlytics dashboard and the case appears as another key.

With only one case in the enum it doesn’t make much sense to add the localized error, but if the error enum grows, and each instance has variables, it adds a lot of value to having all the data.

enum MyCustomError: Error, LocalizedError, CustomNSError {
    case loginError(reason: LoginErrorReason)
    case userError(reason: UserErrorReason)
    case apiError(reason: ApiErrorReason)
    
    public var errorDescription: String? {
        switch self {
        case let .loginError(reason):
            return "\(self) - \(reason)"
        case let .userError(reason):
            return "\(self) - \(reason)"
        case let .apiError(reason):
            return "\(self) - \(reason)"
        }
    }

    public var errorUserInfo: [String: Any] {
        return [NSLocalizedDescriptionKey: self.errorDescription ?? ""]
    }
}

With this error structure, the types of errors can be defined in a finite list of cases, making it easier to use and to identify what has happened.

enum LoginErrorReason {
    case wrongCredentials
    case serverError
    case unknown
}

Now when consulting the Crashlytics dashboard, the specific case that has been logged now appears in the keys section:

I hope this post helps you to have more visibility on your custom errors. If you use another method, leave it in a comment so I can try it! Thank you for reading this far.

Leave a Reply

Your email address will not be published. Required fields are marked *