Android Coding Best Practices
This is a set of 'nice to follow' guidelines useful when working in larger teams. Helps to keep the code nice, clean and uniform. It also gives new people joining the project a flat learning curve; once they're familiar with the principles, it's much easier to get acquainted with the codebase.
Strive to follow these rules when working on STRV projects. Each rule has a special # 6-chars identifier, allowing us to easily refer to it.
Code
- Follow OOP principles, SOLID principles, DRY and KISS. #PRNCPL
- Avoid God classes. A class with hundreds of lines is probably doing too much. Each class should have only one responsibility. #GODCLS
- Avoid complicated functions. Every function should be doing only one thing. If possible, don't mix side effects with computation logic inside one function. #GODFUN
- Be consistent in naming classes, methods, properties, resources, etc. #CONSIS
- Use meaningful names for variables. For example, "e" is a bad name. #MNGFUL
- Don't add new classes, methods, properties, resources, etc. automatically at the end of a file. Consider the proper position in the file where the element should be added. Order lifecycle methods chronologically. #POSITN
- Don't comment out unused code, just delete it. #UNUSED
- Be careful with initializing multiple libraries in
Application.onCreate()
. Do this stuff off the main thread whenever possible to speed up the start of the app. #LIBINI - Don't show any logs in production build. #LOGPRD
- Enhance crash reporting with custom logs from your app. Make sure to filter out any sensitive data. #LOGCRA
- Use factory pattern for creating a new Intent or for creating a new instance of a Fragment. #FACTRY
- Use Parcelable rather than Serializable. #PARCEL
- Use RecyclerView rather than ListView. #RECYCL
- Use stable ids in RecyclerView adapter whenever possible. You will achieve better performance because ViewHolders can be reused after
notifyDataSetChanged()
and you will get animations. #STABID - Don't call
notifyDatasetChanged()
on adapters directly. Use DiffUtil for optimized calculations of adapter data. #DIFFUT - Consider using ArrayMap/ArraySet instead of HashMap/HashSet. It's designed to be more memory efficient. #ARRMAP
- Consider using the Data Binding library from Android Jetpack. #DATBND
- Don't use
findViewById()
whenever possible. Use the Data Binding library, Kotlin Android Extensions or auto generated View Binding. #BNDFND - Don't use complicated expressions in data binding. Views should be as dumb as possible. ViewModel is responsible for display logic. #BNDEXP
- Leverage the power of custom BindingAdapters. #BNDADP
- Don't use
String.format()
in data binding. Use@string/example_text(data.foo, data.bar)
instead. #BNDSTR
Architecture
- Use a dependency injection framework to make code reusable, easily maintainable and testable. #DEPINJ
- Use MVVM or MVI architecture. UI logic should be implemented in ViewModel. Activity/Fragment serves as a View. #MVVMAR
- The View layer should keep neither data nor state. These should be stored in some persistent object, such as ViewModel or Redux Store. #VIEWDT
- ViewModel should contain only presentation logic. Business logic should be implemented in other classes (e.g. repository pattern). #VMLOGI
- ViewModel must not access Activity Context. #VMCNTX
- ViewModel must not access
android.view
norandroid.widget
classes. That is View's responsibility. #VMWIDG - Always check whether the app handles saving the persistent state. When the system kills the app, ViewModel doesn't save the state. Use plain
onSaveInstanceState()
or Saved State module for ViewModel. #SAVSTA - Consider using LiveData for holding the current state of a screen. #LIVDAT
- Strive to have a unidirectional flow of data with observer patterns. Don't imperatively set stuff if observing is possible. Leverage MediatorLiveData or similar transformations. #UNIDIR
- Don't use retained Fragments. #RETFRG
- Don't use Loaders. #LOADER
- Don't use long-standing Service if it is not absolutely necessary. Better to use JobScheduler or WorkManager. #SERVIC
- Consider using Android Navigation Component. #NAVIGA
Kotlin
- Follow Android Kotlin Style Guide. #KTSTYL
- Use Android KTX extensions. #KTXAND
- Consider using Kotlin Coroutines. #KTCORO
- Don't write unreadable monster expressions. Obsessing over getting by with a single expression and over utilizing smart casts can lead to pretty unreadable code. #KTEXPR
- Don't use non-null assertion
!!
if it's not absolutely necessary. #KTNULL - Don't use nullable types for non-null variables with delayed initialization. Use
lateinit
for this. #KTLATE - Use
when
expression instead of longif-else-if
chain. #KTWHEN - Use data classes for entity objects. #KTDATA
- Use Kotlin scope functions from the standard library (
let
,run
,also
,apply
,with
) to structure the code in a more idiomatic way. #KTSCOP - Extension functions are not a replacement for all utility functions. Extension functions are good for extending existing abstractions. Don't abuse them if an extension semantically doesn't make sense. For example,
Int.px()
is a semantically wrong extension. #KTEXTF - Consider using inline classes instead of plain primitives (e.g. currency, time). #KTINLN
Resources
- Don't hardcode resource values with the exception of 8dp grid dimen values (
8dp
,16dp
,24dp
,32dp
,48dp
, etc.). Put resource values in separate XML files. #HCDRES - Be careful with an overly deep hierarchy of layouts and views. Leverage the power of ConstraintLayout. #DEEPLA
- Watch out for overdraws. #OVRDRW
- Use vector drawables whenever possible. Ask your designer to provide you with SVG files. #VCTDRW
- Use adaptive launcher icons. #ADPICO
- Use PNG only for graphics (not vectors) and JPEG for photos to optimize APK size. Consider using WebP image format and an image compression tool. #IMGRES
- Distinguish between themes and styles. Put them in separate XML files. #THMSTY
- Use
ThemeOverlay
theme descendants to apply local changes to themes. #THMOVR - Use
android:textAppearance
for text style. #TXTAPR - Use predefined text sizes and try to avoid using custom values. Use directly or extend Material text appearance styles from
TextAppearance.AppCompat
. #TXTSTY - Use XML attributes in the
tools
namespace (tools:src
,tools:listitem
,tools:visibility
, etc.) that enable useful design-time features. #TOOLAT - Use
@tools:sample/*
resources to inject placeholder data or images into views. Avoid using custom sample resources which increase APK size. #SAMPLE
Project
- Follow Semantic Versioning for app versioning. #SEMVER
- Don't use dynamic versions for dependencies, such as "1.0.+". #DYNVER
- Target SDK should be set to the maximum API level. #TGTSDK
- Separate the project into multiple Gradle modules by layer and feature. Modules can be built in parallel or isolated, which reduces build time. #GRDMOD
- Keep the
build.gradle
file brief and don't overfill it with config that is not relevant to this specific module or app. You can extract some common tasks or settings to a separate file. #GRDBRF - Consider using
buildSrc
folder withVersions
,Dependencies
and custom tasks or plugins for the whole project. #GRDSRC - Use a config class to store all configuration values (API URLs, API keys, log settings, etc.) in one place. Avoid defining config values in multiple places, like
build.gradle
,AndroidManifest.xml
, config class, etc. #CONFIG - Use a continuous integration service to build your project, test your code and deploy APK file to the Play Store. #CONINT
Git
- Follow STRV Git Guidelines. #GITGDL
- Follow How to Write a Git Commit Message. #GITMSG
- Capitalize the subject line in commit message. #GITCAP
- Use the imperative mood in the subject line of a commit message. #GITIMP
- Consider using Git Flow or a similar branching system. #GITFLO
UI & UX
- Follow Material Design Guidelines. #MATERL
- The app should support both landscape and portrait mode, even in a situation where the portrait mode is forced in production build. #LANDSC
- The app should support split screen mode. #SPLTSC
- Handle all possible edge cases and states: content, progress, offline, empty, etc. #STATES
- Show progress indicator when something is loading. #PRGIND
- Don't use progress dialogs. #PRGDLG
- Clickable views should have a ripple touch feedback. #RIPPLE
- Clickable views should have at least
48dp
width/height. #CLCSIZ - Dialogs should not disappear after an orientation change or Activity restore. #ORIDLG
- ScrollView or RecyclerView should keep the scroll position after an orientation change, Activity restore or popping the backstack. #ORISCR
- Selectable views should keep the select state after an orientation change or Activity restore. #ORISEL
- If it's already loaded, don't load data again after an orientation change or Activity restore. #ORILOA
- Strive to make the app offline first by using a caching method (e.g. Room, Firestore, Realm). #OFFLIN
- Always test the app on various screen sizes, densities and Android versions. #TSTAPP
- Always test if the app works properly when the system kills it on inactivity. You can use ADB Idea plugin, "Don't keep activities" in developer options or ADB script. #TSTKIL