0% found this document useful (0 votes)
52 views23 pages

Kotlin Coding Convention

The Kotlin Coding Convention document outlines the structure and naming conventions for resources, files, classes, methods, and XML in Kotlin development. It emphasizes best practices for code readability, organization, and maintainability, including guidelines for variable naming, method length, and exception handling. Additionally, it provides specific examples of good and bad practices to follow when coding in Kotlin.

Uploaded by

Tony Nguyễn
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
52 views23 pages

Kotlin Coding Convention

The Kotlin Coding Convention document outlines the structure and naming conventions for resources, files, classes, methods, and XML in Kotlin development. It emphasizes best practices for code readability, organization, and maintainability, including guidelines for variable naming, method length, and exception handling. Additionally, it provides specific examples of good and bad practices to follow when coding in Kotlin.

Uploaded by

Tony Nguyễn
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

Kotlin Coding Convention

Structure
Resource structure

Name Path Description

XML This is where we put our XML layout


res/layout/
Layouts files.

This is where we put our AppBar menu


XML Menus res/menu/
actions.

This is where we put animation (API <


Anim res/anim/
11).

Animator res/animator/ This is where we put animator (API > 11).

Drawables res/drawable This is where we put XML drawables.

res/drawable-
Resource This is where we put images.
xhdpi|xxhdpi|xxxhdpi

This is where we put databases, raw


Asset res/assets
datas, etc

Colors res/values/colors.xml This is where we put color definitions.

Dimensions res/values/dimens.xml This is where we put dimension values.

String res/values/strings.xml This is where we put strings.

Styles res/values/styles.xml This is where we put style values.

File structure
A .kt file comprises of the following, in order:

Copyright and/or license header (optional)


Package statement
Import statements
Top-level declarations

Copyright and/or license header (optional)


If a copyright or license header belongs in the file it should be placed at the
immediate top in a multi-line comment.

GOOD*

/*
* Copyright 2017 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
* ...
*/

BAD*

/**
* Copyright 2017 Google, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
* http://www.apache.org/licenses/LICENSE-2.0
* ...
*/

BAD*

// Copyright 2017 Google, Inc.


//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.apache.org/licenses/LICENSE-2.0
// ...

Package statements
Package names are all lowercase, with consecutive words simply concatenated
together (no underscores).

// Okay
package com.example.deepspace
// WRONG!
package com.example.deepSpace
// WRONG!
package com.example.deep_space

