quickstart-unity icon indicating copy to clipboard operation
quickstart-unity copied to clipboard

[Crashlytics][FR] Callback after crash happened

Open MartinGonzalez opened this issue 4 years ago • 7 comments

Is there a way to get a callback into my app when a crash happened in the previous session?

MartinGonzalez avatar May 04 '20 13:05 MartinGonzalez

This issue does not seem to follow the issue template. Make sure you provide all the required information.

google-oss-bot avatar May 04 '20 13:05 google-oss-bot

In this thread they talk about android callbacks, but seems te be old.

If native implementations have callback can someone point me in that direction please?

MartinGonzalez avatar May 26 '20 01:05 MartinGonzalez

Seems like there is native implementation, boolean instead of a callback, here. Android: https://firebase.google.com/docs/reference/android/com/google/firebase/crashlytics/FirebaseCrashlytics#didCrashOnPreviousExecution() iOS: https://firebase.google.com/docs/reference/ios/firebasecrashlytics/api/reference/Classes/FIRCrashlytics#/c:objc(cs)FIRCrashlytics(im)didCrashDuringPreviousExecution

But does not seems to be available in Unity SDK.

I can file a feature request for you. Shawn

chkuang-g avatar May 28 '20 19:05 chkuang-g

We don't currently offer a didCrashOnPreviousExecution API for Unity because the vast majority of Unity events that are collected by Crashlytics are caught by the Unity runtime and therefore did not result in a hard crash. (Internally, they are reported to Crashlytics similar to how a caught exception would be manually reported in Android. This is why the Crashlytics dashboard lists them as "non-fatals".)

Most app developers who use the didCrashOnPreviousExecution APIs for Android & iOS use them to pop a dialog to the customer apologizing for the crash and offering a discount, or requesting more info for a bug report, etc. This wouldn't make sense for a logged Unity exception because the customer may or may not have actually had a degraded experience as a result of the exception, and it is hard / impossible to know that without analyzing the exception report pretty closely.

Crashlytics can also catch hard crashes in the Unity runtime, which will be reported as fatals in the Crashlytics console. If we added a didCrashOnPreviousExecution for Unity, it would only return true for those hard crash cases. In our experience, this is fairly rare (in C# code, at least) and there hasn't been a lot of customer demand for it, though let us know if you believe it would be valuable! It would be great to hear how you might use it.

mrichards avatar May 28 '20 20:05 mrichards

Having this didCrashOnPreviousExecution on those hard crashes would be really useful since we are looking for understanding users funnels. We would like to send a metric to our analytics services. Also as you said if we would like to give any discount/bug report we can improve the user experience based on that.

MartinGonzalez avatar Jun 17 '20 12:06 MartinGonzalez

@mrichards Is there any possibility to add this feature?

MartinGonzalez avatar Jul 01 '20 23:07 MartinGonzalez

@mrichards I found a way to get this value working. Is there a way to contribute in the SDK?

Tested on Unity 2018.4.16f1 + Crashlytics 6.15.2 in Android, iOS and Unity Editor.

Crashes were seen in Firebase Console as crashes in both platforms.

Main Class

This would be awesome to have it under Firebase Crashlytics class.

public static class CrashlyticsUtils
{
    private static ICrashlyticsUtils _crashlyticsUtilsImp;

    static CrashlyticsUtils()
    {
        _crashlyticsUtilsImp = GetCrashlyticsUtils();
    }

    public static bool DidCrashInPreviousExecution
    {
        get { return _crashlyticsUtilsImp.DidCrashInPreviousExecution; }
    }

    public static void ForceNativeCrash()
    {
        _crashlyticsUtilsImp.ForceNativeCrash();
    }

    private static ICrashlyticsUtils GetCrashlyticsUtils()
    {
#if UNITY_ANDROID && !UNITY_EDITOR
        return new AndroidCrashlyticsUtils();
#elif UNITY_IOS && !UNITY_EDITOR
        return new IOSCrashlyticsUtils();

#else
        return new EditorCrashlyticsUtils();
#endif
    }
}
public interface ICrashlyticsUtils
{
    bool DidCrashInPreviousExecution { get; }
    void ForceNativeCrash();
}

Android

#if UNITY_ANDROID && !UNITY_EDITOR
using System;
using UnityEngine;

public class AndroidCrashlyticsUtils : ICrashlyticsUtils
{
    public bool DidCrashInPreviousExecution
    {
        get
        {
            try
            {
                var firebaseClass = new AndroidJavaClass("com.google.firebase.crashlytics.FirebaseCrashlytics");
                var getInstance = firebaseClass.CallStatic<AndroidJavaObject>("getInstance");
                return getInstance.Call<bool>("didCrashOnPreviousExecution");
            }
            catch (Exception e)
            {
                Debug.LogError("Error accessing to FirebaseCrashlytics class.");
                return false;
            }
        }
    }

    public void ForceNativeCrash()
    {
        var message = new AndroidJavaObject("java.lang.String", "This is a test crash, ignore.");
        var exception = new AndroidJavaObject("java.lang.Exception", message);
        var looperClass = new AndroidJavaClass("android.os.Looper");
        var mainLooper = looperClass.CallStatic<AndroidJavaObject>("getMainLooper");
        var mainThread = mainLooper.Call<AndroidJavaObject>("getThread");
        var exceptionHandler = mainThread.Call<AndroidJavaObject>("getUncaughtExceptionHandler");
        exceptionHandler.Call("uncaughtException", mainThread, exception);
    }
}
#endif

iOS

#if UNITY_IOS
using System.Runtime.InteropServices;

public class IOSCrashlyticsUtils : ICrashlyticsUtils
{
    [DllImport("__Internal")]
    private static extern bool didCrashDuringPreviousExecution();

    [DllImport("__Internal")]
    private static extern void crash();

    public bool DidCrashInPreviousExecution
    {
        get { return didCrashDuringPreviousExecution(); }
    }

    public void ForceNativeCrash()
    {
        crash();
    }
}
#endif

And you need to add under Plugins/iOS the following .mm

//
//  CrashlyticsUtils.mm
//  Unity-iPhone
//
//  Created by Martin Gonzalez on 03/08/2020.
//

#import <Foundation/Foundation.h>
#import <FirebaseCrashlytics.h>

extern "C" {
    bool didCrashDuringPreviousExecution() {
       return [[FIRCrashlytics crashlytics] didCrashDuringPreviousExecution];
    }
    
    void crash() {
        strcpy(0, "crash!");
    }
}

Usage

public class Demo : MonoBehaviour
{
    public TMP_Text txtDidCrashed;
    public Button btnForceCrash;
    private Firebase.FirebaseApp _app;

    void Start()
    {
        // Subscription to ForceCrash Button
        btnForceCrash.onClick.AddListener(CrashlyticsUtils.ForceNativeCrash);

        // Firebase Initialization   
        Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
        {
            var dependencyStatus = task.Result;
            if (dependencyStatus == Firebase.DependencyStatus.Available)
            {
                Debug.Log("Firebase Initialized");
                _app = Firebase.FirebaseApp.DefaultInstance;
                
                // Showing if previous execution crashed in Text
                txtDidCrashed.text = "Did Crash on Previous Execution? " + CrashlyticsUtils.DidCrashInPreviousExecution;
            }
            else
            {
                Debug.LogError($"Could not resolve all Firebase dependencies: {dependencyStatus}");
            }
            //Need to add TaskScheduler.FromCurrentSynchronizationContext() to go back to main thread.
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }
}

MartinGonzalez avatar Aug 03 '20 22:08 MartinGonzalez