Android – How To Select Scope Functions In Kotlin?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Select Scope Functions In Kotlin?).

In this article, we will learn about how to select Scope Functions in Kotlin. The Kotlin standard library contains several functions whose sole purpose is to execute a block of code within the context of an object. When we call such a function on an object with a lambda expression provided, it forms a temporary scope. In this scope, we can access the object without its name. Such functions are called Scope Functions. This article provides the detailed descriptions of the differences between scope functions and the conventions on their usage in Kotlin with some authentic examples.

A famous quote about learning is :

” Tell me and I forget, teach me and I may remember, involve me and I learn.”

So Let’s begin.

Scope Functions in Kotlin

The definition of Scope function is

Scoped functions are functions that execute a block of code within the context of an object.

These functions provide a way to give temporary scope to the object under consideration where specific operations can be applied to the object within the block of code, thereby, resulting in a clean and concise code. There are five scoped functions in Kotlin: letrunwithalso and apply.

Basically, these functions do the same: execute a block of code on an object. What’s different is how this object becomes available inside the block and what is the result of the whole expression.

Example

Here’s a typical usage of a scope function:

Person("Alice", 20, "Amsterdam").let {
     println(it)
     it.moveTo("London")
     it.incrementAge()
     println(it)
 }

If we write the same without let, we’ll have to introduce a new variable and repeat its name whenever we use it.

val alice = Person("Alice", 20, "Amsterdam")
 println(alice)
 alice.moveTo("London")
 alice.incrementAge()
 println(alice)

The scope functions do not introduce any new technical capabilities, but they can make our code more concise and readable.

Due to the similar nature of scope functions, choosing the right one for our case can be a bit tricky. The choice mainly depends on our intent and the consistency of use in our project. 

Common Difference between Scope Functions

Because the scope functions are all quite similar in nature, it’s important to understand the differences between them. There are two main differences between each scope function:

  • The way to refer to the context object
  • The return value.

Context object – this or it

Inside the lambda of a scope function, the context object is available by a short reference instead of its actual name. Each scope function uses one of two ways to access the context object: as a lambda receiver (this) or as a lambda argument (it). Both provide the same capabilities, so we’ll describe the pros and cons of each for different cases and provide recommendations on their use.

fun main() {
     val str = "Hello"
     // this
     str.run {
         println("The receiver string length: $length")
         //println("The receiver string length: ${this.length}") // does the same
     }
     // it 
     str.let {     
        println("The receiver string's length is ${it.length}")           }
}

this

runwith, and apply refer to the context object as a lambda receiver – by keyword this. Hence, in their lambdas, the object is available as it would be in ordinary class functions. In most cases, we can omit this when accessing the members of the receiver object, making the code shorter. On the other hand, if this is omitted, it can be hard to distinguish between the receiver members and external objects or functions. So, having the context object as a receiver (this) is recommended for lambdas that mainly operate on the object members: call its functions or assign properties.

val adam = Person("Adam").apply { 
     age = 20                       // same as this.age = 20 or adam.age = 20
     city = "London"
 }
 println(adam)

it

In turn, let and also have the context object as a lambda argument. If the argument name is not specified, the object is accessed by the implicit default name itit is shorter than this and expressions with it are usually easier for reading. However, when calling the object functions or properties we don’t have the object available implicitly like this. Hence, having the context object as it is better when the object is mostly used as an argument in function calls. it is also better if we use multiple variables in the code block.

fun getRandomInt(): Int {
     return Random.nextInt(100).also {
         writeToLog("getRandomInt() generated value $it")
     }
 }
 val i = getRandomInt()

Additionally, when we pass the context object as an argument, we can provide a custom name for the context object inside the scope.

fun getRandomInt(): Int {
     return Random.nextInt(100).also { value ->
         writeToLog("getRandomInt() generated value $value")
     }
 }
 val i = getRandomInt()

Return value

The scope functions differ by the result they return:

  • apply and also return the context object.
  • letrun, and with return the lambda result.

These two options let we choose the proper function depending on what we do next in our code.

Context object

The return value of apply and also is the context object itself. Hence, they can be included into call chains as side steps: we can continue chaining function calls on the same object after them.

val numberList = mutableListOf()
 numberList.also { println("Populating the list") }
     .apply {
         add(2.71)
         add(3.14)
         add(1.0)
     }
     .also { println("Sorting the list") }
     .sort()

They also can be used in return statements of functions returning the context object.

fun getRandomInt(): Int {
     return Random.nextInt(100).also {
         writeToLog("getRandomInt() generated value $it")
     }
 }
 val i = getRandomInt()

Lambda result

letrun, and with return the lambda result. So, we can use them when assigning the result to a variable, chaining operations on the result, and so on.

val numbers = mutableListOf("one", "two", "three")
 val countEndsWithE = numbers.run { 
     add("four")
     add("five")
     count { it.endsWith("e") }
 }
 println("There are $countEndsWithE elements that end with e.")

Additionally, we can ignore the return value and use a scope function to create a temporary scope for variables.

val numbers = mutableListOf("one", "two", "three")
 with(numbers) {
     val firstItem = first()
     val lastItem = last()        
     println("First item: $firstItem, last item: $lastItem")
 }

We can analyze the common scope functions difference summary as below diagram:

Scoped Functions Summary

Five Scope Functions In Kotlin

To help we choose the right scope function for our case, we’ll describe them in detail and provide usage recommendations. Technically, functions are interchangeable in many cases, so the examples show the conventions that define the common usage style.

There are five scoped functions in Kotlin: letrunwithalso and apply. 

Let’s go through them one by one.

Scope Function – let

The context object is available as an argument (it). The return value is the lambda result.

let can be used to invoke one or more functions on results of call chains. For example, the following code prints the results of two operations on a collection:

val numbers = mutableListOf("one", "two", "three", "four", "five")
 val resultList = numbers.map { it.length }.filter { it > 3 }
 println(resultList)    

With let, we can rewrite it:

val numbers = mutableListOf("one", "two", "three", "four", "five")
 numbers.map { it.length }.filter { it > 3 }.let { 
     println(it)
     // and more function calls if needed
 } 

If the code block contains a single function with it as an argument, we can use the method reference (::) instead of the lambda:

val numbers = mutableListOf("one", "two", "three", "four", "five")
 numbers.map { it.length }.filter { it > 3 }.let(::println)

let is often used for executing a code block only with non-null values. To perform actions on a non-null object, use the safe call operator ?. on it and call let with the actions in its lambda.

val str: String? = "Hello"   
 //processNonNullString(str)       // compilation error: str can be null
 val length = str?.let { 
     println("let() called on $it")        
     processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
     it.length
 }

Another case for using let is introducing local variables with a limited scope for improving code readability. To define a new variable for the context object, provide its name as the lambda argument so that it can be used instead of the default it.

