Avoid reinventing the wheel by following these guidelines.
Note (October 2026): this guide is a fork of a 2017-era guide and predates current Android practice. The principles hold (build from Gradle files, no secrets in git, keep layouts flat, test release builds early). Several specifics have moved on:
- Kotlin is Google's preferred language, and Jetpack Compose is the recommended UI toolkit for new screens. The Java-only, XML-layout advice below still applies to existing View-based code.
- R8 replaced ProGuard as the default shrinker in Android Gradle Plugin 3.4. R8 reads the same proguard-rules.pro files, so the keep-rule advice still works.
- For new storage code, Room (SQLite) and DataStore (key-value) are the usual choices over raw ContentProviders and SharedPreferences.
- Retrolambda is not needed, and Robolectric now has first-class support in AndroidX Test, so "avoid Robolectric" is no longer a blanket rule. AssertJ-Android is no longer maintained.
- Coroutines and Flow have taken over much of what RxJava was used for.
- Check the current Android documentation before choosing library versions or a minSdkVersion.
This series has 3 parts. Each one reads on its own.
- Build setup and libraries (this page)
- App structure and resources
- Testing, ProGuard, storage and tooling
1Summary
- Use gradle and its default project structure
- Put passwords and sensitive data in gradle.properties
- Use the Jackson library to parse JSON data
- Don't write your own HTTP client, use OkHttp libraries
- Avoid guava and use only a few libraries due to the 65k method limit
- Sail carefully when choosing between activities and fragments
- Layout XMLs are code, organize them well
- Use styles to avoid duplicate attributes in layout XMLs
- Use multiple style files to avoid a single huge one
- Keep your colors.xml short and DRY, just define the palette
- Also keep dimens.xml DRY, define generic constants
- Do not make a deep hierarchy of viewgroups
- Avoid client-side processing for webviews, and beware of leaks
- Use JUnit for unit tests, Espresso for connected (UI) tests, and AssertJ-Android for easier assertions in your android tests
- Always use proguard or dexguard
- Use sharedpreferences for simple persistence, otherwise contentproviders
- Use stetho to debug your application
- Use leak canary to find memory leaks
- Use continuous integration
1.1Android SDK
Place your Android SDK somewhere in your home directory or some other application-independent location. Some distributions of IDEs include the SDK when installed, and may place it under the same directory as the IDE. This can be bad when you need to upgrade (or reinstall) the IDE, as you may lose your SDK installation, forcing a long and tedious redownload.
Also avoid putting the SDK in a system-level directory that might need root permissions, to avoid permissions issues.
1.2Build system
Your default option should be Gradle using the Android Gradle plugin.
It is important that your application's build process is defined by your Gradle files, rather than being reliant on IDE specific configurations. This allows for consistent builds between tools and better support for continuous integration systems.
1.3Project structure
Although Gradle offers a large degree of flexibility in your project structure, unless you have a compelling reason to do otherwise, you should accept its default structure as this simplify your build scripts.
1.4Gradle configuration
General structure. Follow Google's guide on Gradle for Android.
minSdkVersion: 21 We recommend to have a look at the Android version usage chart before defining the minimum API required. Remember that the statistics given are global statistics and may differ when targeting a specific regional/demographic market. It is worth mentioning that some material design features are only available on Android 5.0 (API level 21) and above. And also, from API 21, the multidex support library is not needed anymore.
Small tasks. Instead of (shell, Python, Perl, etc) scripts, you can make tasks in Gradle. Just follow Gradle's documentation for more details. Google also provides some helpful Gradle recipes, specific to Android.
Passwords. In your app's build.gradle you will need to define the signingConfigs for the release build. Here is what you should avoid:
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 which should not be added to the version control system:
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.")
}
}
}
Prefer Maven dependency resolution to importing jar files. If you explicitly include jar files in your project, they will be a specific frozen version, such as 2.1.1. Downloading jars and handling updates is cumbersome and is a problem that Maven already solves properly. Where possible, you should attempt to use Maven to resolve your dependencies, for example:
dependencies {
implementation 'com.squareup.okhttp:okhttp3:3.8.0'
}
Avoid Maven dynamic dependency resolution Avoid the use of dynamic dependency versions, such as 2.1.+ as this may result in different and unstable builds or subtle, untracked differences in behavior between builds. The use of static versions such as 2.1.1 helps create a more stable, predictable and repeatable development environment.
Use different package name for non-release builds Use applicationIdSuffix for debug build type to be able to install both debug and release apk on the same device (do this also for custom build types, if you need any). This will be especially valuable after your app has been published.
android {
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
}
release {
// ...
}
}
}
Use different icons to distinguish the builds installed on a device, for example with different colors or an overlaid "debug" label. Gradle makes this very easy: with default project structure, simply put debug icon in app/src/debug/res and release icon in app/src/release/res. You could also change app name per build type, as well as versionName (as in the above example).
Share debug app keystore file Sharing the debug APK keystore file via the app repository saves time when testing on shared devices and avoids the uninstalling/reinstalling of the app. It also simplifies the processing of working with some Android SDKs, such as Facebook, which require the registration of a single key store hash. Unlike the release key file, the debug key file can safely be added to your repository.
Share code style formatting defintions Sharing the code style and formatting definitions via the app repository helps ensure a visually consistent code base and makes code comprehension and reviews easier.
1.5Android Studio as your main IDE
The recommended IDE for Android development is Android Studio because it is developed and constantly updated by Google, has good support for Gradle, contains a range of useful monitoring and analysis tools and is fully tailored for Android development.
Avoid adding Android Studio's specific configuration files, such as .iml files to the version control system as these often contain configurations specific of your local machine, which won't work for your colleagues.
1.6Libraries
Jackson is a Java library for JSON serialization and deserialization, it has a wide-scoped and versatile API, supporting various ways of processing JSON: streaming, in-memory tree model, and traditional JSON-POJO data binding.
Gson is another popular choice and being a smaller library than Jackson, you might prefer it to avoid 65k methods limitation. Also, if you are using
Moshi, another of Square's open source libraries, builds upon learnings from the development of Gson and also integrates well with Kotlin.
Networking, caching, and images. There are a couple of battle-proven solutions for performing requests to backend servers, which you should use rather than implementing your own client. We recommend basing your stack around OkHttp for efficient HTTP requests and using Retrofit to provide a typesafe layer. If you choose Retrofit, consider Picasso for loading and caching images.
Retrofit, Picasso and OkHttp are created by the same company, so they complement each other nicely and compatability issues are uncommon.
Glide is another option for loading and caching images. It has support for animated GIFs, circular images and claims of better performance than Picasso, but also a bigger method count.
RxJava is a library for Reactive Programming, in other words, handling asynchronous events. It is a powerful paradigm, but it also has a steep learning curve. We recommend taking some caution before using this library to architect the entire application. For a reference app, our open source app Freesound Android makes extensive use of RxJava 2.
If you have no previous experience with Rx, start by applying it only for responses from app's backend APIs. Alternatively, start by applying it for simple UI event handling, like click events or typing events on a search field. If you are confident in your Rx skills and want to apply it to the whole architecture, then write documentation on all the tricky parts. Keep in mind that another programmer unfamiliar to RxJava might have a very hard time maintaining the project. Do your best to help them understand your code and also Rx.
Use RxAndroid for Android threading support and RxBinding to easily create Observables from existing Android components.
Retrolambda is a Java library for using Lambda expression syntax in Android and other pre-JDK8 platforms. It helps keep your code tight and readable especially if you use a functional style, such as in RxJava.
Android Studio offers code assist support for Java 8 lambdas. If you are new to lambdas, just use the following to get started:
- Any interface with just one method is "lambda friendly" and can be folded into the more tight syntax
- If in doubt about parameters and such, write a normal anonymous inner class and then let Android Studio fold it into a lambda for you.
Note that from Android Studio 3.0, Retrolambda is no longer required.
Beware of the dex method limitation, and avoid using many libraries. Android apps, when packaged as a dex file, have a hard limitation of 65536 referenced methods 1(https://medium.com/@rotxed/dex-skys-the-limit-no-65k-methods-is-28e6cb40cf71) 2(http://blog.persistent.info/2014/05/per-package-method-counts-for-androids.html) 3(http://jakewharton.com/play-services-is-a-monolith/). You will see a fatal error on compilation if you pass the limit. For that reason, use a minimal amount of libraries, and use the dex-method-counts tool to determine which set of libraries can be used in order to stay under the limit. Especially avoid using the Guava library, since it contains over 13k methods.