A set of rules base on current context of the ViettelPay App development
- Nomenclature
- Declarations
- Spacing
- Coding Rules
- Semicolons
- Getters & Setters
- Brace Style
- When Statements
- Use an IDE suggestion
- Use a smart cast
it
in Lambda expression applies to wide scope- Should use a type interface
- Don't write for loop
- Use
to
expression creatingPair
class - Use
when
in case there are two or more branches ofif-else
- Use
is
in case of comparing a class type - Use
range
in case comparing Int values - Don't start a new line in variable declaration using
if-else
- Code guidelines
- Scope function
- Function
- Types
- XML Guidance
- Language
On the whole, naming should follow Java standards, as Kotlin is a JVM-compatible language.
Package names are similar to Java: all lower-case, multiple words concatenated together, without hypens or underscores:
BAD:
vn.ViettelPay.network
GOOD:
vn.viettelpay.network
Written in UpperCamelCase. For example BaseCallAdapter
.
Written in lowerCamelCase. For example setValue
.
Generally, written in lowerCamelCase. Fields should not be named with Hungarian notation, as Hungarian notation is erroneously thought to be recommended by Google.
Example field names:
class MyClass {
var publicField: Int = 0
val person = Person()
private var privateField: Int?
}
Constant values in the companion object should be written in uppercase, with an underscore separating words:
companion object {
const val THE_ANSWER = 42
}
Written in lowerCamelCase.
Single character values must be avoided, except for temporary looping variables.
Abbreviations in variable names are strongly discouraged. Acronyms may be used if they are standard nomenclature (commonly used in place of their longhand counterparts).
BAD:
val bookTtl = "Programming 101"
val ipAddr = "127.0.0.1"
GOOD:
val bookTitle = "Programming 101"
val ipAddress = "127.0.0.1"
Boolean Feature Flags configured remotely should be named such that true coresponds to the feature being enabled and false is disabled.
BAD:
val featureAbc = ExampleRemoteBoolean("feature_abc_disabled", true) // WRONG! true would result in feature being enabled
GOOD:
val featureAbc = ExampleRemoteBoolean("feature_abc", true) // Okay, defaults to enabled
val featureAbc = ExampleRemoteBoolean("feature_abc", false) // Okay, defaults to disabled
In code, acronyms should be treated as words. For example:
BAD:
XMLHTTPRequest
URL: String?
findPostByID
GOOD:
XmlHttpRequest
url: String
findPostById
Only include visibility modifiers if you need something other than the default of public.
BAD:
public val wideOpenProperty = 1
private val myOwnPrivateProperty = "private"
GOOD:
val wideOpenProperty = 1
private val myOwnPrivateProperty = "private"
Access level modifiers should be explicitly defined for classes, methods and member variables.
Prefer single declaration per line.
GOOD:
username: String
twitterHandle: String
Exactly one class per source file, although inner classes are encouraged where scoping appropriate.
Prefer data classes for simple data holding objects.
BAD:
class Person(val name: String) {
override fun toString() : String {
return "Person(name=$name)"
}
}
GOOD:
data class Person(val name: String)
Enum classes without methods may be formatted without line-breaks, as follows:
enum class AccountStatus { ACTIVE, LOCK_WRONG_PIN, LOCK_BY_CUSTOMER, CANCEL; }
Spacing is especially important in coding, as code needs to be easily readable as part of the tutorial.
Indentation is using spaces - never tabs.
Indentation for blocks uses 2 spaces:
BAD:
for (i in 0..9) {
Log.i(TAG, "index=" + i)
}
GOOD:
for (i in 0..9) {
Log.i(TAG, "index=" + i)
}
Indentation for line wraps should use 4 spaces:
BAD:
val widget: CoolUiWidget =
someIncrediblyLongExpression(that, reallyWouldNotFit, on, aSingle, line)
GOOD:
val widget: CoolUiWidget =
someIncrediblyLongExpression(that, reallyWouldNotFit, on, aSingle, line)
Lines should be no longer than 100 characters long.
There should be exactly one blank line between methods to aid in visual clarity and organization. Whitespace within methods should separate functionality, but having too many sections in a method often means you should refactor into several methods.
When they are needed, use comments to explain why a particular piece of code does something. Comments must be kept up-to-date or deleted.
Avoid block comments inline with code, as the code should be as self-documenting as possible. Exception: This does not apply to those comments used to generate documentation.
Semicolons are dead to us should be avoided wherever possible in Kotlin.
BAD:
val horseGiftedByTrojans = true;
if (horseGiftedByTrojans) {
bringHorseIntoWalledCity();
}
GOOD:
val horseGiftedByTrojans = true
if (horseGiftedByTrojans) {
bringHorseIntoWalledCity()
}
Unlike Java, direct access to fields in Kotlin is preferred.
If custom getters and setters are required, they should be declared following Kotlin conventions rather than as separate methods.
Only trailing closing-braces are awarded their own line. All others appear the same line as preceding code:
BAD:
class MyClass
{
fun doSomething()
{
if (someTest)
{
// ...
}
else
{
// ...
}
}
}
GOOD:
class MyClass {
fun doSomething() {
if (someTest) {
// ...
} else {
// ...
}
}
}
Conditional statements are always required to be enclosed with braces, irrespective of the number of lines required.
BAD:
if (someTest)
doSomething()
if (someTest) doSomethingElse()
GOOD:
if (someTest) {
doSomething()
}
if (someTest) { doSomethingElse() }
Unlike switch
statements in Java, when
statements do not fall through. Separate cases using commas if they should be handled the same way. Always include the else case.
BAD:
when (anInput) {
1 -> doSomethingForCaseOneOrTwo()
2 -> doSomethingForCaseOneOrTwo()
3 -> doSomethingForCaseThree()
}
GOOD:
when (anInput) {
1, 2 -> doSomethingForCaseOneOrTwo()
3 -> doSomethingForCaseThree()
else -> println("No case satisfied")
}
val array = ArrayList<Int>()
BAD:
array.get(0)
getActivity().finish()
GOOD:
array[0]
requireActivity().finish()
fun hoge(value: Boolean?) {
value ?: return
if (value) {
}
}
fun hoge(context: Context) {
context as Activity
context.finish() // finish activity
}
BAD:
if (hoge !is Hoge) {
throw IllegalArgumentException("not Hoge!")
}
hoge.foo()
GOOD:
hoge as? Hoge ?: throw IllegalArgumentException("not Hoge!")
hoge.foo()
BAD:
// bad
{ hoge ->
hoge?.let { it.foo() }
}
GOOD:
{
it?.let { hoge -> hoge.foo() }
}
You can write a type if it is difficult to understand.
- property
val hoge = 0 // Int
val foo = 10L // Long
val bar = 100f //Float
- function
// return Boolean
fun Context.isConnectToWifi() =
(getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager)
.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI
// return Point
fun Display.getSize(): Point = Point().apply { getSize(this) }
You don't have to write for loop because threre is forEach
in collections package of Kotlin.
BAD:
for (i in 0..9) {
}
GOOD:
(0..9).forEach {
}
(0..9).forEachIndexed { index, value ->
}
BAD:
val pair = Pair(foo, bar)
GOOD:
val pair = foo to bar
to
is infix function
kotlin public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
val char = 'K'
BAD:
if (char >= 'A' && 'c' <= 'Z') print("Hit!")
GOOD:
if (char in 'A'..'Z') print("Hit!")
when (char) {
in 'A'..'Z' -> print("Hit!")
else -> return
}
BAD:
val hoge = 10if (hoge > 10) {
} else if (hoge > 5) {
} else if (hoge > 0) {
} else {
}
GOOD:
when {
hoge > 10 -> print("10")
hoge > 5 -> print("0")
hoge > 0 -> print("0")
else -> print("else")
}
val hoge: Hoge = Hoge()
when (hoge) {
is Hoge -> {
}
else -> {
}
}
val hoge = 10
when (hoge) {
in 0..4 -> print("0..4")
in 5..10 -> print("5..10")
}
val foo: Int = 5
BAD:
val bar = if (foo > 10) {
"kotlin"
} else {
"java"
}
GOOD:
val bar = if(foo > 10) "kotlin" else "java"
class Hoge {
fun fun1() {}
fun fun2() {}
}
BAD:
val hoge = Hoge()
hoge.fun1()
hoge.fun2()
GOOD:
val hoge = Hoge().apply {
fun1()
fun2()
}
You don't need to use with
because you can substitute run
, let
, apply
, also
.
You can express using a scope function.
But it becomes hard to read in case it excessibely use.
You should declare variables if you need to think.
class Foo
class Bar(val foo: Foo)
class Hoge {
fun fun1() {}
fun fun2(bar: Bar) {}
fun fun3(foo: Foo) {}
}
BAD:
val hoge = Hoge()
val foo = Foo()
val bar = Bar(foo)
hoge.fun1()
hoge.fun2(bar)
hoge.fun3(foo)
GOOD:
Hoge().run {
fun1()
Foo().let {
fun2(Bar(it))
fun3(it)
}
}
- 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) {}
}
BAD:
Hoge().let {
it.fun1()
Foo().run {
it.fun2(Bar(this))
it.fun3(this)
}
}
GOOD:
Hoge().run {
fun1()
Foo().let {
fun2(Bar(it))
fun3(it)
}
}
Avoid not handling exceptions in the correct manner. For example:
BAD:
fun setUserId(id: String) {
try {
mUserId = id.toInt()
} catch (e: NumberFormatException) {
}
}
This gives no information to both the developer and the user, making it harder to debug and could also leave the user confused if something goes wrong. When catching an exception, we should also always log the error to the console for debugging purposes and if necessary alert the user of the issue. For example:
GOOD:
fun setCount(count: String?) {
var count = count
try {
count = id.toInt()
} catch (e: NumberFormatException) {
count = 0
Log.e(TAG, "There was an error parsing the count $e")
DialogFactory.showErrorMessage(R.string.error_message_parsing_count)
}
}
Here we handle the error appropriately by:
- Showing a message to the user notifying them that there has been an error
- Setting a default value for the variable if possible
- Throw an appropriate exception
Catching exceptions generally should not be done
Why? Do not do this. In almost all cases it is inappropriate to catch generic Exception or Throwable (preferably not Throwable because it includes Error exceptions). It is very dangerous because it means that Exceptions you never expected (including RuntimeExceptions like ClassCastException) get caught in application-level error handling. It obscures the failure handling properties of your code, meaning if someone adds a new type of Exception in the code you're calling, the compiler won't help you realize you need to handle the error differently. In most cases you shouldn't be handling different types of exception the same way.
BAD:
fun openCustomTab(context: Context, uri: Uri?) {
val intent: Intent = buildIntent(context, uri)
try {
context.startActivity(intent)
} catch (e: Exception) {
Log.e(TAG, "There was an error opening the custom tab $e")
}
}
GOOD:
fun openCustomTab(context: Context, uri: Uri?) {
val intent: Intent = buildIntent(context, uri)
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "There was an error opening the custom tab $e")
}
}
Using try-catch statements improves the readability of the code where the exception is taking place. This is because the error is handled where it occurs, making it easier to both debug or make a change to how the error is handled.
There are no guarantees as to when a finalizer will be called, or even that it will be called at all. In most cases, you can do what you need from a finalizer with good exception handling. If you absolutely need it, define a close() method (or the like) and document exactly when that method needs to be called. See InputStreamfor an example. In this case it is appropriate but not required to print a short log message from the finalizer, as long as it is not expected to flood the logs.
When declaring imports, use the full package declaration.
BAD:
import android.support.v7.widget.*;
GOOD:
import android.support.v7.widget.RecyclerView;
Sometimes removing code from a class can mean that some imports are no longer needed. If this is the case then the corresponding imports should be removed alongside the code.
Always use Kotlin's native types when available. Kotlin is JVM-compatible so [TODO: more info]
Type inference should be preferred where possible to explicitly declared types.
BAD:
val something: MyType = MyType()
val meaningOfLife: Int = 42
GOOD:
val something = MyType()
val meaningOfLife = 42
Constants are defined using the val
keyword, and variables with the var
keyword. Always use val
instead of var
if the value of the variable will not change.
Tip: A good technique is to define everything using val
and only change it to var
if the compiler complains!
Declare variables and function return types as nullable with ?
where a null
value is acceptable.
Use implicitly unwrapped types declared with !!
only for instance variables that you know will be initialized before use, such as subviews that will be set up in onCreate
for an Activity or onCreateView
for a Fragment.
When naming nullable variables and parameters, avoid naming them like nullableString
or maybeView
since their nullability is already in the type declaration.
When accessing a nullable value, use the safe call operator if the value is only accessed once or if there are many nullables in the chain:
editText?.setText("foo")
Don't use !!
, it will erase the benefits of Kotlin.
You use it only when you want to explicitly raise a Null Pointer Exception.
class Hoge {
fun fun1() {}
fun fun2() {}
fun fun3() = true
}
var hoge: Hoge? = null
BAD:
if (hoge != null) {
hoge.fun1()
} else {
val hoge = Hoge()
hoge.fun1()
hoge.fun2()
}
GOOD:
hoge?.run {
fun1()
} ?: run {
hoge = Hoge().apply {
fun1()
fun2()
}
}
GOOD:
if (hoge != null && hoge.fun3()) {
hoge.fun1()
} else {
hoge = Hoge().apply {
fun1()
fun2()
}
}
Basically, we omit a type of return value as declaration variables using type interface.
You should write it in case we cannot guess from a method name.
fun createIntent(context: Context, foo: Int, bar: Boolean) =
Intent(context, HogeActivity::class.java).apply {
putExtra("foo", foo)
putExtra("bar", bar)
}
fun hoge(value: Int): String = when (value) {
in 0..10 -> "foo"
in 100..500 -> "bar"
else -> "else"
}
Don't name setHoge
, getHoge
at property and function because it is used in Kotlin language.
BAD:
fun setHoge() {
}
BAD:
class Hoge {
fun hoge() {
print("hoge")
}
fun hoge(prefix: String) {
print(prefix + "hoge")
}
}
GOOD:
class Hoge {
fun hoge(prefix: String = "") {
print(prefix + "hoge")
}
}
BAD:
interface CallBackListener {
fun onHoge(foo: String, bar: Int)
}
// caller
var callback: CallBackListener? = null
callback?.onHoge("foo", 100)
// callee
val callback = object : CallBackListener {
override fun onHoge(foo: String, bar: Int) {
print("$foo : $bar")
}
}
GOOD:
typealias CallBackListener = (foo: String, bar: Int) -> Unit
// caller
var callback: CallBackListener? = null
callback?.invoke("foo", 100)
// callee
val callback = { foo, bar ->
print("$foo : $bar")
}
-
Use it as utility functions in Java
-
It is not necessary to use extension functions in case it is defined wide scope. i.g. String In case we recommend the private extension functions.
-
Extension class name :
HogeExt.kt
-
Structure of package
main
|-data
|-ui
|-util
|-ext
|-util
|-ext
Functions taking only one object as an argument replace private extension functions. (not necessary)
enum class Hoge() {
FOO,
BAR,
NONE
}
BAD:
fun toHoge(arg: String): Hoge {
if (!arg.startsWith("hoge")) {
return Hoge.NONE
}
return when (arg) {
"hogeFoo" -> Hoge.FOO
"hogeBar" -> Hoge.BAR
else -> Hoge.NONE
}
}
GOOD:
fun String.toHoge(): Hoge {
if (!startsWith("hoge")) {
return Hoge.NONE
}
return when (this) {
"hogeFoo" -> Hoge.FOO
"hogeBar" -> Hoge.BAR
else -> Hoge.NONE }
}
}
class Hoge {
// bad
fun Hoge.foo() {
}
// good
fun foo() {
}
}
Since Android uses XML extensively in addition to Java, we have some rules specific to XML.
View-based XML files should be prefixed with the type of view that they represent.
BAD:
login.xml
main_screen.xml
rounded_edges_button.xml
GOOD:
activity_login.xml
fragment_main_screen.xml
button_rounded_edges.xml
If the XML files is store in local modules, it should be prefixed with module name.
GOOD:
commons_button_gradient.xml
commons_fragment_base.xml
Similarly to Kotlin, indentation should be two characters.
Wherever possible XML resource files should be used:
- Strings =>
res/values/strings.xml
- Styles =>
res/values/styles.xml
- Colors =>
res/color/colors.xml
- Animations =>
res/anim/
- Drawable =>
res/drawable
Where appropriate, XML attributes should appear in the following order:
id
attributelayout_*
attributes- style attributes such as
gravity
ortextColor
- value attributes such as
text
orsrc
Within each of these groups, the attributes should be ordered alphabetically.
When Kotlin extensions are used (and view IDs are made available via import), view ID names (android:id in XML) should be all lowercase with underscores separating words (snake_case). It is required that every name have at least one underscore to distinguish it from local variables. In instances were it proves difficult, then name may be postfixed with the component type (e.g. _button or _view).
BAD:
<android.support.v7.widget.RecyclerView
android:id="@+id/moves"
/>
<android.support.v7.widget.RecyclerView
android:id="@+id/danceMoves"
/>
GOOD:
<android.support.v7.widget.RecyclerView
android:id="@+id/dance_moves"
/>
Drawable resource files should be named using the ic_ prefix along with the size and color of the asset. For example, white accept icon sized at 24dp would be named:
ic_accept_24sdp_white
ic_cancel_48sdp_black
We use this naming convention so that a drawable file is recognisable by its name. If the colour and size are not stated in the name, then the developer needs to open the drawable file to find out this information. This saves us a little bit of time. If the drawable files is stored in local modules, it should be prefixed with moudle name.
commons_ic_accept_24sdp_white
network_ic_cancel_48sdp_black
Use en-US
English spelling. 🇺🇸
BAD:
val colourName = "red"
GOOD:
val colorName = "red"