val numbers = listOf("one", "two", "three", "four")
 val modifiedFirstItem = numbers.first().let { firstItem ->
     println("The first item of the list is '$firstItem'")
     if (firstItem.length >= 5) firstItem else "!" + firstItem + "!"
 }.toUpperCase()
 println("First item after modifications: '$modifiedFirstItem'")

Scope Function – with

A non-extension function: the context object is passed as an argument, but inside the lambda, it’s available as a receiver (this). The return value is the lambda result.

We recommend with for calling functions on the context object without providing the lambda result. In the code, with can be read as “with this object, do the following.

val numbers = mutableListOf("one", "two", "three")
 with(numbers) {
     println("'with' is called with argument $this")
     println("It contains $size elements")
 }

Another use case for with is introducing a helper object whose properties or functions will be used for calculating a value.

val numbers = mutableListOf("one", "two", "three")
 val firstAndLast = with(numbers) {
     "The first element is ${first()}," +
     " the last element is ${last()}"
 }
 println(firstAndLast)

It is convenient when we have to call multiple different methods on the same object. Instead of repeating the variable containing this object on each line, we can use withThis function is used to change instance properties without the need to call dot operator over the reference every time.

Scope Function – run

The context object is available as a receiver (this). The return value is the lambda result.

run does the same as with but invokes as let – as an extension function of the context object.

run is useful when our lambda contains both the object initialization and the computation of the return value.

val service = MultiportService("https://example.kotlinlang.org", 80)
 val result = service.run {
     port = 8080
     query(prepareRequest() + " to port $port")
 }
 // the same code written with let() function:
 val letResult = service.let {
     it.port = 8080
     it.query(it.prepareRequest() + " to port ${it.port}")
 }

Besides calling run on a receiver object, we can use it as a non-extension function. Non-extension run lets us execute a block of several statements where an expression is required.

val hexNumberRegex = run {
     val digits = "0-9"
     val hexDigits = "A-Fa-f"
     val sign = "+-"
 Regex("[$sign]?[$digits$hexDigits]+")
 }
 for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
     println(match.value)
 }

run is actually a combination of with() and let().

Scope Function – apply

The context object is available as a receiver (this). The return value is the object itself.

Use apply for code blocks that don’t return a value and mainly operate on the members of the receiver object. The common case for apply is the object configuration. Such calls can be read as “apply the following assignments to the object.

val adam = Person("Adam").apply {
     age = 32
     city = "London"        
 }
 println(adam)

Having the receiver as the return value, we can easily include apply into call chains for more complex processing.

Scope Function – also

The context object is available as an argument (it). The return value is the object itself.

also is good for performing some actions that take the context object as an argument. Use also for actions that need a reference rather to the object than to its properties and functions, or when we don’t want to shadow this reference from an outer scope.

When we see also in the code, we can read it as “and also do the following with the object.

val numbers = mutableListOf("one", "two", "three")
 numbers
     .also { println("The list elements before adding new one: $it") }
     .add("four")

Scope Functions – run vs let

run is similar to let in terms of accepting any return value , but this is different in the context of the object terms. run function refers to the context of the object as “this” and not “it”. That is the reason we did not use “${this.name}” as it would be redundant here since the block of code understands that “name” is used here concerning the Person object.

Scope Functions – run redundant this

Another point here is that since the context is referred to as “this”, it cannot be renamed to a readable lambda parameter. So depending on the use case and requirement , we have to choose between the let and the run operator. The “run” operator also helps in easy null checks similar to the “let” operator.

var name: String? = "Abcd" 
private fun performRunOperation() {     
val name = Person().name?.run {         
"The name of the Person is: $this"     
}     
print(name) 
} 

Scope Functions – with vs run

Let’s consider a case where a Person object can be nullable.

Scope Functions With nullable Value

we can see that the context of the object referred to as “this” is a nullable type of Person. And hence, to correct this, we need to change the code as:

private fun performWithOperation() {     
val person: Person? = null     
with(person) {         
  this?.name = "asdf"         
  this?.contactNumber = "1234"         
  this?.address = "wasd"         
  this?.displayInfo()     
 } 
}

So performing a null check using a “with” operator is difficult and this is where we can replace it with “run” as follows:

private fun performRunOperation() {     
 val person: Person? = null     
 person?.run {         
   name = "asdf"         
   contactNumber = "1234"         
   address = "wasd"         
   displayInfo()     
 } 
}

This looks a lot cleaner.

Scope Functions – run vs apply

So let’s see the difference between run and apply functions.

Scope Functions – apply-vs-run

We can see that run accepts a return statement whereas apply does not accept a return statement(we can see the error thrown by the IDE in the image) and always returns the same object which it is referring to.

Scope Functions – let vs also

So let’s see the difference between let and also functions.

Scope Functions – let-vs-also

We can see that let accepts a return statement whereas “also” does not accept a return statement(we can see the error thrown by the IDE in the image) and always returns the same object which it is referring to.

Standard Library Scope Functions – takeIf and takeUnless

In addition to scope functions, the standard library contains the functions takeIf and takeUnless. These functions let us embed checks of the object state in call chains.

When we called on an object with a predicate provided, takeIf returns this object if it matches the predicate. Otherwise, it returns null. So, takeIf is a filtering function for a single object. In turn, takeUnless returns the object if it doesn’t match the predicate and null if it does. The object is available as a lambda argument (it).

val number = Random.nextInt(100)
val evenOrNull = number.takeIf { it % 2 == 0 }
val oddOrNull = number.takeUnless { it % 2 == 0 }
println("even: $evenOrNull, odd: $oddOrNull")

When we do chaining other functions after takeIf and takeUnless, we don’t forget to perform the null check or the safe call (?.) because their return value is nullable.

val str = "Hello"
 val caps = str.takeIf { it.isNotEmpty() }?.toUpperCase()
 //val caps = str.takeIf { it.isNotEmpty() }.toUpperCase() //compilation error
 println(caps)

takeIf and takeUnless are especially useful together with scope functions. A good case is chaining them with let for running a code block on objects that match the given predicate. To do this, call takeIf on the object and then call let with a safe call (?). For objects that don’t match the predicate, takeIf returns null and let isn’t invoked.

fun displaySubstringPosition(input: String, sub: String) {
     input.indexOf(sub).takeIf { it >= 0 }?.let {
         println("The substring $sub is found in $input.")
         println("Its start position is $it.")
     }
 }
 displaySubstringPosition("010000011", "11")
 displaySubstringPosition("010000011", "12")

This is how the same function looks without the standard library functions:

fun displaySubstringPosition(input: String, sub: String) {
     val index = input.indexOf(sub)
     if (index >= 0) {
         println("The substring $sub is found in $input.")
         println("Its start position is $index.")
     }
 }
 displaySubstringPosition("010000011", "11")
 displaySubstringPosition("010000011", "12")

