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.
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.
Saving crash logs to file in app data partition i.e. on device:
Refer to this link
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
}
}
}
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.
Save the ErrorLog locally on the device
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
}
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.
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. It's 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
}
}
}
Firebase Realtime database has a function goOffline, goOnline which can manage the connection manually. But, Firestore doesn't have these methods.
Does Firestore return exception something like NETWORK_ERROR when device lost connection with server? If it doesn't, how can I manage Firestore connection manually when device can't connect to internet and reconnected. (e.g. Airplane mode, Bad wifi)
In my case, I don't use persistence mode.
Does Firestore return exception something like NETWORK_ERROR when
device lost connection with server?
You have use addOnFailureListener which uses OnFailureListener that can just gives you an Exception object to find the cause or message behind the exception via getCause or getMessage and further can apply String checks like contains etc to verify the cause but currently there is no standard way provided by FirebaseFirestore.
how can I manage Firestore connection manually when device can't connect to internet and reconnected. (e.g. Airplane mode, Bad wifi)
You can check the connectivity yourself also at desired places before executing firebase code
I'm trying to make an employee attendance app where the users (employees) could punch in when they come to the office. The details they punch-in will be sent to the server. The major focus in on the time.
I am trying to implement offline functionality as well. (If the app doesn't detect internet connectivity, the punched-in record will be stored on the local db (SQLite) , and once it does, push the record from the db (and clear it) onto the server.
I tried capturing the time using the GregorianCalendar class but the time values seem to be vulnerable to user manipulation. (Especially Scenario A)
Scenario A
A user could turn internet connectivity off, turn Automatic Data & Time off, manually set the time and then open the app to punch in.
Example: Mr. X comes to office at 8.45AM, turns airplane mode on, manually sets the device time (say 8.30AM) and then punches-in the record. The time value that he sets gets entered in the db instead of the actual time he came in.
How do I prevent this from happening?
Scenario B
A user could just edit the local db values manually (rooted phones). [I know this is inevitable but any suggestions to make his harder?]
Scenario A:
Solution 1: only accept values when there is an internet connection:
private boolean haveInternet()
{
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}
Solution 2: ping your server for the timeDate.
Solution 3: register a broadcast receiver for the timechange event and disable the app until you can ping your server (taken from here: https://stackoverflow.com/a/20766107/2540578)
Scenario B: encrypt the data. There are multiple solutions to do this, but it depends on what you are using (sqlite, realm, greendao etc)
I have check for internet connectivity that goes like this:
public static boolean isInternetAvailable(Context ctx)
{
NetworkInfo info = ((ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (info == null || !info.isConnected()) return false;
if (info.isRoaming())
{
return Preferences.getIsRoamingAllowed(ctx);
}
return true;
}
It's been working good for all installations so far. Today user came in with a phone where everything worked (browser, email, etc) and it wasn't roaming. But my app due to this check was giving "No connection" error.
Phone is HTC Droid ERIS with 2.1
Anyone saw this issue? Any pointers why?
Ok I have written a test application that gets the activenetwork and all networks and lets me see what is happening, I am about to go out and test this since the anomalies I am seeing happen when switching from one network to the other (as in when you go out of wifi range and into cdma etc)
Couple of things that might help regardless first you can change info.isConnected to the following
if (info == null || !info.isConnectedOrConnecting()) return false;
This makes it a little more robust in that if you are in the middle of switch over you still let the user logon
Second thing is that you said that your app denied login if roaming because your apps allow roaming preference was set to false
return Preferences.getIsRoamingAllowed(ctx);
I think you need to follow a different pattern (just my opinion) first because If the user has disallowed roaming via their settings (phone not your app) and they are on a roaming network then the .getActiveNetwork() will return null, or not connected or not available (in which case the .getReason returns "noroaming")
Personally I would let the phone decide, but if you need to restrict it then he pattern I would follow would be
Set the default to true, but note that it's the first time your activity has started and the user has had no chance to set anything (since no pref's are set this should be easy enough to detect) Detect your network connection, if you have one then also note if they are roaming
Prompt them with an alert dialog which warns them they are currently roaming and Ask them if they want to login now or wait until later
OR
Normal Login if they are not roaming
But in either case offer them the ability to set the "roaming" option the first time instead of having them figure it out themselves.
That would address your catch 22 situation and save you some phone calls, anyway that's a design decision but I think it would work out better for you than your current pattern.
Also I think instead of just telling them there is no connection you might want to tell them why, return an enum instead of a boolean and then format dependent on that.
Finally I am going to test a bit more before my final answer because I am seeing some oddity's in the network state but want to confirm my findings before giving you the results, I need this as well so it was a good time for me to dig into this.
I'm making an app that is essentially a web form. I fill it out and send it to a website to be processed. But I only want it to send the data when connected to wifi.
I was thinking of putting the data into a tinyDB then running a check for wifi immediately. If connected it would submit the form and delete the db entry. I'd probably also run a check when the app is loaded and closed. It's important that I don't lose the data.
Is there a better way to do this?
if the form is small, you may not even need a db, you could just use SharedPreferences
http://developer.android.com/guide/topics/data/data-storage.html#pref , unless you have multiple rows of the user answering again and again then this might not be the best solution
here's some network check code if you need it
ConnectivityManager manager =(ConnectivityManager)activityOrContext.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo info = manager.getActiveNetworkInfo();
if(info==null || !info.isConnected()){
return false;// network not active
}
not sure if you need this, you may already know how to do this or have it figured out
Have a look at my question How to respect network use settings in Android. Some of the answers demonstrate how to check the network type.
You can prompt the user to ask him if he wants to enable Wifi, and then you can enable it from code. Take a look at WifiManager class.