Try   HackMD

Android Code Style Guide

Before reading this checkout this sources:

Download & import: Kotlin Code Style for Android Studio

Code organization

Code organized by packages as stated in architecture guide.

Class layout

Class structure has following order:

  • initializer
  • companion object
  • fields declaration
  • methods declaration
  • nested classes declaration

Initializers

Arguments

If constructor arguments doesn't fit in one line or it has class scoped arguments each argument should be states on a new line.

// Good class A( private val name: String, private val title: String ) // Good class Derived(count: Int) : Base(count) // Bad class Adapter(private val data: List<String>) { ... }
JvmOverloads

If class extends from View class it will be good to use JvmOverloads annotation.

class CustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View

Companion object

For regular classes companion object should be at the top of class (exceptions model classes, dagger modules). Companion object follows the same structure rules as a regular classes.

class A { companion object { const val NAME = "name" fun newInstance(): A = A() } }

Fields declaration

In first place should be declared fields that will be injected.

class A { @Inject internal lateinit var fieldA: FieldA @Inject internal lateinit var fiedlB: FieldB private val b = B() }

Methods declaration

Order

Methods declaration ordering has following rules:

  • methods should follow order in superclass
  • lifecycle methods should follow call order
  • methods should be ordered by mention
// Good class A : Activity { override fun onStart() { super.onStart() attach() } override fun onStop() { detach() super.onStop() } private fun attach() { ... } private fun detach() { ... } } // Bad class B : Activity { private fun detach() { ... } override fun onStop() { detach() super.onStop() } private fun attach() { ... } override fun onStart() { super.onStart() attach() } }
Types

It's preferred for all methods to have explicitly specified type.

class A(c: C) { // Good fun getName(): String = resolveCalcs() // Good private fun resolveCalcs(): String = c.resolveCalcs() // Very bad fun getName() = resolveCalcs() // Bad private fun resolveCalcs() = c.resolveCalcs() }
Expression body

Expression body is preferred when it is possible to use it.

// Good fun getResponse(): Response = api.getResponse() // Bad fun getResponse(): Response { return api.getResponse() }

Nested classes

Nested classes should be at the end of parent class (view state is exception). inner keyword should be avoided if it possible.

class A { fun f() { ... } class B { ... } }

Nested classes shouldn't be referenced without parent.

class Video { class Url } // Good fun openVideoUrl(videoUrl: Video.Url) { ... } // Bad fun openVideoUrl(url: Url) { ... }

Model classes

Model classes follows the general rules for classes.

Serialization

If model class supposed to be used in network call it should have @SerializedName on each valuable field.

class Video( @SerializedName("id") val id: Long, @SerializedName("url") val url: String )

Also developer should keep in mind that Gson ingonres Kotlin default values.

Data classes

Data classes should be used only on demand. So if there is a class that is response wrapper and it won't be used or created manually there is no reason to use data class keyword.

// Good class Response( @SerializedName("name") val name: String? ) // Bad data class Response( @SerializedName("name") val name: String? )

Extensions

Global extensions should be placed in file with name: original class name + Extensions suffix on appropriate code layer. Also global extension should solve "global" problems.

// Good fun LongArray?.isNullOrEmpty(): Boolean = this == null || this.isEmpty() // Bad val String.USER_AGENT: String get() = "Android ${BuildConfig.APPLICATION_ID}" // Bad fun List<String>.getFullName(): String = this[0] + " " + this[1]

MVP

MVP pattern implemented on top of Android Architecture components that make it possible to preserve view state on configuration changes.

View contract

View state is usually represented by State object with addition one shoot methods. If some atom cannot be represented by state it should be expressed as one shoot action.

View State object is implemented with sealed class with required payload.

// Good interface CourseView { sealed class State { object NoConnection : State() object Loading : State() class CourseLoaded(val course: Course) : State() } fun setState(state: State) // one shoot method as it doesn't describe state fun showSectionScreen(section: Section) } // Bad interface CourseView { sealed class State { object NoConnection : State() object Loading : State() class CourseLoaded(val course: Course) : State() // doesn't describe state class ShowSectionScreen(val section: Section) : State() } }

Dependency injection

Dependency injection works on top of Dagger.

Modules

It is good to split modules by logic. Predefined modules names are:

  • FeatureDataModule for domain + data + remote + cache layers
  • FeatureRoutingModule for routing
  • FeatureModule for presentation + view layer

Inject annotation

We prefer @Inject annotation on constructor instead of manual constructor call in module.

// Good class InjecteeImpl @Inject constructor( private val injectee1: Injectee1 ) : Injectee abstract class FeatureModule { @Binds internal abstract fun bindInjectee( injectee: InjecteeImpl ): Injectee }
// Bad class InjecteeImpl( private val injectee1: Injectee1 ) : Injectee abstract class FeatureModule { @Provides internal fun provideInjectee(injectee1: Injectee1): Injectee = InjecteeImpl(injectee1) }

Modificators

We prefer private val modificator for constructor injected properties and internal lateinit var for fields. Also it is prefered to use internal in dagger modules and components.

// Good class InjecteeImpl @Inject constructor( private val injectee1: Injectee1 ) class Injected { @Inject internal lateinit var injectee1: Injectee1 } abstract class FeatureModule { @Binds internal abstract fun bindInjectee( injectee: InjecteeImpl ): Injectee }
// Bad class InjecteeImpl @Inject constructor( var injectee1: Injectee1 ) class Injected { @Inject var injectee1: Injectee1? = null } abstract class FeatureModule { @Binds abstract fun bindInjectee( injectee: InjecteeImpl ): Injectee }

Common practices

Guards

Prefer to use guards in methods instead of nested if. Guards should be on top of method.

sealed class State { object Loading : State() class Success(val payload: String?) : State() } // ok fun changeState(currentState: State?) { val payload = (currentState as? State.Success) ?.payload ?: return ... } // Bad fun changeState(currentState: State?) { if (currentState != null) { if (currentState is State.Success) { if (currentState.payload != null) { ... } } } } // Not ok fun changeState(currentState: State?) { if (currentState == null || currentState !is State.Success || currentState.payload == null ) { return } ... }

Horizontal alignment

Horizontal alignment is allowed only for named arguments.

// moderate val someNewValue = Value( id = 24, title = "Title", description = "Description", cover = "Cover", summary = "Summary", workload = "Workload", intro = "Intro", language = "Language" ) // bad val name : String = "name" val title : String = "title" val description: String = "description"

Constraint Layout

Do not use ConstraintLayout for every layout. It's better to use simpler layout if it possible.

Support & AndroidX

Support & AndroidX views is preferred over default ones.

Resources

Naming

Non layout files

Prefixes:

  • bg_ prefix for all background or foreground resources
  • ic_ prefix for all icons
  • color_ prefix for color resources

Suffixes:

  • _selector suffix for selectors
  • _ripple suffix for ripple variants resources (only for projects with minSdk < 21)
  • suffixes for states

Prefixes & suffixes can be combined.

Layout files

Prefixes:

  • activity_ for activity layout
  • fragment_ for fragment layout
  • layout_ for include layouts
  • item_ for recycler view items
  • view_ for custom view

Common

All resources names after prefixes described above should also contain feature name.

Examples:

  • activity_restaurants_map.xml
  • layout_restaurant_header.xml
<string name="restaurant_header_title">Restaurant</string>
<string name="restaurant_header_action_book">Book table</string>

Clickable views

All clickable views should have visual response on clicks. By default you can use ?selectableItemBackground or ?selectableItemBackgroundBorderless for square and round views. Or you should create your own background drawable with ripple effect for this view if it necessary.