The Selection Of Scope Functions

To help we choose the right scope function for our purpose, we provide the table of key differences between them.

FunctionObject referenceReturn valueIs extension function
letitLambda resultYes
runthisLambda resultYes
runLambda resultNo: called without the context object
withthisLambda resultNo: takes the context object as an argument.
applythisContext objectYes
alsoitContext objectYes
Key differences between Scope Function

Here is a short guidelines for choosing scope functions depending on the intended purpose:

  • Executing a lambda on non-null objects: let
  • Introducing an expression as a variable in local scope: let
  • Object configuration: apply
  • Object configuration and computing the result: run
  • Running statements where an expression is required: non-extension run
  • Additional effects: also
  • Grouping function calls on an object: with

The use cases of different scope functions overlap, so that we can choose the functions based on the specific conventions used in our project or team.

That’s all about in this article.

Related Other Articles / Posts

Conclusion

In this article, we understood about how to select Scope Functions in Kotlin. Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease our code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it’s easy to get confused about the current context object and the value of this or it. This article showed the detailed descriptions of the differences between scope functions and the conventions on their usage in Kotlin with some authentic examples.

Thanks for reading! I hope you enjoyed and learned about Scope Functions concepts in Kotlin. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – Understanding Kotlin Style Guide

Hello Readers, CoolMonkTechie heartily welcomes you in this article (Understanding Kotlin Style Guide).

In this article, We will learn about the Kotlin Style Guide. This article serves as the complete definition of Google’s Android coding standards for source code in the Kotlin Programming Language. A Kotlin source file is described as being in Google Android Style if and only if it adheres to the rules herein.

Like other programming style guides, the issues covered span not only aesthetic issues of formatting, but other types of conventions or coding standards as well. However, this article focuses primarily on the hard-and-fast rules that we follow universally, and avoids giving advice that isn’t clearly enforceable (whether by human or tool).

A famous quote about learning is :

” We now accept the fact that learning is a lifelong process of keeping abreast of change. And the most pressing task is to teach people how to learn. “

So Let’s begin.


1. Source Files

All source files must be encoded as UTF-8.


1.1. Naming

If a source file contains only a single top-level class, the file name should reflect the case-sensitive name plus the .kt extension. Otherwise, if a source file contains multiple top-level declarations, choose a name that describes the contents of the file, apply PascalCase, and append the .kt extension.

// MyClass.kt
class MyClass { }
// Bar.kt
class Bar { }
fun Runnable.toBar(): Bar = // …
// Map.kt
fun <T, O> Set<T>.map(func: (T) -> O): List<O> = // …
fun <T, O> List<T>.map(func: (T) -> O): List<O> = // …


1.2. Special Characters


1.2.1. Whitespace Characters

Aside from the line terminator sequence, the ASCII horizontal space character (0x20) is the only whitespace character that appears anywhere in a source file. This implies that:

  • All other whitespace characters in string and character literals are escaped.
  • Tab characters are not used for indentation.


1.2.2. Special Escape Sequences

