Android Crashlytics – restrict network access-ThrowExceptions

Exception or error:

I’ve been working for a while around a lack of ability to restrict Crashlytics network usage under certain conditions. For example – on roaming, on metered networks and so on..

According to SDK documentation, only two options I found addressing somehow this:

  • “Opt Out” on runtime by simply not initialize Crashlytics

  • built-in User consent dialog before sending a crash report

This API’s are very limited, because:

  • Not initialize Crashlytics not only prevents network access but also prevents any chance Crashlytics will save locally the crash report so that eventually the event will be sent. Not to mention there is no good way to opt out in runtime, besides overriding brutally the Thread.setUncaughtExceptionHandler

  • consent dialog not making any sense to the user if a crash happens in the background.

My question basically:
Am I missing something?
Is there any way to restrict Crashlytics network access?

My motivation comes from a need to prevent situation my app uses network bandwidth potentially can cost money to the user under certain conditions, although “cellular network” or “use data over roaming” device settings are enabled.

How to solve:

I’m the former maintainer of the Crashlytics SDK for iOS/macOS. I’m relatively unfamiliar with the Android version of the SDK, and definitely unfamiliar with Android in general. But, I’ll give this a shot.

What you want to do is something that has been requested on the iOS side a few times. I would have loved to do it actually, because it seems pretty terrible to force end-users to incur these costs. However, the iOS SDK’s networking and start up routine are both very complex and very delicate. It is highly challenging to guarantee that crashes are delivered and that there are zero possibilities for inconsistent states. I believe that Android is simpler here, but I cannot say this with authority.

The iOS SDK, however, does have some hooks for additional client-level functionality. Check out the warning around one of those APIs:

 *  @warning Just implementing this delegate method will disable all forms of synchronous report submission. This can
 *           impact the reliability of reporting crashes very early in application launch.

Basically, in order to satisfy the contract of this particular API, some techniques to improve reporting reliability have to be disabled. The thing is, sometimes it’s worth it. Lots of apps decide to make this tradeoff. Many apps also delay initializing Crashlytics to eek out extra performance. This has a huge impact on reporting reliability, but that’s another tradeoff app developers have to make.

I think you should seriously consider just not enabling Crashlytics in these situations, if you can easily detect them. Maybe Android even allows end-users to do this on a per-app basis? In that case, you’d never get any reports anyways. I would imagine that your user base is diverse enough that missing some reports in these situations wouldn’t be that terrible. Or, perhaps you’d like to surface it as a user-facing option.

You could even do something totally crazy, like override Thread.setUncaughtExceptionHandler yourself, and buffer up exceptions during this situation to disk. And then, replay them to Crashlytics when things are better. Turn it into an open source lib. I bet people will love it! Possibly not the Crashlytics’ Android team though 😉 (Hi!)

This is also basically the same recommendation Gastón offered above, with just some extra context around what I’ve seen on the iOS side. Also shoot the Crashlytics people an email asking for this. I think it’s a great idea.

###

There is not a way to restrict the internet usage for Crashlytics in an application. But how I would fix it is to either give the user information that Crashlytics is using roaming or just save the crash report locally and send them once the user in connected with a wifi network. Also you could give the user the choice if he prefers to save the crash reports locally or send them right away over roaming.

  1. Save the ErrorLog locally on the device
  2. Upload the ErrorLog once a connection with a wifi is established

You should be able to use the ConnectivityManager to get the state of the Wi-Fi adapter. From there you can check if it is connected or even available.

ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

if (mWifi.isConnected()) {
    // post error logs
}

###

There is two step process which we are using in our app, this is not using Mobile Network and also not related to roaming as well.

  1. Saving crash logs to file in app data partition i.e. on device:

    Refer to this link

  2. Upload crash data to server when WiFi network is connected:

    public class ConnectivityStatusReceiver extends BroadcastReceiver {
    
      @Override
      public void onReceive(Context context, Intent intent) {
    
        final ConnectivityManager connMgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    
        NetworkInfo activeNetworkInfo = connMgr.getActiveNetworkInfo();
    
        if (activeNetworkInfo != null && activeNetworkInfo.getTypeName() == "WIFI") {
          // post your crash logs to server
        }
      }
    }
    

