Petr NohejlTomas Mlynaric6 min

Android Coding Best Practices

Inside STRVEngineeringJan 8, 2020

Inside STRVEngineering

/

Jan 8, 2020

Petr NohejlAndroid Engineering Director
Tomas MlynaricAndroid Engineer

Share this article

Strict rules or chaos? The answer lies in finding the middle ground. And that's exactly what we did with STRV’s best.md — 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.

Each rule has a special # 6-chars identifier, allowing us to easily refer to it. We’ve worked hard on this list of dos & don’ts, and we’re happy to share it with you.

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 nor android.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 long if-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 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 with Versions, 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

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

That’s all from us. You can also find Android Coding Best Practices on our GitHub. We’d like to thank our colleague, Jakub Kinst, for his contribution to this article. And thank you for reading all the way to the end. Please let us know if you have any questions!

REACH OUT

Share this article