For any character that has a special escape sequence (\b\n\r\t\'\"\\, and \$), that sequence is used rather than the corresponding Unicode (e.g., \u000a) escape.


1.2.3. Non-ASCII Characters

For the remaining non-ASCII characters, either the actual Unicode character (e.g., ) or the equivalent Unicode escape (e.g., \u221e) is used. The choice depends only on which makes the code easier to read and understand. Unicode escapes are discouraged for printable characters at any location and are strongly discouraged outside of string literals and comments.

ExampleDiscussion
val unitAbbrev = "μs"Best: perfectly clear even without a comment.
val unitAbbrev = "\u03bcs" // μsPoor: there’s no reason to use an escape with a printable character.
val unitAbbrev = “\u03bcs”`Poor: the reader has no idea what this is.
return "\ufeff" + contentGood: use escapes for non-printable characters, and comment if necessary.


1.3. Structure

.kt file comprises the following, in order:

  • Copyright and/or license header (optional)
  • File-level annotations
  • Package statement
  • Import statements
  • Top-level declarations

Exactly one blank line separates each of these sections.


1.3.1. Copyright / License

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

/*
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
 

Do not use a KDoc-style or single-line-style comment.

/**
 * Copyright 2017 Google, Inc.
 *
 * ...
 */
// Copyright 2017 Google, Inc.
//
// ...


1.3.2. File-level Annotations

Annotations with the “file” use-site target are placed between any header comment and the package declaration.


1.3.3. Package Statement

The package statement is not subject to any column limit and is never line-wrapped.


1.3.4. Import Statements

Import statements for classes, functions, and properties are grouped together in a single list and ASCII sorted.

Wildcard imports (of any type) are not allowed.

Similar to the package statement, import statements are not subject to a column limit and they are never line-wrapped.


1.3.5. Top-level Declarations

.kt file can declare one or more types, functions, properties, or type aliases at the top-level.

The contents of a file should be focused on a single theme. Examples of this would be a single public type or a set of extension functions performing the same operation on multiple receiver types. Unrelated declarations should be separated into their own files and public declarations within a single file should be minimized.

No explicit restriction is placed on the number nor order of the contents of a file.

Source files are usually read from top-to-bottom meaning that the order, in general, should reflect that the declarations higher up will inform understanding of those farther down. Different files may choose to order their contents differently. Similarly, one file may contain 100 properties, another 10 functions, and yet another a single class.

What is important is that each class uses some logical order, which its maintainer could explain if asked. For example, new functions are not just habitually added to the end of the class, as that would yield “chronological by date added” ordering, which is not a logical ordering.


1.3.6. Class Member Ordering

The order of members within a class follow the same rules as the top-level declarations.


2. Formatting


2.1. Braces

Braces are not required for when branches and if statement bodies which have no else if/else branches and which fit on a single line.

if (string.isEmpty()) return

when (value) {
    0 -> return
    // …
}

Braces are otherwise required for any ifforwhen branch, do, and while statements, even when the body is empty or contains only a single statement.

if (string.isEmpty())
    return  // WRONG!

if (string.isEmpty()) {
    return  // Okay
}


2.1.1. Non-empty Blocks

The braces follow the Kernighan and Ritchie style (“Egyptian brackets”) for nonempty blocks and block-like constructs:

  • Firstly, No line break before the opening brace.
  • Secondly, Line break after the opening brace.
  • Thirdly, Line break before the closing brace.
  • And finally, Line break after the closing brace, only if that brace terminates a statement or terminates the body of a function, constructor, or named class. For example, there is no line break after the brace if it is followed by else or a comma.

return Runnable {
    while (condition()) {
        foo()
    }
}

return object : MyClass() {
    override fun foo() {
        if (condition()) {
            try {
                something()
            } catch (e: ProblemException) {
                recover()
            }
        } else if (otherCondition()) {
            somethingElse()
        } else {
            lastThing()
        }
    }
}


2.1.2. Empty Blocks

An empty block or block-like construct must be in K&R style.

try {
    doSomething()
} catch (e: Exception) {} // WRONG!
try {
    doSomething()
} catch (e: Exception) {
} // Okay


2.1.3. Expressions

An if/else conditional that is used as an expression may omit braces only if the entire expression fits on one line.

val value = if (string.isEmpty()) 0 else 1  // Okay
val value = if (string.isEmpty())  // WRONG!
    0
else
    1
val value = if (string.isEmpty()) { // Okay
    0
} else {
    1
}


2.1.4. Indentation

Each time a new block or block-like construct is opened, the indent increases by four spaces. When the block ends, the indent returns to the previous indent level. The indent level applies to both code and comments throughout the block.


2.1.5. One Statement Per Line

Each statement is followed by a line break. Semicolons are not used.


2.1.6. Line Wrapping

Code has a column limit of 100 characters. Except as noted below, any line that would exceed this limit must be line-wrapped, as explained below.

Exceptions:

  • Lines where obeying the column limit is not possible (for example, a long URL in KDoc)
  • package and import statements
  • Command lines in a comment that may be cut-and-pasted into a shell


2.1.7. Where to break

The prime directive of line-wrapping is: prefer to break at a higher syntactic level. Also:

  • When a line is broken at an operator or infix function name, the break comes after the operator or infix function name.
  • When a line is broken at the following “operator-like” symbols, the break comes before the symbol:
    • The dot separator (.?.).
    • The two colons of a member reference (::).
  • 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.


2.1.8. Functions

When a function signature does not fit on a single line, break each parameter declaration onto its own line. Parameters defined in this format should use a single indent (+4). The closing parenthesis ()) and return type are placed on their own line with no additional indent.

fun <T> Iterable<T>.joinToString(
    separator: CharSequence = ", ",
    prefix: CharSequence = "",
    postfix: CharSequence = ""
): String {
    // …
}


2.1.9. Expression Functions

When a function contains only a single expression it can be represented as an expression function.

override fun toString(): String {
    return "Hey"
}
override fun toString(): String = "Hey"

The only time an expression function should wrap to multiple lines is when it opens a block.

fun main() = runBlocking {
  // …
}

Otherwise, if an expression function grows to require wrapping, use a normal function body, a return declaration, and normal expression wrapping rules instead.


2.1.10. Properties

When a property initializer does not fit on a single line, break after the equals sign (=) and use an indent.

private val defaultCharset: Charset? =
        EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)

Properties declaring a get and/or set function should place each on their own line with a normal indent (+4). Format them using the same rules as functions.

var directory: File? = null
    set(value) {
        // …
    }

Read-only properties can use a shorter syntax which fits on a single line.

val defaultExtension: String get() = "kt"


2.2. Whitespace


2.2.1. Vertical

A single blank line appears:

  • Between consecutive members of a class: properties, constructors, functions, nested classes, etc.
    • Exception: A blank line between two consecutive properties (having no other code between them) is optional. Such blank lines are used as needed to create logical groupings of properties and associate properties with their backing property, if present.
    • Exception: Blank lines between enum constants are covered below.
  • Between statements, as needed to organize the code into logical subsections.
  • Optionally before the first statement in a function, before the first member of a class, or after the last member of a class (neither encouraged nor discouraged).

Multiple consecutive blank lines are permitted, but not encouraged or ever required.


2.2.2. Horizontal

Beyond where required by the language or other style rules, and apart from literals, comments, and KDoc, a single ASCII space also appears in the following places only:

  • Separating any reserved word, such as iffor, or catch from an open parenthesis (() that follows it on that line.
// WRONG!
for(i in 0..1) {
}
// Okay
for (i in 0..1) {
}
  • Separating any reserved word, such as else or catch, from a closing curly brace (}) that precedes it on that line.
// WRONG!
}else {
}
// Okay
} else {
}
  • Before any open curly brace ({).
// WRONG!
if (list.isEmpty()){
}
// Okay
if (list.isEmpty()) {
}
  • Before a colon (:) only if used in a class declaration for specifying a base class or interfaces, or when used in a where clause for generic constraints.
// WRONG!
class Foo: Runnable
// Okay
class Foo : Runnable
// WRONG
fun <T: Comparable> max(a: T, b: T)
// Okay
fun <T : Comparable> max(a: T, b: T)
// WRONG
fun <T> max(a: T, b: T) where T: Comparable<T>
// Okay
fun <T> max(a: T, b: T) where T : Comparable<T>
  • After a comma (,) or colon (:).
// WRONG!
val oneAndTwo = listOf(1,2)
// Okay
val oneAndTwo = listOf(1, 2)
// WRONG!
class Foo :Runnable
// Okay
class Foo : Runnable
  • On both sides of the double slash (//) that begins an end-of-line comment. Here, multiple spaces are allowed, but not required.
// WRONG!
var debugging = false//disabled by default
// Okay
var debugging = false // disabled by default
  • On both sides of any binary operator.
// WRONG!
val two = 1+1
// Okay
val two = 1 + 1

This also applies to the following “operator-like” symbols:

  • the arrow in a lambda expression (->).
// WRONG!
ints.map { value->value.toString() }
// Okay
ints.map { value -> value.toString() }

But not:

  • the two colons (::) of a member reference.
// WRONG!
val toString = Any :: toString
// Okay
val toString = Any::toString
  • the dot separator (.).
// WRONG
it . toString()
// Okay
it.toString()
  • the range operator (..).
// WRONG
 for (i in 1 .. 4) print(i)
 
 // Okay
 for (i in 1..4) print(i)

This rule is never interpreted as requiring or forbidding additional space at the start or end of a line; it addresses only interior space.


2.3. Specific Constructs


2.3.1. 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() = """¯\_(ツ)_/¯"""
    }
}

Since enum classes are classes, all other rules for formatting classes apply.


2.3.2. Annotations

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.

@JvmField @Volatile
var disposable: Disposable? = null

When only a single annotation without arguments is present, it may be placed on the same line as the declaration.

@Volatile var disposable: Disposable? = null

@Test fun selectAll() {
    // …
}

@[...] syntax may only be used with an explicit use-site target, and only for combining 2 or more annotations without arguments on a single line.

@field:[JvmStatic Volatile]
var disposable: Disposable? = null


2.3.3. Implicit Return/Property Types

If an expression function body or a property initializer is a scalar value or the return type can be clearly inferred from the body then it can be omitted.

override fun toString(): String = "Hey"
// becomes
override fun toString() = "Hey"
private val ICON: Icon = IconLoader.getIcon("/icons/kotlin.png")
// becomes
private val ICON = IconLoader.getIcon("/icons/kotlin.png")

When writing a library, retain the explicit type declaration when it is part of the public API.


2.4. Naming

Identifiers use only ASCII letters and digits, and, in a small number of cases noted below, underscores. Thus each valid identifier name is matched by the regular expression \w+.

Special prefixes or suffixes, like those seen in the examples name_mNames_name, and kName, are not used except in the case of backing properties.


2.4.1. Package Names

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


2.4.2. Type Names

Class names are written in PascalCase and are typically nouns or noun phrases. For example, Character or ImmutableList. Interface names may also be nouns or noun phrases (for example, List), but may sometimes be adjectives or adjective phrases instead (for example Readable).

Test classes are named starting with the name of the class they are testing, and ending with Test. For example, HashTest or HashIntegrationTest.


2.4.3. Function Names

Function names are written in camelCase and are typically verbs or verb phrases. For example, sendMessage or stop.

Underscores are permitted to appear in test function names to separate logical components of the name.

@Test fun pop_emptyStack() {
    // …
}

Functions annotated with @Composable that return Unit are PascalCased and named as nouns, as if they were types.

@Composable
fun NameTag(name: String) {
    // …
}


2.4.4. Constant Names

Constant names use UPPER_SNAKE_CASE: all uppercase letters, with words separated by underscores. But what is a constant, exactly?

Constants are val properties with no custom get function, whose contents are deeply immutable, and whose functions have no detectable side-effects. This includes immutable types and immutable collections of immutable types as well as scalars and string if marked as const. If any of an instance’s observable state can change, it is not a constant. Merely intending to never mutate the object is not enough.

const val NUMBER = 5
val NAMES = listOf("Alice", "Bob")
val AGES = mapOf("Alice" to 35, "Bob" to 32)
val COMMA_JOINER = Joiner.on(',') // Joiner is immutable
val EMPTY_ARRAY = arrayOf()

These names are typically nouns or noun phrases.

Constant values can only be defined inside of an object or as a top-level declaration. Values otherwise meeting the requirement of a constant but defined inside of a class must use a non-constant name.

Constants which are scalar values must use the const modifier.


2.4.5. Non-constant Names

Non-constant names are written in camelCase. These apply to instance properties, local properties, and parameter names.

val variable = "var"
val nonConstScalar = "non-const"
val mutableCollection: MutableSet = HashSet()
val mutableElements = listOf(mutableInstance)
val mutableValues = mapOf("Alice" to mutableInstance, "Bob" to mutableInstance2)
val logger = Logger.getLogger(MyClass::class.java.name)
val nonEmptyArray = arrayOf("these", "can", "change")

These names are typically nouns or noun phrases.


2.4.6. Backing Properties

When a backing property is needed, its name should exactly match that of the real property except prefixed with an underscore.

private var _table: Map? = null

val table: Map
    get() {
        if (_table == null) {
            _table = HashMap()
        }
        return _table ?: throw AssertionError()
    }


2.4.7. Type Variable Names

Each type variable is named in one of two styles:

  • A single capital letter, optionally followed by a single numeral (such as ETXT2).
  • A name in the form used for classes, followed by the capital letter T (such as RequestTFooBarT).


2.4.8. Camel Case

Sometimes there is more than one reasonable way to convert an English phrase into camel case, such as when acronyms or unusual constructs like “IPv6” or “iOS” are present. To improve predictability, use the following scheme.

Beginning with the prose form of the name:

  1. Convert the phrase to plain ASCII and remove any apostrophes. For example, “Müller’s algorithm” might become “Muellers algorithm”.
  2. Divide this result into words, splitting on spaces and any remaining punctuation (typically hyphens). Recommended: if any word already has a conventional camel-case appearance in common usage, split this into its constituent parts (e.g., “AdWords” becomes “ad words”). Note that a word such as “iOS” is not really in camel case per se; it defies any convention, so this recommendation does not apply.
  3. Now lowercase everything (including acronyms), then do one of the following:
    • Uppercase the first character of each word to yield pascal case.
    • Uppercase the first character of each word except the first to yield camel case.
  4. Finally, join all the words into a single identifier.

We note that the casing of the original words is almost entirely disregarded.

Prose formCorrectIncorrect
“XML Http Request”XmlHttpRequestXMLHTTPRequest
“new customer ID”newCustomerIdnewCustomerID
“inner stopwatch”innerStopwatchinnerStopWatch
“supports IPv6 on iOS”supportsIpv6OnIossupportsIPv6OnIOS
“YouTube importer”YouTubeImporterYoutubeImporter*

(* Acceptable, but not recommended.)


2.5. Documentation


2.5.1. Formatting

The basic formatting of KDoc blocks is seen in this example:

/**
 * Multiple lines of KDoc text are written here,
 * wrapped normally…
 */
fun method(arg: String) {
    // …
}

…or in this single-line example:

/** An especially short bit of KDoc. */

The basic form is always acceptable. The single-line form may be substituted when the entirety of the KDoc block (including comment markers) can fit on a single line. Note that this only applies when there are no block tags such as @return.


2.5.2. Paragraphs

One blank line—that is, a line containing only the aligned leading asterisk (*)—appears between paragraphs, and before the group of block tags if present.


2.5.3. Block Tags

Any of the standard “block tags” that are used appear in the order @constructor@receiver@param@property@return@throws@see, and these never appear with an empty description. When a block tag doesn’t fit on a single line, continuation lines are indented 4 spaces from the position of the @.


2.5.4. Summary Fragment

Each KDoc block begins with a brief summary fragment. This fragment is very important: it is the only part of the text that appears in certain contexts such as class and method indexes.

This is a fragment–a noun phrase or verb phrase, not a complete sentence. It does not begin with “A `Foo` is a...“, or “This method returns...“, nor does it have to form a complete imperative sentence like “Save the record.“. However, the fragment is capitalized and punctuated as if it were a complete sentence.


2.5.5. Usage

At the minimum, KDoc is present for every public type, and every public or protected member of such a type, with a few exceptions noted below.


2.5.5.1. Exception: Self-explanatory Functions

KDoc is optional for “simple, obvious” functions like getFoo and properties like foo, in cases where there really and truly is nothing else worthwhile to say but “Returns the foo”.

It is not appropriate to cite this exception to justify omitting relevant information that a typical reader might need to know. For example, for a function named getCanonicalName or property named canonicalName, don’t omit its documentation (with the rationale that it would say only /** Returns the canonical name. */) if a typical reader may have no idea what the term “canonical name” means!

2.5.5.2. Exception: Overrides

KDoc is not always present on a method that overrides a supertype method.

That’s all about in this article.


Conclusion

In this article, We understood about Kotlin Style Guide for Android application development. This article served as the complete definition of Google’s Android coding standards for source code in the Kotlin Programming Language. We discussed about Source code and formatting style guideline standard for Kotlin which is used in android application development.

Thanks for reading ! I hope you enjoyed and learned about Kotlin Style Guide concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find Other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – How To Apply Common Kotlin Patterns In Android Application ?

Hello Readers, CoolMonkTechie heartily welcomes you in this article (How To Apply Common Kotlin Patterns In Android Application ?)

In this article, We will learn how to apply common Kotlin patterns in Android apps. This article will focus on some of the most useful aspects of the Kotlin language when developing for Android.

A famous quote about learning is :

” Anyone who stops learning is old, whether at twenty or eighty. Anyone who keeps learning stays young. The greatest thing in life is to keep your mind young. “

So Let’s begin.


Work with fragments

In this sections, we use Fragment examples to highlight some of Kotlin’s best features as below:


Inheritance

We can declare a class in Kotlin with the class keyword. In the following example, LoginFragment is a subclass of Fragment. We can indicate inheritance by using the : operator between the subclass and its parent:

class LoginFragment : Fragment()

In this class declaration, LoginFragment is responsible for calling the constructor of its superclass, Fragment.

Within LoginFragment, we can override a number of lifecycle callbacks to respond to state changes in our Fragment. To override a function, use the override keyword, as shown in the following example:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

To reference a function in the parent class, use the super keyword, as shown in the following example:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}


Nullability and Initialization

In the previous examples, some of the parameters in the overridden methods have types suffixed with a question mark ?. This indicates that the arguments passed for these parameters can be null. Be sure to handle their nullability safely.

In Kotlin, we must initialize an object’s properties when declaring the object. This implies that when we obtain an instance of a class, we can immediately reference any of its accessible properties. The View objects in a Fragment, however, aren’t ready to be inflated until calling Fragment#onCreateView, so we need a way to defer property initialization for a View.

The lateinit lets us defer property initialization. When using lateinit, we should initialize our property as soon as possible.

The following example demonstrates using lateinit to assign View objects in onViewCreated:

class LoginFragment : Fragment() {

    private lateinit var usernameEditText: EditText
    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
    }

    ...
}

We aware that if we access a property before it is initialized, Kotlin throws an UninitializedPropertyAccessException.


SAM Conversion

We can listen for click events in Android by implementing the OnClickListener interface. Button objects contain a setOnClickListener() function that takes in an implementation of OnClickListener.

OnClickListener has a single abstract method, onClick(), that we must implement. Because setOnClickListener() always takes an OnClickListener as an argument, and because OnClickListener always has the same single abstract method, this implementation can be represented using an anonymous function in Kotlin. This process is known as Single Abstract Method conversion, or SAM conversion.

SAM conversion can make our code considerably cleaner. The following example shows how to use SAM conversion to implement an OnClickListener for a Button:

loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
    )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
    }
}

The code within the anonymous function passed to setOnClickListener() executes when a user clicks loginButton.


Companion Objects

The Companion objects provide a mechanism for defining variables or functions that linked conceptually to a type but do not tie to a particular object. Companion objects are similar to using Java’s static keyword for variables and methods.

In the following example, TAG is a String constant. We don’t need a unique instance of the String for each instance of LoginFragment, so we should define it in a companion object:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
    }
}

We could define TAG at the top level of the file, but the file might also have a large number of variables, functions, and classes that are also defined at the top level. Companion objects help to connect variables, functions, and the class definition without referring to any particular instance of that class.


Property Delegation

When initializing properties, we might repeat some of Android’s more common patterns, such as accessing a ViewModel within a Fragment. To avoid excess duplicate code, we can use Kotlin’s property delegation syntax.

private val viewModel: LoginViewModel by viewModels()

Property delegation provides a common implementation that we can reuse throughout our app. Android KTX provides some property delegates for us. viewModels, for example, retrieves a ViewModel that is scoped to the current Fragment.

Property delegation uses reflection, which adds some performance overhead. The tradeoff is a concise syntax that saves development time.


Nullability

Kotlin provides strict nullability rules that maintain type-safety throughout our app. In Kotlin, references to objects cannot contain null values by default. To assign a null value to a variable, we must declare a nullable variable type by adding ? to the end of the base type.

As an example, the following expression is illegal in Kotlin. name is of type String and isn’t nullable:

val name: String = null

To allow a null value, we must use a nullable String type, String?, as shown in the following example:

val name: String? = null


Interoperability

Kotlin’s strict rules make our code safer and more concise. These rules lower the chances of having a NullPointerException that would cause our app to crash. Moreover, they reduce the number of null checks, we need to make in our code.

Often, we must also call into non-Kotlin code when writing an Android app, as most Android APIs are written in the Java programming language.

Nullability is a key area where Java and Kotlin differ in behavior. Java is less strict with nullability syntax.

As an example, the Account class has a few properties, including a String property called name. Java does not have Kotlin’s rules around nullability, instead relying on optional nullability annotations to explicitly declare whether we can assign a null value.

Because the Android framework is written primarily in Java, we might run into this scenario when calling into APIs without nullability annotations.


Platform Types

If we use Kotlin to reference a unannotated name member that is defined in a Java Account class, the compiler doesn’t know whether the String maps to a String or a String? in Kotlin. This ambiguity is represented via a platform typeString!.

String! has no special meaning to the Kotlin compiler. String! can represent either a String or a String?, and the compiler lets us assign a value of either type. Note that we risk throwing a NullPointerException if we represent the type as a String and assign a null value.

To address this issue, we should use nullability annotations whenever we write code in Java. These annotations help both Java and Kotlin developers.

For example, here’s the Account class as it’s defined in Java:

public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

One of the member variables, accessId, is annotated with @Nullable, indicating that it can hold a null value. Kotlin would then treat accessId as a String?.

To indicate that a variable can never be null, use the @NonNull annotation:

public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}

In this scenario, name is considered a non-nullable String in Kotlin.

Nullability annotations are included in all new Android APIs and many existing Android APIs. Many Java libraries have added nullability annotations to better support both Kotlin and Java developers.


Handling nullability

If we are unsure about a Java type, we should consider it to be nullable. As an example, the name member of the Account class is not annotated, so we should assume it to be a nullable String.

If we want to trim name so that its value does not include leading or trailing whitespace, we can use Kotlin’s trim function. We can safely trim a String? in a few different ways. One of these ways is to use the not-null assertion operator!!, as shown in the following example:

val account = Account("name", "type")
val accountName = account.name!!.trim()

The !! operator treats everything on its left-hand side as non-null, so in this case, we are treating name as a non-null String. If the result of the expression to its left is null, then our app throws a NullPointerException. This operator is quick and easy, but it should be used sparingly, as it can reintroduce instances of NullPointerException into our code.

A safer choice is to use the safe-call operator?., as shown in the following example:

val account = Account("name", "type")
val accountName = account.name?.trim()

Using the safe-call operator, if name is non-null, then the result of name?.trim() is a name value without leading or trailing whitespace. If name is null, then the result of name?.trim() is null. This means that our app can never throw a NullPointerException when executing this statement.

While the safe-call operator saves us from a potential NullPointerException, it does pass a null value to the next statement. We can instead handle null cases immediately by using an Elvis operator (?:), as shown in the following example:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

If the result of the expression on the left-hand side of the Elvis operator is null, then the value on the right-hand side is assigned to accountName. This technique is useful for providing a default value that would otherwise be null.

We can also use the Elvis operator to return from a function early, as shown in the following example:

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}


Android API changes

Android APIs are becoming increasingly Kotlin-friendly. Many of Android’s most-common APIs, including AppCompatActivity and Fragment, contain nullability annotations, and certain calls like Fragment#getContext have more Kotlin-friendly alternatives.

For example, accessing the Context of a Fragment is almost always non-null, since most of the calls that we make in a Fragment occur while the Fragment is attached to an Activity (a subclass of Context). That said, Fragment#getContext does not always return a non-null value, as there are scenarios where a Fragment is not attached to an Activity. Thus, the return type of Fragment#getContext is nullable.

Since the Context returned from Fragment#getContext is nullable (and is annotated as @Nullable), we must treat it as a Context? in our Kotlin code. This means applying one of the previously-mentioned operators to address nullability before accessing its properties and functions. For some of these scenarios, Android contains alternative APIs that provide this convenience. Fragment#requireContext, for example, returns a non-null Context and throws an IllegalStateException if called when a Context would be null. This way, we can treat the resulting Context as non-null without the need for safe-call operators or workarounds.


Property Initialization

Properties in Kotlin are not initialized by default. They must be initialized when their enclosing class is initialized.

We can initialize properties in a few different ways. The following example shows how to initialize an index variable by assigning a value to it in the class declaration:

class LoginFragment : Fragment() {
    val index: Int = 12
}

This initialization can also be defined in an initializer block:

class LoginFragment : Fragment() {
    val index: Int

    init {
        index = 12
    }
}

In the examples above, index is initialized when a LoginFragment is constructed.

However, we might have some properties that can’t be initialized during object construction. For example, we might want to reference a View from within a Fragment, which means that the layout must be inflated first. Inflation does not occur when a Fragment is constructed. Instead, it’s inflated when calling Fragment#onCreateView.

One way to address this scenario is to declare the view as nullable and initialize it as soon as possible, as shown in the following example:

class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}

While this works as expected, we must now manage the nullability of the View whenever we reference it. A better solution is to use lateinit for View initialization, as shown in the following example:

class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}

The lateinit keyword allows us to avoid initializing a property when an object is constructed. If our property is referenced before being initialized, Kotlin throws an UninitializedPropertyAccessException, so be sure to initialize our property as soon as possible.

That’s all about in this article.

Related Other Articles / Posts


Conclusion

In this article, We understood about how to apply common Kotlin patterns in Android apps. This article demonstrated the most useful aspects of the Kotlin language like Working with Fragments and Nullability when developing for Android.

Thanks for reading ! I hope you enjoyed and learned about common Kotlin patterns concepts in Android. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find Other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Android – 5 Quick Valuable Concepts To Learn Kotlin Programming Language

Hello Readers, CoolMonkTechie heartily welcomes you in this article (5 Quick Valuable Concepts To Learn Kotlin Programming Language).

In this article, We will learn about Valuable Concepts To Learn Kotlin Programming Language. At Google I/O 2019, Google announced that Android development will be increasingly Kotlin-first, and we’ve stood by that commitment. Kotlin is an expressive and concise programming language that reduces common code errors and easily integrates into existing apps. If we’re looking to build an Android app, we recommend starting with Kotlin to take advantage of its best-in-class features. This article shows the kotlin fundamental for android application development

For understanding the basic fundamental concepts of kotlin, we will explore the below topics one by one:

  • Variable declaration
  • Conditionals
  • Functions
  • Classes
  • Interoperability

A famous quote about learning is :

” Develop a passion for learning. If you do, you will never cease to grow.”

So Let’s begin.


1. Variable declaration

Kotlin uses two different keywords to declare variables: val and var.

  • Use val for a variable whose value never changes. We can’t reassign a value to a variable that was declared using val.
  • Use var for a variable whose value can change.

In the example below, count is a variable of type Int that is assigned an initial value of 10:

var count: Int = 10

Int is a type that represents an integer, one of the many numerical types that can be represented in Kotlin. Similar to other languages, we can also use ByteShortLongFloat, and Double depending on our numerical data.

The var keyword means that we can reassign values to count as needed. For example, we can change the value of count from 10 to 15:

var count: Int = 10
count = 15

Some values are not meant to be changed, though. Consider a String called languageName. If we want to ensure that languageName always holds a value of “Kotlin”, then we can declare languageName using the val keyword:

val languageName: String = "Kotlin"

These keywords allow us to be explicit about what can be changed. Use them to our advantage as needed. If a variable reference must be reassignable, then declare it as a var. Otherwise, use val.


Type inference

Continuing the previous example, when we assign an initial value to languageName, the Kotlin compiler can infer the type based off of the type of the assigned value.

Since the value of "Kotlin" is of type String, the compiler infers that languageName is also a String. Note that Kotlin is a statically-typed language. This means that the type is resolved at compile time and never changes.

In the following example, languageName is inferred as a String, so we can’t call any functions that aren’t part of the String class:

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() is a function that can only be called on variables of type String. Because the Kotlin compiler has inferred languageName as a String, we can safely call toUpperCase()inc(), however, is an Int operator function, so it can’t be called on a String. Kotlin’s approach to type inference gives us both conciseness and type-safety.


Null safety

In some languages, a reference type variable can be declared without providing an initial explicit value. In these cases, the variables usually contain a null value. Kotlin variables can’t hold null values by default. This means that the following snippet is invalid:

// Fails to compile
val languageName: String = null

For a variable to hold a null value, it must be of a nullable type. We can specify a variable as being nullable by suffixing its type with ?, as shown in the following example:

val languageName: String? = null

With a String? type, we can assign either a String value or null to languageName.

We must handle nullable variables carefully or risk a dreaded NullPointerException. In Java, for example, if we attempt to invoke a method on a null value, our program crashes.

Kotlin provides a number of mechanisms for safely working with nullable variables.


2. Conditionals

Kotlin features several mechanisms for implementing conditional logic. The most common of these is an if-else statement. If an expression wrapped in parentheses next to an if keyword evaluates to true, then code within that branch (i.e. the immediately-following code that is wrapped in curly braces) is executed. Otherwise, the code within the else branch is executed.

Example : if / else

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

We can represent multiple conditions using else if. This lets us represent more granular, complex logic within a single conditional statement, as shown in the following example:

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

Conditional Expressions

Conditional statements are useful for representing stateful logic, but we may find that we repeat ourself when writing them. In the example above, we simply print a String in each branch. To avoid this repetition, Kotlin offers conditional expressions. The last example can be rewritten as follows:

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

Implicitly, each conditional branch returns the result of the expression on its final line, so we don’t need to use a return keyword. Because the result of all three branches is of type String, the result of the if-else expression is also of type String. In this example, answerString is assigned an initial value from the result of the if-else expression. Type inference can be used to omit the explicit type declaration for answerString, but it’s often a good idea to include it for clarity.

We aware that Kotlin does not include a traditional ternary operator, instead favoring the use of conditional expressions.

As the complexity of our conditional statement grows, we might consider replacing our if-else expression with a when expression, as shown in the following example:

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

Each branch in a when expression is represented by a condition, an arrow (->), and a result. If the condition on the left-hand side of the arrow evaluates to true, then the result of the expression on the right-hand side is returned. Note that execution does not fall through from one branch to the next. The code in the when expression example is functionally-equivalent to that in the previous example but is arguably easier to read.

Smart Casting

Kotlin’s conditionals highlight one of its more powerful features, smart casting. Rather than using the safe-call operator or the not-null assertion operator to work with nullable values, we can instead check if a variable contains a reference to a null value using a conditional statement, as shown in the following example:

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

Within the conditional branch, languageName may be treated as non-nullable. Kotlin is smart enough to recognize that the condition for executing the branch is that languageName does not hold a null value, so we do not have to treat languageName as nullable within that branch. This smart casting works for null checkstype checks, or any condition that satisfies a contract.


3. Functions

We can group one or more expressions into a function. Rather than repeating the same series of expressions each time that we need a result, we can wrap the expressions in a function and call that function instead.

To declare a function, use the fun keyword followed by the function name. Next, define the types of inputs that our function takes, if any, and declare the type of output that it returns. A function’s body is where we define expressions that are called when our function is invoked.

Building on previous examples, here’s a complete Kotlin function:

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

The function in the example above has the name generateAnswerString. It doesn’t take any input. It outputs a result of type String. To call a function, use its name, followed by the invocation operator (()). In the example below, the answerString variable is initialized with the result from generateAnswerString().

val answerString = generateAnswerString()

Functions can take arguments as input, as shown in the following example:

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

When declaring a function, we can specify any number of arguments and their types. In the example above, generateAnswerString() takes one argument named countThreshold of type Int. Within the function, we can refer to the argument by using its name.

When calling this function, we must include an argument within the function call’s parentheses:

val answerString = generateAnswerString(42)


Simplifying function declarations

generateAnswerString() is a fairly simple function. The function declares a variable and then immediately returns. When the result of a single expression is returned from a function, we can skip declaring a local variable by directly returning the result of the if-else expression contained in the function, as shown in the following example:

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

We can also replace the return keyword with the assignment operator:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }


Anonymous functions

Not every function needs a name. Some functions are more directly identified by their inputs and outputs. These functions are called anonymous functions. We can keep a reference to an anonymous function, using this reference to call the anonymous function later. We can also pass the reference around our application, as with other reference types.

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

Like named functions, anonymous functions can contain any number of expressions. The returned value of the function is the result of the final expression.

In the example above, stringLengthFunc contains a reference to an anonymous function that takes a String as input and returns the length of the input String as output of type Int. For that reason, the function’s type is denoted as (String) -> Int. This code does not invoke the function, however. To retrieve the result of the function, we must invoke it with like we would a named function. We must supply a String when calling stringLengthFunc, as shown in the following example:

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")


Higher-order functions

A function can take another function as an argument. Functions that use other functions as arguments are called Higher-order functions. This pattern is useful for communicating between components in the same way that we might use a callback interface in Java.

Here’s an example of a higher-order function:

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

The stringMapper() function takes a String along with a function that derives an Int value from a String that we pass into it.

We can call stringMapper() by passing a String and a function that satisfies the other input parameter, namely a function that takes a String as input and outputs an Int, as shown in the following example:

stringMapper("Android", { input ->
    input.length
})

If the anonymous function is the last parameter defined on a function, we can pass it outside of the parentheses used to invoke the function, as shown in the following example:

stringMapper("Android") { input ->
    input.length
}

Anonymous functions can be found throughout the Kotlin standard library. 


4. Classes

All of the types mentioned so far are built into the Kotlin programming language. If we would like to add our own custom type, we can define a class using the class keyword, as shown in the following example:

class Car


Properties

Classes represent state using properties. A property is a class-level variable that can include a getter, a setter, and a backing field.

Since a car needs wheels to drive, we can add a list of Wheel objects as a property of Car, as shown in the following example:

class Car {
    val wheels = listOf<Wheel>()
}

Note that wheels is a public val, meaning that wheels can be accessed from outside of the Car class, and it can’t be reassigned. If we want to obtain an instance of Car, we must first call its constructor. From there, we can access any of its accessible properties.

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

If we want to customize our wheels, we can define a custom constructor that specifies how our class properties are initialized:

class Car(val wheels: List<Wheel>)

In the example above, the class constructor takes a List<Wheel> as a constructor argument and uses that argument to initialize its wheels property.


Class functions and encapsulation

Classes use functions to model behavior. Functions can modify state, helping us to expose only the data that we wish to expose. This access control is part of a larger object-oriented concept known as encapsulation.

In the following example, the doorLock property is kept private from anything outside of the Car class. To unlock the car, we must call the unlockDoor() function passing in a valid key, as shown in the following example:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

If we would like to customize how a property is referenced, we can provide a custom getter and setter. For example, if we would like to expose a property’s getter while restricting access to its setter, we can designate that setter as private:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

With a combination of properties and functions, we can create classes that model all types of objects.


5. Interoperability

One of Kotlin’s most important features is its fluid interoperability with Java. Because Kotlin code compiles down to JVM bytecode, our Kotlin code can call directly into Java code and vice-versa. This means that we can leverage existing Java libraries directly from Kotlin. Furthermore, the majority of Android APIs are written in Java, and we can call them directly from Kotlin.

That’s all about in this article.


Conclusion

In this article, We understood about Valuable Concepts To Learn Kotlin Programming Language. Kotlin is a flexible, pragmatic language with growing support and momentum. We discussed about Kotlin fundamental like variable declaration, function, conditions and classes concepts which is widely used by Android developers everywhere for application development. This article showed the basic Kotlin fundamental concepts for android application development

Thanks for reading ! I hope you enjoyed and learned about the basic fundamental concepts of Kotlin for android development. Reading is one thing, but the only way to master it is to do it yourself.

Please follow and subscribe to the blog and support us in any way possible. Also like and share the article with others for spread valuable knowledge.

You can find Other articles of CoolMonkTechie as below link :

You can also follow official website and tutorials of Android as below links :

If you have any comments, questions, or think I missed something, feel free to leave them below in the comment box.

Thanks again Reading. HAPPY READING !!???

Exit mobile version