# Android Code Style Guide Before reading this checkout this sources: - [Official Kotlin style guide](https://kotlinlang.org/docs/reference/coding-conventions.html) - [Android Architecture](https://hackmd.io/s/BJg82eA7N) - Clean Code - [Representing State](https://www.youtube.com/watch?v=-lVVfxsRjcY) **Download & import:** [Kotlin Code Style for Android Studio](https://drive.google.com/file/d/1E-Z-4LoM9ycjDMSlnNvzRmGpnEG1DzsC/view?usp=sharing) ## Code organization Code organized by packages as stated in [architecture guide](https://hackmd.io/s/BJg82eA7N). ### 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. ```kotlin= // 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. ```kotlin= 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. ```kotlin= 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. ```kotlin= 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 ```kotlin= // 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. ```kotlin= 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. ```kotlin= // 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. ```kotlin= class A { fun f() { ... } class B { ... } } ``` Nested classes shouldn't be referenced without parent. ```kotlin= 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. ```kotlin= 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. ```kotlin= // 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. ```kotlin= // 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. ```kotlin= // 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. ```kotlin= // Good class InjecteeImpl @Inject constructor( private val injectee1: Injectee1 ) : Injectee abstract class FeatureModule { @Binds internal abstract fun bindInjectee( injectee: InjecteeImpl ): Injectee } ``` ```kotlin= // 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. ```kotlin= // 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 } ``` ```kotlin= // 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. ```kotlin= 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. ```kotlin= // 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` ```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.