###

I was reading the docs at fabric and I just found something interesting

Crashlytics processes exceptions on a dedicated background thread, so
the performance impact to your app is minimal. To reduce your users’
network traffic, Crashlytics batches logged exceptions together and
sends them the next time the app launches.

So I was thinking about a workaround since the crashes without network are being sent when the app is initialized, you could prompt any dialog to the user at startup telling if they want to connect to the internet to send crash reports to solve current problems in the app. ( so you are using their network data with the user consent)

The thing here is we don’t know how to stop crashlytics from sending this reports, they will store it at the device if the device is offline and send it back after the device is just with connection again as it states here

Another way out could be just log important fatal issues with the custom login they offer and just send them, you can find more about it here

To make sure that sending crash reports has the smallest impact on
your user’s devices, Crashlytics logs have a maximum size of 64 KB.
When a log exceeds 64 KB, the earliest logged values will be dropped
in order to maintain this threshold.

In conclusion, after reading the docs, there is no way to disable crashlytics to constantly send reports, you can only manage the network connection of the user when you want them to send or not reports. Its like connectivity is the switch on and off of crashlytics at the moment

it just talks about “Reducing network traffic” but not about disabling crashlytics network at all.

Another way that comes to my head is to make a flag for starting crashlytics, and then use inside a condition Crashlytics.start()

When you want to disable it just do the following

CrashlyticsCore core = new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build();
Fabric.with(this, new Crashlytics.Builder().core(core).build());

playing with these two things is the only way I think it’s possible to reduce network usage of crashlytics at the moment

###

You can restrict Crashlytics network usage by a static field.

Define a static global variable, according to its value write logic for your Crashlytics.

private static boolean INROAMING = false;

Now you can use below logic for your purpose. Like don’t provide co

if(isInternetIsConnected(this).equals("MOBILE")){
        if(INROAMING){
            //write your logic for context here, when phone is in roaming
            //restrict logic for crashlytics
        }else{
            //write your logic for context herem, when phone is not in roaming
            //un-restrict logic for crashlytics
        }
    }

public boolean checkForRoaming() {
        final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        PhoneStateListener phoneStateListener = new PhoneStateListener() {
            @Override
            public void onServiceStateChanged(ServiceState serviceState) {
                super.onServiceStateChanged(serviceState);
                if (telephonyManager.isNetworkRoaming()) {
                    // In Roaming
                    INROAMING = true;
                } else {
                    // Not in Roaming
                    INROAMING = false;
                }
                // You can also check roaming state using this
                if (serviceState.getRoaming()) {
                    // In Roaming
                    INROAMING = true;
                } else {
                    // Not in Roaming
                    INROAMING = false;
                }
            }
        };
    }

    public String isInternetIsConnected(Context context) {
        try {
            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            assert cm != null;
            @SuppressLint("MissingPermission") NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
            if (activeNetwork != null) { // connected to the internet
                if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
                    // connected to wifi
                    return "WIFI";

                } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
                    // connected to the mobile provider's data plan
                    checkForRoaming();
                    return "MOBILE";
                }
            } else {
                // not connected to the internet
                return "NO CONNECTION";
            }
        } catch (Exception e) {
            e.printStackTrace();

        }
        return "NO CONNECTION";
    }
}

###

There is not a way to restrict the internet usage for Crashlytics in an application. You can give choice to user, if he prefers to save the crash reports locally or send them right away over roaming.

Save the ErrorLog locally on the device

Upload the ErrorLog once a connection with a wifi is established.

You can use ConnectivityManager to get the state of the network. You can check if it is connected or even available.

ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

if (mWifi.isConnected()) {
    // send error logs
}

Above code you can add in broadcastreceiver which will notify connection

Example:

 public class ConnectivityStatusReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {

  ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  NetworkInfo mWifi = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);

  if (mWifi.isConnected()) {
   // send error logs
  }
 }
}

Leave a Reply

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