Skip to content
Snippets Groups Projects
Select Git revision
  • 5710207eecd78b3105c020b29b832487743ceaec
  • master default
  • notifications_test_ndf
  • main_ud_local_ndf
  • crust-integration
  • new-client
  • internal_build
  • FE-1054_test_net_ndf
  • FE-1053_change_retry_behavior
  • app_modularization_refactor
  • FE_1020_remove_version_check
  • FE-992_android_migration
  • XX-4094_add_fact_panic
  • FE-990_retry_reset_request
  • development
  • 2.92
  • 2.9
  • 2.8
  • 2.7
  • 2.5
  • 2.3
  • 2.2
  • 2.1
  • 2.04
  • 2.03
  • 2.02
26 results

code-standards.md

  • Kamal Bramwell's avatar
    Kamal Bramwell authored
    8068d692
    History

    The Official XX Network Kotlin Style Guide (in progress)

    Goals

    Use of this style guide ensures:

    • Maintainability
    • Conciseness
    • Readability
    • Simplicity

    Inspiration

    Most of this guide statements are drawn or adapted from one of the following sources:

    Google Java Style Guide - Old school can still be contemporary

    Android Kotlin Style Guide - Official Android style guide

    Kotlin Coding Conventions - Official Kotlin guidelines for coding

    Android Contributors Style Guide - Contribution insights on open sourcing

    Google Swift Guide - General Formatting and Rules that can be applied to Kotlin, as they share many syntax similarities

    Table of Contents

    Click to expand

    Naming

    Kotlin is a JVM-compatible language like Java, and naming should follow Kotlin standards.

    Packages

    • Package names are all lower-case
    • Multiple words are discouraged
    • If multiple words are needed, they can be used concatenated together, without hyphens or underscores

    BAD :no_entry::

    io.Elixxir.some_widget

    GOOD :white_check_mark::

    io.elixxir.somewidget

    Classes, Objects & Interfaces

    • Written in UpperCamelCase

    BAD :no_entry::

    open class myExampleClass { /*...*/ }
    object emptyObject : myExampleClass() { /*...*/ }
    interface myInterface { /*...*/}

    GOOD :white_check_mark::

    open class MyExampleClass { /*...*/ }
    object EmptyExampleClass : MyExampleClass() { /*...*/ }
    interface MyInterface { /*...*/}

    Methods

    • Written in lowerCamelCase, without hyphens or underscores

    BAD :no_entry::

    fun ProcessRequests() { /*...*/ }
    var finishedCount = 1

    GOOD :white_check_mark::

    fun processRequests() { /*...*/ }
    var finishedCount = 1

    Properties

    • Generally, written in lowerCamelCase
    • Properties should not be named with Hungarian notation, as it is as it is not recommended by Google
    • Constant values in the companion object should be written all uppercase using underscores in cases with multiple words

    Example property names

    BAD :no_entry::

    class MyClass {
      var publicfield: Int = 0
      val Person = Person()
      private var mPrivateField: Int?
    }

    GOOD :white_check_mark::

    class MyClass {
      var publicField: Int = 0
      val person = Person()
      private var privateField: Int?
    }

    Example constant names

    BAD :no_entry::

    companion object {
      const val theAnswer = 42
    }

    GOOD :white_check_mark::

    companion object {
      const val THE_ANSWER = 42
    }

    Exception #1 val properties with no custom get function that hold deeply immutable data) should use uppercase underscore-separated names.

    BAD :no_entry::

    val keyName = "KeyValue" //Immutable

    GOOD :white_check_mark::

    val KEY_NAME = "KeyValue" //Immutable

    Variables

    • Written in lowerCamelCase.

    BAD :no_entry::

    val HTTPSAddress = "https://xx.network"

    GOOD :white_check_mark::

    val httpsAddress = "https://xx.network"
    • Except for temporary looping variables, single character variables are not encouraged.
    for (x in 0..10) { /*...*/ }

    Other

    • Acronyms should be treated as words
    Good :white_check_mark: Bad :no_entry:
    XmlHttpRequest XMLHTTPRequest
    customerId customerID
    url URL
    id ID

    Resources Naming

    General Rule

    One drawable must have the same file name for all screen densities (mdpi, hdpi etc.) and the file must be added to the corresponding drawable density folder
    (drawable-mdpi, drawable-hdpi, drawable-xhdpi, drawable-xxhdpi).

    All resources names have the following convention depending on the need:
    <LOCATION>_<TYPE>_<WHAT>_<STATE>_<COLOR>_<SIZE>

    <LOCATION>

    • If the resources belongs to an specific screen only, it must start with its name, e.g.: onboard_feature_1.xml

    <TYPE>

    • The resource type, as specified in the next topics, e.g.: btn_gradient_big_corner.xml
    • If not can specified, it can be described with a long form name, e.g.: shape_circle_filled_white.xml

    <WHAT>

    • Describes what that resource is representing, e.g.: main_selector_tab_indicator.xml

    <STATE>

    • If the resource represents a state, it should be named accordingly, e.g.: btn_success_pressed.xml

    <COLOR>

    • If the resource has more than one color, it should be specified, e.g.: selector_btn_light.xml and selector_btn_dark.xml

    <SIZE>

    • Most used for drawables and dimensions, to represent the resource in different sizes, e.g.: ic_error_sign_red_24dp.xml and ic_error_sign_red_32dp.xml

    Main Types

    Resource Type Prefix Example
    Action bar ab_ ab_stacked.png
    Button btn_ btn_send_pressed.png
    Dialog dialog_ dialog_top.png
    Divider divider_ divider_horizontal.png
    Icon ic_ ic_star.png
    Menu menu_ menu_submenu_bg.png
    Notification notification_ notification_bg.png
    Tabs tab_ tab_pressed.png
    Selector selector_ selector_btn_color.xml
    Shape shape_ shape_rectangle_big.xml

    Icons

    Resource Type Prefix Example
    Icons ic_ ic_star.xml
    Launcher icons ic_launcher ic_launcher_calendar.xml
    Menu icons and Action Bar icons ic_menu ic_menu_archive.xml
    Status bar icons ic_stat_notify ic_stat_notify_msg.xml
    Tab icons ic_tab ic_tab_recent.xml
    Dialog icons ic_dialog ic_dialog_info.xml

    Selector States

    State Suffix Example
    Normal _normal btn_order_normal.xml
    Pressed _pressed btn_order_pressed.xml
    Focused _focused btn_order_focused.xml
    Disabled _disabled btn_order_disabled.xml
    Selected _selected btn_order_selected.xml

    Strings

    String names should start with a prefix that identifies the section they belong to and its description:

    Section String name
    Login login_username_hint
    Registration registration_help_text
    Main main_title
    User Profile user_profile_picture

    If a string is not associated to an specific screen, you should follow the rules below:

    Prefix Description
    error_ An error message
    msg_ A regular information message
    title_ A title, i.e. a dialog title
    action_ An action such as "Save" or "Create"

    Dimensions

    Apps should only define dimensions for values which are constantly reused.

    Dimension Type Dimension Name
    typical spacing <dimen name="spacing_xsmall">8dp</dimen>
    font <dimen name="font_larger">22sp</dimen>
    padding <dimen name="padding_large">24dp</dimen>

    Layouts

    Layout files should match the name of the Android locations that they are intended for, using that as a prefix:

    Component Class Name Layout Name
    Activity LoginActivity activity_login.xml
    Fragment SignUpFragment fragment_sign_up.xml
    Dialog WebViewDialog dialog_web_view.xml
    Adapter RecyclerView ---
    Holder item --- item_contact.xml
    Other layout --- component_menu_bar.xml

    Formatting

    Spacing is important for code readability

    Source File

    All source files must be encoded as UTF-8.

    Indentation

    • Indentation is using 4 spaces, never tabs
    • If using tabs, consider using soft tabs (tabs converted to spaces) only. Tabs (hard tabs) are highly problematic since they correspond to different spaces depending on the text editor/IDE

    BAD :no_entry::

    val test: Int = 12
    for (i in 10..42) {
      println(
        when {
          i < test -> -1
          i > test -> 1
          else -> 0
        }
      )
    }

    GOOD :white_check_mark::

    val test: Int = 12
    for (i in 10..42) {
        println(
            when {
                i < test -> -1
                i > test -> 1
                else -> 0
            }
        )
    }

    Line Wrapping

    This should follow Google's Kotlin Style Guide.

    • One statement per line, no semicolon (;)
    • Except for package, import, shell command lines in comments or long URLs, lines should be no longer than 100 characters long
    • Indentation for line wraps should use 4 spaces as well

    BAD :no_entry::

    val foo: FunkyWidget =
            someReallyLongExpression("that would not fit in a single line")

    GOOD :white_check_mark::

    val foo: FunkyWidget =
        someReallyLongExpression("that would not fit in a single line")

    Line Break

    Should follow some rules of Google's Java Guide in addition to Google's Kotlin Style Guide with some adaptations:

    Break at operators

    • When a line is broken at a non-assignment operator the break comes before the symbol
      • This also applies to the following "operator-like" symbols:
      • The dot separator (.)
      • The two colons of a member reference (::)

    BAD :no_entry::

    val longVariable = anotherVeryLongVariable + anotherLongerOne - thisRidiculousLongOne +
        theFinalOne

    GOOD :white_check_mark::

    val longVariable = anotherVeryLongVariable + anotherLongerOne
     - thisRidiculousLongOne + theFinalOne

    Observation:

    According to Kotlin Grammar, as there is no symbol for line continuation in Kotlin, if the first line of the statement is valid, the second line won't work. To avoid that behavior, we can use parenthesis:

    BAD :no_entry::

    val text = "This is" + "a valid" + "statement"
        + "this " + "line " + "won\'t" + "work" // syntax error

    GOOD :white_check_mark::

    val text = ("This is" + "a valid" + "statement"
        + "this " + "line " + "will" + "work") // no syntax error

    Assignment Operator

    An exception to the previous rule is the assignment operator =, where the line break should happen after the operator.

    • When a line is broken at an assignment operator the break typically comes after the symbol.
    • A method or constructor name stays attached to the open parenthesis (() that follows it.
    • A comma (,) stays attached to the token that precedes it.
    • A lambda arrow (->) stays attached to the argument list that precedes it.

    BAD :no_entry::

    val foo: FunkyWidget =
            someReallyLongExpression("that would not fit in a single line")

    GOOD :white_check_mark::

    val foo: FunkyWidget =
        someReallyLongExpression("that would not fit in a single line")

    Method Calls in Chain

    When multiple calls are chained in the same line, the Assignment Operator Rule is applied.

    BAD :no_entry::

    Picasso.with(context).load("https://elixxir.io/_nuxt/img/phone.d5cc20a.png").into(imageView)

    GOOD :white_check_mark::

    Picasso.with(context)
        .load("https://elixxir.io/_nuxt/img/phone.d5cc20a.png")
        .into(imageView)

    Long parameters case

    When a method has many parameters or its parameters are very long, we also apply Assignment Operator Rule

    BAD :no_entry::

    loadImage(context, "https://elixxir.io/_nuxt/img/phone.d5cc20a.png", profilePictureImageView, clickListener, "XX Messenger for Smartphones")

    GOOD :white_check_mark::

    loadImage(
        context,
        "https://elixxir.io/_nuxt/img/phone.d5cc20a.png",
        profilePictureImageView,
        clickListener,
        "XX Messenger for Smartphones"
    )
    
    //or
    
    loadImage(context,"https://elixxir.io/_nuxt/img/phone.d5cc20a.png",
        profilePictureImageView, clickListener,"XX Messenger for Smartphones"
    )

    Vertical Indentation

    Single line spacing between method. If a method has many "sections" that often means it should be split into several methods.

    Horizontal Indentation

    In general, you should add a single space separation in the following cases:

    • if, for, or catch before an open parenthesis (() that follows it on the same line
    • Before any open curly brace ({)
    • For any reserved word such as else or catch, separating it from a closing curly brace (}) that precedes it

    BAD :no_entry::

    if(list.isEmpty()){
        doSomething()
    }else{
        doSomethingElse()
    }

    GOOD :white_check_mark::

    if (list.isEmpty()) {
        doSomething()
    } else {
        doSomethingElse()
    }

    Horizontal Operators Indentation

    Operators other than two colons (::), dot (.) and range (..) should be separated on both sides:

    BAD :no_entry::

    val c = a+b //binary operator
    
    list.map { item->item.toString() } //arrow operator
    
    val toString =  Any  :: toString //two colons
    
    it . toString() //dot
    
    for  (i in  1  ..  10) print(i) //range operator

    GOOD :white_check_mark::

    val c = a + b //binary operator
    
    list.map { item -> item.toString() } //arrow operator
    
    val toString = Any::toString //two colons
    
    it.toString() //dot
    
    for (i in 1..10) print(i) //range operator

    Exceptions:

    • Spacing must be added after a (,) or (;)
    • Spacing should be added before a colon (:) only if that is used for base class or interface declarations

    BAD :no_entry::

    class Foo: Bar
    
    fun <T> max(a: T, b: T) where T: Comparable<T> // last T is not declaration

    GOOD :white_check_mark::

    class Foo : Bar
    
    fun <T> max(a: T, b: T) where T : Comparable<T> // last T is not declaration

    Modifiers

    Public Modifier

    Just like Java, the default modifier for Kotlin is public . As that's redundant, it should be omitted.

    BAD :no_entry::

     class Person(
        id: Int,
        name: String,
        surname: String
    ) : Human(id, name) { /*...*/ }

    GOOD :white_check_mark::

    class Person(
        id: Int,
        name: String,
        surname: String
    ) : Human(id, name) { /*...*/ }

    Other Visibility Modifiers

    Modifiers should be explicitly used for classes, methods and member variables. According to Kotlin Coding Conventions, if the declaration has multiple modifiers, they should be put in the following order:

    public / protected / private / internal
    expect / actual
    final / open / abstract / sealed / const
    external
    override
    lateinit
    tailrec
    vararg
    suspend
    inner
    enum / annotation
    companion
    inline
    infix
    operator
    data

    Classes, Variables & Objects

    Variable Declaration

    Shall have single declaration per line.

    BAD :no_entry::

    val username: String; val password: String

    GOOD :white_check_mark::

    val username: String
    val password: String

    Classes

    • A single class per source file
    • Inner classes only when appropriate.

    Data Classes

    For holding simple data, data classes are preferred.

    BAD :no_entry::

    class Person(val name: String, val age: Int) {
      override fun toString() : String {
        return "Person(name = $name, age = $age)"
      }
    }

    GOOD :white_check_mark::

    data class Person(
        val name: String = "",
        val age: Int = 0
    )

    Enum Classes

    Enum classes may be formatted as follows:

    private enum class Color {
     RED, GREEN, BLUE, YELLOW
    }
    
    //or
    
    private enum class Color {
       RED,
       GREEN,
       BLUE,
       YELLOW
    }

    Getters & Setters

    Unlike Java, getters and setters in Kotlin are auto-generated, and they should be only declared if needed following Kotlin conventions:

    class  Example {
       var name: String = "Elixxir"
    }

    Companion Objects

    Companion objects are similar to Java's static modifier, and it should only be used while declaring a function or a property to be tied to a class rather than to instances of it.

    Conditionals & Braces

    Braces

    For curly braces, the opening braces will be placed in the same line that opens a scope block and the closing brace on a separate line aligned horizontally with the opening construct:

    BAD :no_entry::

    class MyExample
        {
            fun doSomething()
                {
                    if (someTest)
                        {
                          /* some logic */
                        }
                    else
                        {
                          /* some logic */
                        }
                }
        }

    GOOD :white_check_mark::

    class MyExample {
        fun doSomething() {
            if (someTest) {
                /* some logic */
            } else {
                /* some other logic */
            }
        }
    }

    If Statements

    If statements are preferable to be used for binary conditions, and must be always enclosed with braces for both readability and code correctness, as it makes it easier to avoid mistakes:

    BAD :no_entry::

    if (someCondition)
        doSomething()
    
    if (someCondition)
        doSomething()
        doSomethingElse() //This is not part of the if scope

    GOOD :white_check_mark::

    if (someCondition) {
        doSomething()
    }
    
    //Or
    
    if (someCondition) {
      doSomething()
      doSomethingElse() //This is part of the scope
    }

    if braces can be omitted if the entire expression fits a single line:

    BAD :no_entry::

    var v = if (condition)
            a //that's not ok
        else
            b //that's not ok
    
    if (condition)
        callSomeFunc() //bad

    GOOD :white_check_mark::

    var v = if (condition) a else b //that's ok
    
    if (condition) {
        doSomething() //this is acceptable
    }
    
    if (condition) callSomeFunc() //this is also acceptable

    When Statements

    • Preferably used when comparing one expression against many expressions.
    • Separate cases using commas if they should be handled the same way.
    • You must always include else case.

    BAD :no_entry::

    when (input) {
        'a' -> doSomething()
        'b' -> doSomething()
        'c' -> doSomethingElse()
    }

    GOOD :white_check_mark::

    when (input) {
        'a', 'b' -> doSomethingForCaseOneOrTwo()
        'c' -> doSomethingForCaseThree()
        else -> println("No case was satisfied")
    }

    Types

    Immutable Types

    Only use var for values that will change over time. Otherwise, use val

    Type Inference

    A local value initialized by an expression should preferably use type inference instead of declaring its type, where possible:

    BAD :no_entry::

    fun main() {
        val manufacturer: String = myCarObj.getManufacturer() //Type inference
        println("Car manufacturer: $manufacturer")
    }

    GOOD :white_check_mark::

    fun main() {
        val manufacturer = myCarObj.getManufacturer() //Type inference
        println("Car manufacturer: $manufacturer")
    }

    Nullable Types

    As Kotlin compiler by default doesn’t allow any null values at compile-time, only declare a type as nullable if required using ? after type declaration.

    BAD :no_entry::

    var example: String = "Elixxir Style Guide"
    str = null  // compilation error

    GOOD :white_check_mark::

    var example: String? = "Elixxir Style Guide"
    str = null  // no error

    Use implicitly unwrapped types (!!) only if the variable will be initialized before use, or the safe operator (?) if it is not certain the variable will be initialized:

    BAD :no_entry::

    var item: Example? = null
    /* other calls */
    item.setMsg("example")
    item.msg

    GOOD :white_check_mark::

    var item: Example? = null
    /* other calls */
    item = Example()
    /* some more calls */
    item?.setMsg("example") //If unsure the value is initialized
    item!!.msg              //If sure the value is initialized

    Misc

    Comments

    In most cases, code should be self explanatory. In exceptionally clever or complicated instances, comments can be used for ease of readability and understanding. Comments must be kept up-to-date or deleted.

    Exception: This does not apply to those comments used to generate documentation.

    Language

    Use en-US english spelling. :flag_us:

    BAD :no_entry::

    val centreAlignment = 24f

    GOOD :white_check_mark::

    val centerAlignment = 24f

    Copyright Statement

    The following copyright statement should be included at the top of every source file:

    Copyright here

    Credits

    This style guide is a collaborative effort from: