Android Development Best Practices

Blog by theDroidLady




Introduction

Android development continues to dominate the world of mobile development. Fun projects, great pay, and tons of job prospects are just some of the reasons developers are starting their journeys into the exciting world of the Android operating system. Some experts say that there has never been a better time to learn Android skills, especially since the recent updates, like the addition of Kotlin and improvements to Google’s policies.

It’s been five years now that I’ve been into Android development and there has been no single day I haven’t learned something new. But with these passing years, what I have realized is:
Just writing the code is not enough, Writing in an efficient way is the real challenge.

Tips and not Tricks

1) Choose your App Architecture wisely based on your need, not just what the trend is.

The architecture defines where the application performs its core functionality and how that functionality interacts with things like the database and the user interface.
We have many architectures like MVC, MVP, MVVM, MVI, Clean Architecture.
If any of these architectures is fulfilling your project requirements and you are following the standard coding guidelines and keeping your code clean, no architecture is bad.

2) Consider using SVGs or WebPs for your image drawables.

Supporting multiple resolutions are a sometimes nightmare to developers. Including multiple images for different resolutions also increases the project size.
The solution is to use Vector Graphics such as SVG images or use WebP which can make a big difference in solving the image size problem by compressing lossless images.

3) Choose your layout wisely and separate out the reusable XML and add it using include tag.

We have different layouts like ConstraintLayout, LinearLayout, RelativeLayout, FrameLayout, CoordinatorLayout. I did performance analysis for some of them and found out that one should use layout based on their scenario/requirement only.
Also, If you have some part of your XML getting reused in different layouts, extract it in a separate layout and use tag to avoid replication of code in different layouts.

4) Learn how to use Build types, Product Flavors and Build Variants and make most out of it for faster and easier development.

Build Type

Decides how our code will be compiled. For instance, If we want to sign our .apk with debug key, we put our debug configuration into debug build type. If we want to have obfuscated code when it is compiled and ready to release, we put that configuration on our release build type. If we want to log our HTTP request in debug mode but we want to disable it on release mode, we put that configuration on build types or call build types in library dependencies.

buildTypes
{
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            applicationIdSuffix ".debug"
        }
}

Here is the simplest example:
When you run your app in debug mode, your application package name will be packagename.debug and if you run your app in release mode, your application package name will be packagename.

Product Flavor

Let’s say we have are developing your app for your customer users. Everything is going fine for customer app. Then your product owner said that you need to develop that app for admin users. Admin user app should have all functionalities that customer app has. But also admin user can have access to statistics page and admin user should see the app in different colours and resources. And also your admin app’s analytics should not be mixed with customer app.What will you do? The answer is Product Flavor. Same app, different behaviour.
Edit your Gradle file:

android
 {
    ...
    defaultConfig {...}
    buildTypes {...}
    productFlavors {
        admin {
            ..
        }
        customer {
            ..
        }
    }
}

Build Variants

Combines your build types and product flavors. Sync your project after you update your build.gradle. Then you will see all your build variants.

5) Learn & Use Android Debug Bridge (ADB) to debug your application.

Android Debug Bridge (ADB) is a versatile command-line tool that lets you communicate with a device. It allows you to do things on an Android device that may not be suitable for everyday use, yet can greatly benefit your user or developer experience. For example, you can install apps outside of the Play Store, debug apps, access hidden features, and bring up a Unix shell so you can issue commands directly on the device. ADB provides you with more details than your Android Studio Logcat. Just try it once and you can thank me later :-)

6) Configure your gradle.properties to increase your build speed.

Long build times have always been a problem in the developer’s life.


org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.configureondemand=true
android.enableBuildCache=true
org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
org.gradle.caching= true
android.useAndroidX=true
android.enableJetifier=true
kapt.incremental.apt=true
kapt.use.worker.api=true

Showing you a sample of enhancements I did. You can read more about speeding up here.

7) Keep a check on structural problems in your App code through Lint.

