It happens all the time.
Your app seems to work perfectly
You deploy it to Google Play
And suddenly it blows like a time bomb…
I wrote about 6 tricky Android bugs that may be the reason.
Read on to make sure that your app is safe
1. Serialization issues
No matter if you use Serializable or Parcelizable (latter is preferred), it happened to everyone. And it’s a really easy mistake to make. I’m talking about crashes when you forget to serialize your models.
You can also run into obfuscation problems in shared preferences. A not serialized model may become obfuscated and impossible to retrieve.
There is a second critical case. A few times I used the wrong model with a similar name that was not serialized. I used it for a Retrofit response. It caused crashes, and unfortunately, not on the development environment but only when I sent requests to their endpoints.
It’s easy to prevent these bugs but not on development builds. Ideally, test it using a ‘stage’ build type. If you don’t have a staging environment, consider creating one. By the way, I haven’t found many good articles about build environments to link here. Maybe I will create one. If you know any good ones, link it in comments, please.
Remember that you can always check it again on google play before publishing. What’s important is to test your app on an environment with enabled minification. This way you can catch problems with serialization. For example, models that you forgot to serialize or broken shared preferences.
By the way, you shouldn’t set minifyEnabled to true on the dev environment. Why? Because it won’t allow you to debug your code even if you set debuggable to true. If you want to debug on stage, set minifyEnabled to false temporarily but don’t commit it.
To test app compatibility, using ADB idea, remove app data from your device. Then build the previous version. Do some operations to fill in shared preferences. Then install a new version and test its behavior.
2. Mutating Drawable
This one is really tricky. Some people even blame Android for this bug and never fix it. The issue is in bad icon colors across your app. To understand this issue you have to know how getting Drawable works.
When you get Drawable by id, you retrieve a Drawable with a state that is shared across your app. That’s why if you get a Drawable and change its state, for example, if you tint it- you will share the tinted state across your app. Retrieve Drawable by the same id anywhere else and its state will be affected. The result? Icons with unreasonable colors.
Is it a big deal? Well, sometimes this issue just looks bad but other times it’s a critical problem. What if you tint your icon so it shows active (green) or inactive(gray) state and it doesn’t work properly? Or what if your black icon becomes white on a white background?
That’s why you should make sure to mutate Drawable before modifying it. Mutate returns a Drawable that doesn’t share its state and can be modified exclusively. But what if you don’t mutate Drawables and your icons work well? Add a few more icons and they won’t. Remember to mutate drawable before editing it.
3. Property setters issue
This problem become a real deal when Kotlin came in. That’s because Kotlin brought properties with setters. Properties are super useful but it’s really easy to make a mistake. Look,
if you go through a few layers to get data from your data source, you may have a state issue. For example, let’s say that you use UserPresenter, UserRepository and UserSharedPreferences. What if properties in every layer have setters? In this case, if you update your userId from UserPresenter, skipping userRepository (using UserSharedPreferences directly), you will update the value in UserSharedPreferences but userId in UserRepository remains the same.
You may think that never skipping a repository would solve this problem. But it’s not a good solution. What if you need userId in your OkHttp interceptors or in another unconventional place? You may soon face Dagger dependency cycle issues. And you may have to inject UserSharedPreferences directly.
There is another scenario: what if you don’t work on your project alone? Your teammates may forget about the access only-through-repository rule.
That’s why it’s a better idea to usually use setters only in your last element of data flow- in our example that would be UserSharedPreferences. Let UserRepository access shared preferences but avoid setters in there.
4. Renaming too much
Sometimes rename goes so bad that it even renames icons on your desktop. Well, ok, probably not. But there is another place. There is a big chance that if you rename userId, you may refactor endpoints in your Retrofit interfaces as well.
My next favorite place is Shared Preferences, namely the keys. If you read this and think: Yes, “I got them keys, the keys, the keys” then better secure them. Otherwise, prepare for major bug alert. And by the way, please extract them to constants.
The last one is NotificationService or whatever you name it. The place where you prepare models for payload. Keep this place in mind too.
Why do I mention the renaming issue in this article? Because it’s easy to destroy your app especially if you don’t create a Merge Request. But even if your Code Reviewer usually reads your merge requests attentively, there is a big chance that he won’t notice these issues in your refactors with 100+ changes.
I showed you my favorite time bomb Android bugs. These bugs don’t blow immediately but may surprise you in the future. Keep them in mind and stay bug-free.
BTW, you can subscribe to my newsletter for more Android articles. Join in below.