Android Allow User Settings to Disable Fabric/Firebase Crashlytics (GDPR)

August 29, 2019

If you have yet to setup Crashlytics, refer to Setup Firebase Crashlytics On Android For Crash Reporting.

If are not familiar with Android settings UI using PreferenceFragment, refer to Android Settings Preference Using PreferenceFragmentCompat.

To allow runtime/user enable/disable Crashlytics, you must disable Crashlytics through AndroidManifest.xml.

<manifest ...>
    <application ...>
        <meta-data
            android:name="firebase_crashlytics_collection_enabled"
            android:value="false" />
    </application>
</manifest>

During application startup, check SharedPreference value to enable Crashlytics if required.

class LuaApp : Application() {
    override fun onCreate() {
        super.onCreate()

        val context = this
        val settingsSharedPref = PreferenceManager.getDefaultSharedPreferences(context)

        val isEnablCrashReport = settingsSharedPref.getBoolean(context.getString(R.string.pref_key_enable_crash_report), true)

        /*
        if (isEnablCrashReport) {
            Fabric.with(this, Crashlytics())
        }
         */

        val crashlytics = Crashlytics.Builder()
            .core(CrashlyticsCore.Builder().disabled(!isEnablCrashReport).build())
            .build()
        Fabric.with(this, crashlytics)
    }
}

Edit your PreferenceFragment settings file (e.g. res/xml/pref_settings.xml) to add SwitchPreference for Crashlytics.

<PreferenceScreen ...>
    <PreferenceCategory
        android:title="Privacy"
        android:key="pref_key_privacy_settings">

        <SwitchPreference
            android:key="@string/pref_key_enable_crash_report"
            android:title="Enable Crash Report"
            android:defaultValue="true"
            />

    </PreferenceCategory>
</PreferenceScreen>

Edit your PreferenceFragment in Activity.

class SettingsFragment: PreferenceFragmentCompat() {
    override fun onPreferenceTreeClick(preference: Preference): Boolean {
        return when (preference.key) {
            getString(R.string.pref_key_enable_crash_report) -> {
                val isEnablCrashReport = (preference as SwitchPreference).isChecked
                context?.also { context ->
                    if (isEnablCrashReport) {
                        Fabric.with(context, Crashlytics())
                    }
                    else {
                        AlertDialog.Builder(context)
                                .setMessage("Need to restart app for changes to take effect.")
                                .setPositiveButton(android.R.string.ok) { dialog, whichButton ->
                                    ProcessPhoenix.triggerRebirth(context)
                                }
                                .setNegativeButton(android.R.string.cancel, null)
                                .show()
                    }
                    Log.d(TAG, "isEnableCrashReport=$isEnablCrashReport")
                }
                true
            }
            else -> {
                super.onPreferenceTreeClick(preference)
            }
        }
    }
}

NOTE: Application restart is required to disable Crashlytics during runtime, where ProcessPhoenix is used.

If you bump into the following exception

java.lang.RuntimeException: Unable to create application com.luasoftware.luapass.LuaApp: io.fabric.sdk.android.services.concurrency.UnmetDependencyException: This app relies on Crashlytics. Please sign up for access at https://fabric.io/sign_up,
install an Android build tool and ask a team member to invite you to this app's organization.

Given that your previous Crashlytics is successful before:

  • Don’t use ext.enableCrashlytics = false in Module build.gradle’s buildTypes (both debug and release)
  • Don’t enable Crashlytics in AndroidManifest.xml (make sure firebase_crashlytics_collection_enabled is false)

NOTE: Refer to Crashlytics Data Collection Policy

Caveats

If you disable Crashlytics, calling Crashlytics.logException or Crashlytics.getInstance will cause the following exception.

java.lang.IllegalStateException: Must Initialize Fabric before using singleton()
    at io.fabric.sdk.android.Fabric.singleton(Fabric.java:302)

As of version 2.9.8, there is no way to check if Crashlytics is enabled/initialized or not. Write a wrapper function instead.

fun logException(throwable: Throwable) {
    try {
        Crashlytics.logException(throwable)
    }
    // java.lang.IllegalStateException: Must Initialize Fabric before using singleton() when Crashlytics is disabled
    catch (e: Exception) { }
}

NOTE: Exception log by Crashlytics.logException is marked as Non-fatals, where you need to adjust the Event type = "Non-fatals" filter to view them in Firebase Console - Crashlytics.

UPDATE 2019-08-29:

To prevent Crashlytics.logException from throwing exception when disabled, initialize with the following initialize with the following during application startup.

val crashlytics = Crashlytics.Builder()
    .core(CrashlyticsCore.Builder().disabled(true).build())
    .build()
Fabric.with(this, crashlytics)

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.