Class
First letter of classes is UpperCase.
Name of class only accept in range [A-Z], [a-z] and follow Camel Case.
Classes name must be noun.
For classes that extend an Android component, the name of the class should end
with the name of the component; for example: SignInActivity, SignInFragment,
ImageUploaderService, ChangePasswordDialog.
Open brace ‘ { ‘ appears at the end of the same line as the declaration
statement.
Closing brace ‘ } ‘ starts a line by itself indented to match its corresponding
opening statement, except when it is a null statement the ‘ } ‘ should appear
immediately after the ‘ { ‘
Write KotlinDoc for each class, Kotlin doc must define what are classes working
for.
See Example

/**
* Copyright © Monstarlab Vietnam Co., Ltd.
* Created by mvn-...-dn on 16/04/2021.
* ...
*/
class SomeClass {
...
}

Class member ordering There is no single correct solution for this but using a
logical and consistent order will improve code learnability and readability. It
is recommendable to use the following order:
Constants
Fields
Constructors
Override methods and callbacks
Public methods
Internal methods
Private methods
Inner classes and interfaces

(TODO: inner class can be change position or some positions others)

Example

class MainActivity : Activity() {


var title: String
var tvTitle: TextView

override fun onCreate() {


...
}

internal fun setName(): String {


...
}

private fun setUpView() {


...
}
}

If your class is extending an Android components such as an Activity or a Fragment, it


is a good practice to order the override methods so that they match the component’s
lifecycle. For example, if you have an Activity that implements onCreate(),
onDestroy(), onPause() and onResume(), correct order as below:

class MainActivity : Activity() {

override fun onCreate() {}


override fun onResume() {}

override fun onPause() {}

override fun onDestroy() {}


}

Method
Short method content: Method content should be short and focus on the feature
of method. Avoid repeating code.
Method names not over 30 characters . Except for inputting text or URL.
Code lines not over 30 lines .
Method name must start with verb is first letter.
First letter of method name is LOWERCASE.
@SuppressWarnings : The @SuppressWarnings annotation should only be used under
circumstances where it is impossible to eliminate a warning. If a warning
passes this "impossible to eliminate" test, the @SuppressWarnings annotation
must be used, so as to ensure that all warnings reflect actual problems in the
code.

Example

Write document for methods if it’s necessary.

/**
* Calculate sum of two integers
*
* @param a number
* @param b number
* @return the sum of a and b
*/
fun calculateSum(a:Int, b:Int): Int {
...
}

Limit method block line less than 30 lines, limit method arguments less than 5.
Separate code if function has long line method.
If statement convention.

GOOD

if (...) {
...
}

BAD

if (...)
...

Use // TODO for dummy data or need to change later.

// TODO Remove after api finish


Catch exception.

GOOD

try {
...
} catch (et1: ExceptionType1) {
...
} catch (et2: ExceptionType2) {
...
}

finally {
...
}

BAD

try {
...
} catch (e: Exception) {
...
}

finally {
...
}

XML Conventions
View ID is followed by camel-case

Example

btnLogin, tvCaption

Prefix for id in XML

Name Prefix Name Prefix

Button btn Datepicker datePicker

EditText edt Timepicker timePicker

TextView tv Videoview videoView

Checkbox chk Seekbar seekBar

RadioButton rb RatingBar ratingBar

ToggleButton tb Processbar processBar

Spinner spn ImageView img

Menu menu ImageButton imgBtn

ListView lv RecyclerView recycleView


GalleryView gv CardView cardView

LinearLayout ll ScrollView scrollView

RelativeLayout rl ToolBar toolbar

Calendar calendar FrameLayout fr

XML File Name

Item for ListView, ReyclerView, GridView: item_* .

Custom for view: custom_* .

Custom for dialog: dialog_* .

Item of shape, selector, etc:

If item is a common using with least 2 item view : common_bg_* .


Otherwise: bg_ prefix _* .

Resource Name:

Icon:
Normal: ic_* .
State: ic_*_hover , ic_*_normal , ic_*_select .

Image:
Name: bg_* .
Folder: drawable-xhdpi, drawable-xxhdpi, drawable-xxxhdpi .

Example:

@+id/tvSubjectQuestions
@+id/imgTakeCamera

String resource name:

Name: ScreenName_Description .
Ex: home_show_dialog_ok .

Style resource:

Name: Using Camel but upper first character.


Ex: StyleForButton , StyleForListContact .
Dimension can hard code in style .

Color resource:

Hex color must using Lowercase letters.


When define alpha (transparent), must to show percent of alpha.
Name of color must folow Camel convention.

<color name="red">#f44336</color>
<color name="redLight">#30f44336</color>

Other naming

Model (example: Student, Teacher)


Activity: *Activity ( example: UserActivity)
Fragment: *Fragment (example: HomeFragment)
Adapter: *Adapter (example: PageAdapter)

Padding , Margin : accept to use hard code in XML file.

Text size must use dp instead sp .

Do not make a deep hierarchy of ViewGroups . here

Also keep dimens.xml DRY, define generic constants. here

Use multiple style files to avoid a single huge one. here

When an XML element doesn’t have any contents, you must use self closing tags.

GOOD

<TextView
android: id="@+id/tvProfile"
android: layout_width="wrap_content"
android: layout_height="wrap_content" />

BAD

<TextView
android: id="@+id/text_view_profile"
android: layout_width="wrap_content"
android: layout_height="wrap_content" >
</TextView>

Basic
Variables
Write in lowerCamelCase.

Single character value must be avoided, except for temporary looping variables.

GOOD

var studentName: String

BAD

var StudentName: String

Use non-null value as much as possible


Property priority
1. non-null & val
2. non-null & var
3. nullable & var

Use a lateinit or Delegates.notNull() if you cannot set a initial value.


lateinit is better than Delegates.notNull() because Delegates.notNull() uses
reflection. But primitive values is not applied to lateinit.
// Non-null & val
private val hoge: Hoge = Hoge()
private val drawablePadding: Int by lazy {
activity.resources.getDimensionPixelSize(R.dimen.drawable_padding) }

// Non-null & var


private lateinit var hoge: Hoge
private var hoge: Hoge = Delegates.notNull()

// Nullable & var


private var hoge: Hoge? = null

Type Inference
Type inference should be preferred where possible to explicitly decleard types.

GOOD

val student = Student()


val age = 21

BAD

val student: Student = Student()


val age: Int = 21

Underscores in numeric literals


You can use underscores to make number constants more readable:

With Decimals : Should use the underscore to group the 3 elements together.

val oneMillion = 1_000_000

With Hexadecimals : Should use the underscore to group the 2 elements together
after 0x

val hexBytes = 0xFF_EC_DE_5E

With Binaries : Should use the underscore to group the 8 elements together
after 0b .

val bytes = 0b11010010_01101001_10010100_10010010

Note: With special case, self-defined programer. Example:

val creditCardNumber = 1234_5678_9012_3456L


val socialSecurityNumber = 999_99_9999L

Strings
Always prefer string interpolation if possible.

GOOD
val name = "${user.firstName} ${user.lastName}"

BAD

val name = user.firstName + " " + user.lastName

!!
Do not use !!, it will erase the benefits of Kotlin.
You use it only when you want to explicitly raise a Null Pointer Exception.

If-else expression
Do not start a new line in variable declaration using if-else.

GOOD

val foo = 5
val bar = if(foo > 10) "kotlin" else "java"

BAD

val foo = 5
val bar = if (foo > 10) {
"kotlin"
} else {
"java"
}

When Expression
Use when in case there are two or more branches of if-else.

GOOD

val hoge = 10
when {
hoge > 10 -> print("10")
hoge > 5 -> print("5")
else -> print("different")
}

BAD

val hoge = 10
if (hoge > 10) {
print("10")
} else if (hoge > 5) {
print("5")
} else {
print("different")
}

Unlike switch statements in Java, when can be used either as an expression or


as a statement. Separate cases using commas if they should be handled the same
way. Always include the else case.
GOOD

when (Input) {
1, 2 -> doSomethingForCaseOneOrTwo()
3 -> doSomethingForCaseThree()
else -> print("No case satisfied")
}

BAD

when (Input) {
1 -> doSomeThingForCaseOne()
2 -> doSomeThingForCaseOneOrTwo()
3 -> doSomeThingForCaseThree()
}

Smart Cast
GOOD

dog as? Animal ?: throw IllegalArgumentException("not Animal!")


dog.foo()

BAD

if (dog !is Animal) {


throw IllegalArgumentException("not Animal!")
}
dog.foo()

dog as Animal
dog.foo()

Collections
Should be used MutableList if that List change data in future.

val students: MutableList<Int> = ArrayList()

If you create the list to read only, you can use List instead of using
MutableList .

val students: List<Int> = ArrayList()

When get an item in a list.

GOOD

val array = ArrayList<Int>()


array[0]

BAD
val array = ArrayList<Int>()
array.get(0)

Use an IDE suggestion


When call the activity

GOOD

activity.finish()

BAD

getActivity().finish()

Equality
Should be used equals instead of == operator.

GOOD

a.equals(b)
!a.equals(b)

BAD

a == b
a != b

This Expression
Don't use this@label if compiler understood that this .

GOOD

data class Test(var name: String) {


fun showName(name: String) {
this.name = name
}
}

BAD

data class Test(var name: String) {


fun showName(name: String) {
[email protected] = name
}
}

Break line
Start a new line at right vertical line on Android studio. (About 100 characters).
Any line that would exceed this limit must be line-wrapped

When a line is broken at an assignment operator( =, +=, -=, *=, /=, %=) the
break comes after the symbol.
fun compare(a: String, b: String): Boolean =
...

The break comes before the symbol . and :: and non-assignment operator

addMarker(MarkerOptions()
.position(currentLocation)
.draggable(true)
.title(resources.getString(R.string.current_location))

fun getSomething(
a: String,
b: String,
c: String,
d: String,
::isSomething
){
...
}

A method or constructor name stays attached to the open parenthesis ( ( )


that follows it and a comma (,) stays attached to the token that precedes it.

fun <T> Iterable<T>.joinToString(


separator: CharSequence = "t",
prefix: CharSequence = "",
postfix: CharSequence = ""
): String {
...
}

A lambda arrow ( -> ) stays attached to the argument list what precedes it.

val printSummary = { username: String, age: String, score: Int ->


println("User '$username' with '$age' get $score points.")
}

Annotation
Member or type annotations are placed on separate lines immediately prior to
the annotated construct.

@Retention(SOURCE)
@Target(FUNCTION, PROPERTY_SETTER, FIELD)
annotation class Global

Annotations without arguments can be placed on a single line.

@JsonExclude @JvmField
var x: String
When only a single annotation without arguments is present it may be placed on
the same line as the declaration.

@Test fun selectAll() {


...
}

Loop (optional)
You do not have to write for loop because there is forEach in collections
package of Kotlin.

GOOD

(0..9).forEach { ... }

// If you want to know index


(0..9).forEachIndexed { index, value -> ... }

BAD

for (i in 0..9) { ... }

Use range
GOOD

val char = 'K'


if (char in 'A'..'Z') print("Hit!")

BAD

val char = 'K'


if (char >= 'A' && 'c' <= 'Z') print("Hit!")

OOP
Visibility Modifiers
Kotlin have 4 visibility modifiers: public, internal, protected, private. But
when we using we must define visibility modifier that have the smallest range
of use.
If visibility modifiers is public we can ignore public keyword:

GOOD

val name = "This is name"

BAD

public val name = "This is name"

Coroutine Scope
GlobalScope.launch and GlobalScope.async are highly discouraged by the Kotlin
documentation.
Global scope is used to launch top-level coroutines which are operating on the
whole application lifetime and are not cancelled prematurely.
Application code usually should use an application-defined CoroutineScope.
Using async or launch on the instance of GlobalScope is highly discouraged.

GOOD

val scope = CoroutineScope(Dispatchers.IO)


fun foo() {
scope.launch {
delay(1_000L)
}
}

fun onDestroy() {
scope.cancel()
}

BAD

fun foo() {
GlobalScope.launch {
delay(1_000L)
}
}

Class
Constructor: Initialize class properties as primary constructor parameters
instead of in an init block.

GOOD

class Person (val firstName: String, val lastName) {


...
}

BAD

class Person (firstName: String, lastName: String) {


val firstName: String
val lastName: String

init {
this.firstName = firstName
this.lastName = lastName
}
}

Function
Functions that return Flow from kotlinx.coroutines.flow should not be marked
as suspend . Flows are intended to be cold observable streams. The act of
simply invoking a function that returns a Flow , should not have any side
effects. Only once collection begins against the returned Flow , should work
actually be done.

GOOD

fun observeSignals(): Flow<Unit> {


return flow {
val pollingInterval = getPollingInterval() // Moved into the flow builder
block.
while (true) {
delay(pollingInterval)
emit(Unit)
}
}
}

private suspend fun getPollingInterval(): Long {


// Return the polling interval from some repository
// in a suspending manner.
}

BAD

suspend fun observeSignals(): Flow<Unit> {


val pollingInterval = getPollingInterval() // Done outside of the flow builder
block.
return flow {
while (true) {
delay(pollingInterval)
emit(Unit)
}
}
}

private suspend fun getPollingInterval(): Long {


// Return the polling interval from some repository
// in a suspending manner.
}

If a function returns Unit, the return type should be ignored.

GOOD

fun showText (text: String) {


println(text)
}

BAD

fun showText (text: String): Unit {


println(text)
}
If the function only executes an expression, the expression should be placed on
the declaration line.

GOOD

fun sum (a: Int, b: Int): Int = a + b

BAD

fun sum (a: Int, b: Int): Int {


return a + b
}

If the function has an empty body, use the Unit type instead of empty bracket
body.

GOOD

fun foo() = Unit

BAD

fun foo() {
...
}

Should use scope function.

GOOD

class Hoge {
fun fun1() {}
fun fun2() {}
}
val hoge = Hoge().apply {
fun1()
fun2()
}

BAD

class Hoge {
fun fun1() {}
fun fun2() {}
}
val hoge = Hoge()
hoge.fun1()
hoge.fun2()

suspend modifier should only be used where needed, otherwise the function can
only be used from other suspending functions. This needlessly restricts use of
the function and should be avoided by removing the suspend modifier where it’s
not needed.

GOOD
fun normalFunction() {
println("string")
}

BAD

suspend fun normalFunction() {


println("string")
}

Data class
Only using data keyword when class need equals() , hashCode() , toString() , copy() ,
componentN() function.

Enum classes
An enum with no functions and no documentation on its constants may optionally be
formatted as a single line.

enum class Answer { YES, NO, MAYBE }

When the constants in an enum are placed on separate lines, a blank line is not
required between them except in the case where they define a body.

enum class Answer {


YES,
NO,

MAYBE {
override fun toString() = """¯\_( )_/¯"""
}
}

Generics
When define an object of a generic class we don't need define type of that object.

class Food<T>(kind: T) {
var value = kind
}

GOOD

val fish = Food("Fish")

BAD

val fish: Food<String> = Food<String>("Fish")

Companion Object
It should be placed at the beginning of its wrapper class.
Name of constants is upper case and specified with keywords const val.
GOOD

class MyFragment: Fragment() {


companion object {
const val TYPE_VIEW_HEADER = 0
const val TYPE_VIEW_FOOTER = 1
}
}

BAD

class MyFragment: Fragment() {


companion object {
val TypeViewHeader = 0
val TypeViewFooter = 1
}
}

When you want to use components in companion object, you should call directly.

GOOD

class MyClass {
companion object {
const val NUMBER = 10
}
}

val x: Int = MyClass.NUMBER

BAD

class MyClass {
companion object {
const val NUMBER = 10
}
}

val x: Int = MyClass.Companion.NUMBER

Extension function
Extension function is used when actually needed.
Do not define extension function of a class in this class.

GOOD

class Person(val name: String) {


...
}

fun Person.setName(name: String) {


...
}
BAD

class Person(val name: String) {


...
fun Person.setName(name: String) {
...
}
}

Extension property should not use this keyword to access to properties of the
object applied.

GOOD

val <T> List<T>.lastIndex: Int


get() = size - 1

BAD

val <T> List<T>.lastIndex: Int


get() = this.size - 1

Do not create extension functions in same class.

GOOD

class Animal {
fun Person.foo() {
}
}

BAD

class Animal {
fun Animal.foo() {
}
}

When adding extensions to external classes, create a extension package and make
separate files for each type:
StringExtensions.kt
BitmapExtensions.kt
...

Destructuring Declarations
Parameters should directly destructor without componentN().

GOOD

data class Person(var name: String, var age: Int) {


...
}

val person = Person("Nguyen Van A", 22)


val (name, age) = person
println("Name:$name,Age:$age")

BAD

data class Person(var name: String, var age: Int) {


...
}

val person = Person("Nguyen Van A", 22)


val name = person.component1()
val age = person.component2()
println("Name:$name,Age:$age")

High-Order Function
Parameters in high-order function are placed in order: normal parameters,
functions.

GOOD

fun <T> lock( lock: Lock, body: () -> T) {


...
}

BAD

fun <T> lock(body: () -> T, lock: Lock) {


...
}

Lambda
Should ignore (), ->, name parameter and use it keyword to declare the
parameter explicitly if the function has only one parameter as lambda block.

GOOD

fun onClick(position: (Int) -> Unit) {


...
}

onClick { println(it) }

BAD

fun onClick(position: (Int) -> Unit) {


...
}

onClick({ position -> println(position) })

Should place lambda block out of the round bracket of the function if a
function has more than one parameter.
GOOD

fun onClick(x: String, data: (Int, String) -> Unit) {


...
}
onClick("abc") { position, content ->
println("Position: $position, Content: $content")
}

BAD

fun onClick(x: String, data: (Int, String) -> Unit) {


...
}

onClick("abc", { position, content ->


println("Position: $position, Content: $content")
})

Inline keyword
An inline function is used for small functions(1 -> 5 lines of code), not used
for big functions.
The inline modifier can be used on accessors of properties when we do not want
to create a backing field.

GOOD

inline var bar: Bar


get() = ...
set(v) {...}

BAD

var bar: Bar


get() = ...
set(v) {...}

Which use run or let


Use let to substitute into functions.
Use run to use outside functions.

class Foo
class Bar(val foo: Foo)
class Hoge {
fun fun1() {}
fun fun2(bar: Bar) {}
fun fun3(foo: Foo) {}
}

GOOD
Hoge().run {
fun1()
Foo().let {
fun2(Bar(it))
fun3(it)
}
}

BAD

Hoge().let {
it.fun1()
Foo().run {
it.fun2(Bar(this))
it.fun3(this)
}
}

Environments
Android Studio. download
Setup Kotlin plugin. link

Security Conventions
Remember cancel Thread, AsyncTask,… If go out other screen or turn back home
screen.
Remember enable minifyEnabled function in build.gradle while on release mode
and config proguard carefully.

buildTypes {
release {
debuggable false
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt' ), 'proguard-
rules.pro'
applicationVariants. all { variant ->
appendVersionNameVersionCode(variant)
}
}
}

If you guys create keystore by yourself, let talk to PM or TL about that, and
having backup that keystore carefully and remember information of keystore
(keyAlias, keyPassword).

Don’t push keystore file and information of keystore to GitHub, keep it in


local as in local.properties file.

More practice:

Proguard configure

Gradle configure
local.properties file*

sdk. dir=/Users/Administrator/Library/Android/sdk
keyAlias=sampletext1
keyPassword= sampletext2

Verify input validation carefully.


Verify Runtime Permission function available in Android 6.0 and above
carefully. More details
Avoid leak memory. More details
Avoid out of memory. More details
Avoid run time exception

Code management practices and dependency management


practices
Introduce Gradle Tool

References
Android practices Github
KotlinLang Page
Android Developer Page
Kotlin Code Conventions Page

Code review Checklist


Pass Circle CI or Travis CI with checkstyle findbug lint tools: 70% follow
convention.
Pass Reviewer: 30% follow convention.
Github

More References
Android Boilerplate
Android folder structure

You might also like