The lint tool helps find poorly structured code that can impact the reliability and efficiency of your Android apps. The command for MAC:

./gradlew lint

For Windows:

gradlew lint

8) Log everything in DEBUG mode only.

We use logs to display useful information, errors, workflows or even to debug something.
But, Every information that we log can be a potential source of security issues! So make sure you remove before the code goes live.
And If you really want to keep these logs, you can either use Timber library which can log your messages and gives you the control over the flow of logs or you can create your own custom class to print logs in debug mode.

9) Never add whole third party library if it’s possible for you to extract specific methods or small no. of classes for your functionality.

Just add those classes to your project and modify them accordingly.

10) Detect and Fix memory leaks in Android App time to time.

“A small leak will sink a great ship.” — Benjamin Franklin
Use memory tools like Leak canary to detect the cause for the memory leak.

Its knowledge of the internals of the Android Framework gives it a unique ability to narrow down the cause of each leak, helping developers dramatically reduce OutOfMemoryError crashes.

11) Handle Configuration changes for your App.

Sometimes handling the orientation changes for your Activity, Fragment or AsyncTasks becomes most frustrating things to deal. If orientation changes are not handled properly then it results in unexpected behaviour of the application. When such changes occur, Android restarts the running Activity means it destroys and again created.
There are different options to handle the orientation changes:
1. Lock screen orientation
2. Prevent Activity to recreated
3. Save basic state
4. Save complex objects

12) Perform validations in screens like input form on Client end only.

Do we really need to hit backend for the validations like Is users email valid or Is user’s contact number is of the required length?
Consider this kind of cases and write your logic accordingly.

13) Don’t create references to activities that will prevent them from being garbage collected when they are done.

Consider a very simple scenario — you need to register a local broadcast receiver in your activity. If you don’t unregister the broadcast receiver, then it still holds a reference to the activity, even if you close the activity.

How to solve this?
Always remember to call unregister receiver in onStop() of the activity.

Like this, Find more scenarios and fix them.

14) Use App Chooser for your implicit intents and always handle NoActivityFound Exception.

If multiple apps can respond to the intent and the user might want to use a different app each time, you should explicitly show a chooser dialog.
Also, if for some reason no app is available, your app shouldn’t crash. Please handle it through try /catch with showing some toast message to the user.

15) Put all your sensitive information in gradle.properties and never push it to your version control system.

Don’t do this. This would appear in the version control system.

signingConfigs
 {
    release {
        // DON'T DO THIS!!
        storeFile file("myapp.keystore")
        storePassword "password123"
        keyAlias "thekey"
        keyPassword "password789"
    }
}

Instead, make a gradle.properties file :

KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789

That file is automatically imported by Gradle, so you can use it in build.gradle as such:

signingConfigs
{
    release
    {
        try
         {
            storeFile file("myapp.keystore")
            storePassword KEYSTORE_PASSWORD
            keyAlias "thekey"
            keyPassword KEY_PASSWORD
        }
        catch (ex) {
            throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
        }
    }
}

16) Implement SSL certificate pinning to prevent Man-in-the-middle Attack (MITM).

To intercept any request, we mostly use a proxy tool. The proxy tool installs its own certificate on the device and application trust that certificate as a valid certificate and allow proxy tool to intercept application traffic. This way we can help hackers to tamper or know our data stuff.
With SSL Pinning implementation, the application does not trust custom certificates and does not allow proxy tools to intercept the traffic.
It’s a method that depends on server certificate verification on the client-side. Read more here.

17) Use SafetyNet Attestation API to help determine whether your servers are interacting with your genuine app running on a genuine Android device.

The API verifies the following: 1) Whether the device is rooted or not.
2) Whether the device is monitored.
3) Whether the device has recognized hardware parameters.
4) Whether the software is Android compatible.
5) Whether the device is free form malicious apps.
But before implementing it, please check the Dos and Don’ts.

18) Use EncryptedSharedPreferences instead of SharedPreferences for storing sensitive information like your auth tokens etc.

19) Use the Android Keystore system to store and retrieve sensitive information from your storage like databases etc.

The Android keystore provides a secure system level credential storage. With the keystore, an app can create a new Private/Public key pair, and use this to encrypt application secrets before saving it in the private storage folders. While developing AarogyaSetu, I learnt about using Keystore for encrypting and decrypting highly sensitive information. You can check the implementation here.

Note: We have an Android Backup mechanism which is turned on by default. Android preserves app data by uploading it to the user’s Google Drive — where it’s protected by the user’s Google Account credentials and can be easily downloaded for the same credentials on other devices. But, When you have encrypted something with Android Keystore, you won’t be able to restore it because the cypher key is only for that specific device. We don’t have a sync mechanism for Keystore like iOS and their keychain. A better way to backup your users’ data is to store them on your backend.

20) Call Google Play services methods to ensure that your app is running on a device that has the latest updates to protect against known vulnerabilities found in the default security provider.

For example, a vulnerability was discovered in OpenSSL (CVE-2014–0224) that can leave apps open to a “man-in-the-middle” attack that decrypts secure traffic without either side knowing. With Google Play services version 5.0, a fix is available, but apps must ensure that this fix is installed. By using the Google Play services methods, your app can ensure that it’s running on a device that’s secured against that attack.
To protect against these vulnerabilities, Google Play services provides a way to automatically update a device’s security provider to protect against known exploits. Read more here.

21) Implement reCAPTCHA to ensure your app is not automated i.e handled by a robot.

reCAPTCHA is a free service that uses an advanced risk analysis engine to protect your app from spam and other abusive actions. If the service suspects that the user interacting with your app might be a bot instead of a human, it serves a CAPTCHA that a human must solve before your app can continue executing.

22) Write Unit tests for your feature.

Listing out some benefits:
1) Unit tests help to fix bugs early in the development cycle and save costs.
2) It helps the developers to understand the code base and enables them to make changes quickly
3) Good unit tests serve as project documentation.
4) You become more confident about the stuff you are writing.
There are many more benefits.:-)

23) Make security decisions on the server-side whenever possible.

Don’t trust your application’s client-side. Hacker can easily tamper or hack your application’s codebase and can manipulate your code. So, it’s better to have checks on the backend side whenever possible.

24) Learn how to use Proguard to the maximum for code Obfuscation and Optimization.

It is quite easy to reverse engineer Android applications, so if you want to prevent this from happening, you should use ProGuard for its main function: obfuscation, a process of creating source code in a form that is hard for a human to understand(changing the name of classes and members).
ProGuard has also two other important functions: shrinking which eliminates unused code and is obviously highly useful and also optimization.
Optimization operates with Java bytecode, though, and since Android runs on Dalvik bytecode which is converted from Java bytecode, some optimizations won’t work so well. So you should be careful there.

25) Use Network security configuration to improve your app’s network security.

Security is more about layers of protection than a single iron wall. The Android Network Security Configuration feature provides a simple layer to protect apps from unintentionally transmitting sensitive data in unencrypted cleartext.
If you don’t know what “unencrypted communications” means, think of it this way — let’s say your office has the policy to send all shipments via UPS. A new intern joins the office and is tasked with shipping equipment to an office across the country.
Oblivious to the policy and with all the best intentions, the intern sets up all shipments to be sent through an unknown, less expensive service.
The Android Network Security Configuration feature is like the shipping/receiving manager who examines all inbound and outbound shipments and stops the shipment before the equipment gets into the hands of an unvetted delivery system. It can be used to prevent the accidental use of untrusted, unencrypted connections.
Read more here.

The Critics principle

When you’re reviewing code of your teammates don’t be a friend, Be their arch enemy, don’t let them make mistakes that you might have to clean someday. Cleaning other’s shit will only make your hand dirty. Enforce good practices in code reviews.


Subscribe to theDroidLady Newsletter and get updates via Email