pathway_1_kotlin_fundamentals
Table of Contents
Pathway 1: Kotlin fundamentals
My conspectus of:
https://developer.android.com/courses/pathways/android-basics-compose-unit-2-pathway-1
1. Kotlin Fundamentals
… Optional video …
2. Write conditionals in Kotlin
if / elsestatements
fun main() { val trafficLightColor = "Black" if (trafficLightColor == "Red") { println("Stop") } else if (trafficLightColor == "Yellow") { println("Slow") } else if (trafficLightColor == "Green") { println("Go") } else { println("Invalid traffic-light color") } }
whenstatementswhenstatements are preferred when there are more than two branches to consider.
fun main() { val trafficLightColor = "Black" when (trafficLightColor) { "Red" -> println("Stop") "Yellow" -> println("Slow") "Green" -> println("Go") else -> println("Invalid traffic-light color") } }
whenwithout parameter
when { x.isOdd() -> print("x is odd") y.isEven() -> print("y is even") else -> print("x+y is odd") }
- comma (,) for multiple conditions
when (x) { 2, 3, 5, 7 -> println("x is a prime number between 1 and 10.") else -> println("x isn't a prime number between 1 and 10.") }
inkeyword for a range of conditions
when (x) { 2, 3, 5, 7 -> println("x is a prime number between 1 and 10.") in 1..10 -> println("x is a number between 1 and 10, but not a prime number.") else -> println("x isn't a prime number between 1 and 10.") }
iskeyword to check data type
when (x) { 2, 3, 5, 7 -> println("x is a prime number between 1 and 10.") in 1..10 -> println("x is a number between 1 and 10, but not a prime number.") is Int -> println("x is an integer number, but not between 1 and 10.") else -> println("x isn't an integer number.") }
- Use
if/elseandwhenas expressions- The syntax for conditionals as expressions is similar to statements, but the last line of bodies in each branch need to return a value or an expression, and the conditionals are assigned to a variable.
- If the bodies only contain a return value or expression, you can remove the curly braces to make the code more concise.
fun main() { val trafficLightColor = "Black" val message = if (trafficLightColor == "Red") "Stop" else if (trafficLightColor == "Yellow") "Slow" else if (trafficLightColor == "Green") "Go" else "Invalid traffic-light color" println(message) }
3. Use nullability in Kotlin
- In Kotlin, there's a distinction between nullable and non-nullable types:
- Nullable types are variables that can hold null.
- Non-null types are variables that can't hold null.
Stringtype is non-nullable (!)- To declare nullable variables in Kotlin, you need to add a
?operator to the end of the type.- For example, a
String?type can hold either a string or null, whereas aStringtype can only hold a string.
- Note: While you should use nullable variables for variables that can carry null, you should use non-nullable variables for variables that can never carry null because the access of nullable variables requires more complex handling.
?.safe call operator
fun main() { var favoriteActor: String? = null println(favoriteActor?.length) // => prints "null" }
!!not-null assertion operator- Unlike
?.safe-call operators, the use of a!!not-null assertion operator may result in aNullPointerExceptionerror being thrown if the nullable variable is indeed null. Thus, it should be done only when the variable is always non-nullable or proper exception handling is set in place. When not handled, exceptions cause runtime errors.
if/elseexpressions
fun main() { var favoriteActor: String? = "Sandra Oh" val lengthOfName = if (favoriteActor != null) { favoriteActor.length } else { 0 } println("The number of characters in your favorite actor's name is $lengthOfName.") }
?:Elvis operator- If the variable isn't null, the expression before the
?:Elvis operator executes. If the variable is null, the expression after the?:Elvis operator executes.
fun main() { var favoriteActor: String? = "Sandra Oh" val lengthOfName = favoriteActor?.length ?: 0 println("The number of characters in your favorite actor's name is $lengthOfName.") }
4. Use classes and objects in Kotlin
1. Before you begin
- Basic concepts of OOP:
- encapsulation
- abstraction
- inheritance
- polymorphism
2. Define a class
- A class consists of three major parts:
- properties
- methods
- constructors
class SmartDevice { // empty body }
3. Create an instance of a class
fun main() { val smartTvDevice = SmartDevice() }
4. Define class methods
class SmartDevice { fun turnOn() { println("Smart device is turned on.") } fun turnOff() { println("Smart device is turned off.") } } fun main() { val smartTvDevice = SmartDevice() smartTvDevice.turnOn() smartTvDevice.turnOff() }
5. Define class properties
- You define an immutable property with the
valkeyword and a mutable property with thevarkeyword.
- The full syntax to define a mutable property starts with the variable definition followed by the optional get() and set() functions.
var speakerVolume = 2 get() = field set(value) { field = value }
- When you don't define the getter and setter function for a property, the Kotlin compiler internally creates the functions.
- Warning: Don't use the property name to get or set a value. For example, in the
set()function, if you try to assign the value parameter to thespeakerVolumeproperty itself, the code enters an endless loop because the Kotlin runtime tries to update the value for thespeakerVolumeproperty, which triggers a call to the setter function repeatedly.
6. Define a constructor
Default constructor
class SmartDevice constructor() {
...
}
- Kotlin aims to be concise, so you can remove the constructor keyword if there are no annotations or visibility modifiers, which you learn about soon, on the constructor. You can also remove the parentheses if the constructor has no parameters as shown in this code snippet:
class SmartDevice {
...
}
Parameterized constructor
class SmartDevice(val name: String, val category: String) { var deviceStatus = "online" fun turnOn() { println("Smart device is turned on.") } fun turnOff() { println("Smart device is turned off.") } }
- There are two main types of constructors in Kotlin:
- Primary constructor
- A class can have only one primary constructor, which is defined as part of the class header. A primary constructor can be a default or parameterized constructor. The primary constructor doesn't have a body. That means that it can't contain any code.
- Secondary constructor
- A class can have multiple secondary constructors. You can define the secondary constructor with or without parameters. The secondary constructor can initialize the class and has a body, which can contain initialization logic. If the class has a primary constructor, each secondary constructor needs to initialize the primary constructor.
- The secondary constructor is enclosed in the body of the class and its syntax includes three parts:
- Secondary constructor declaration.
- The secondary constructor definition starts with the constructor keyword followed by parentheses. If applicable, the parentheses contain the parameters required by the secondary constructor.
- Primary constructor initialization.
- The initialization starts with a colon followed by the this keyword and a set of parentheses. If applicable, the parentheses contain the parameters required by the primary constructor.
- Secondary constructor body.
- Initialization of the primary constructor is followed by a set of curly braces, which contain the secondary constructor's body.
class SmartDevice(val name: String, val category: String) {
var deviceStatus = "online"
constructor(name: String, category: String, statusCode: Int) : this(name, category) {
deviceStatus = when (statusCode) {
0 -> "offline"
1 -> "online"
else -> "unknown"
}
}
...
}
7. Implement a relationship between classes
- In Kotlin, all the classes are final by default, which means that you can't extend them, so you have to define the relationships between them.
- Use the keyword
opento make a class extendable
open class SmartDevice(val name: String, val category: String) { ... }
- Extending:
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) { }
- The constructor definition for SmartTvDevice doesn't specify whether the properties are mutable or immutable (the constructor does not contain
val/varkeywords for parameters). This means that thedeviceNameanddeviceCategoryparameters are merely constructor parameters instead of class properties. You won't be able to use them in the class, but simply pass them to the superclass constructor.
- …
- Override superclass methods from subclasses
- with
overridekeyword
class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) { // ... override fun turnOn() { deviceStatus = "on" println( "$name is turned on. Speaker volume is set to $speakerVolume and channel number is " + "set to $channelNumber." ) } override fun turnOff() { deviceStatus = "off" println("$name turned off") } }
- Reuse superclass code in subclasses with the super keyword
class SmartLightDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) { // ... override fun turnOn() { super.turnOn() brightnessLevel = 2 println("$name turned on. The brightness level is $brightnessLevel.") } override fun turnOff() { super.turnOff() brightnessLevel = 0 println("Smart Light turned off") } }
- Override superclass properties from subclasses
open class SmartDevice(val name: String, val category: String) { var deviceStatus = "online" open val deviceType = "unknown" ... } class SmartTvDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) { override val deviceType = "Smart TV" ... } class SmartLightDevice(deviceName: String, deviceCategory: String) : SmartDevice(name = deviceName, category = deviceCategory) { override val deviceType = "Smart Light" ... }
8. Visibility modifiers
- Kotlin provides four visibility modifiers:
- public - default visibility modifier
- private
- protected
- internal - accessible in the same module.
- Note:
- A module is a collection of source files and build settings that let you divide your project into discrete units of functionality. Your project can have one or many modules. You can independently build, test, and debug each module.
- A package is like a directory or a folder that groups related classes, whereas a module provides a container for your app's source code, resource files, and app-level settings. A module can contain multiple packages.
- Visibility modifiers for constructors:
open class SmartDevice protected constructor (val name: String, val category: String) {
// ...
}
- Visibility modifiers for classes
internal open class SmartDevice(val name: String, val category: String) {
// ...
}
9. Define property delegates
- To create the delegate class for the
vartype, you need to implement theReadWritePropertyinterface. Similarly, you need to implement theReadOnlyPropertyinterface for thevaltype.
class RangeRegulator(
initialValue: Int,
private val minValue: Int,
private val maxValue: Int
) : ReadWriteProperty<Any?, Int> {
var fieldData = initialValue
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return fieldData
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in minValue..maxValue) {
fieldData = value
}
}
}
class SmartTvDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart TV"
private var speakerVolume by RangeRegulator(initialValue = 2, minValue = 0, maxValue = 100)
private var channelNumber by RangeRegulator(initialValue = 1, minValue = 0, maxValue = 200)
...
}
class SmartLightDevice(deviceName: String, deviceCategory: String) :
SmartDevice(name = deviceName, category = deviceCategory) {
override val deviceType = "Smart Light"
private var brightnessLevel by RangeRegulator(initialValue = 0, minValue = 0, maxValue = 100)
...
}
10. Test the solution
…
11. Try this challenge
…
12. Conclusion
Summary
- There are four main principles of OOP: encapsulation, abstraction, inheritance, and polymorphism.
- Classes are defined with the class keyword, and contain properties and methods.
- Properties are similar to variables except properties can have custom getters and setters.
- A constructor specifies how to instantiate objects of a class.
- You can omit the constructor keyword when you define a primary constructor.
- Inheritance makes it easier to reuse code.
- The IS-A relationship refers to inheritance.
- The HAS-A relationship refers to composition.
- Visibility modifiers play an important role in the achievement of encapsulation.
- Kotlin provides four visibility modifiers: the public, private, protected, and internal modifiers.
- A property delegate lets you reuse the getter and setter code in multiple classes.
5. Use function types and lambda expressions in Kotlin
1. Introduction
…
2. Watch the code-along video
…
3. Store a function in a variable
- Saving a function in a variable:
fun main() { val trickFunction = ::trick } fun trick() { println("No treats!") }
- Using lambda:
fun main() { val trickFunction = trick // prints "No treats!" trick() // prints "No treats!" again trickFunction() } val trick = { println("No treats!") }
4. Use functions as a data type
Use a function as a return type
val treat: () -> Unit = { println("Have a treat!") }
val trick = { println("No treats!") } val treat = { println("Have a treat!") } fun trickOrTreat(isTrick: Boolean): () -> Unit { if (isTrick) { return trick } else { return treat } }
Pass a function to another function as an argument
// the second parameter is a function, which has an Int parameter and returns String fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit { if (isTrick) { return trick } else { println(extraTreat(5)) return treat } } fun main() { val coins: (Int) -> String = { quantity -> "$quantity quarters" } val cupcake: (Int) -> String = { "Have a cupcake!" } val treatFunction = trickOrTreat(false, coins) val trickFunction = trickOrTreat(true, cupcake) treatFunction() trickFunction() }
Nullable function types
Like other data types, function types can be declared as nullable. In these cases, a variable could contain a function or it could be null.
To declare a function as nullable, surround the function type in parentheses followed by a ? symbol outside the ending parenthesis.
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit { if (isTrick) { return trick } else { if (extraTreat != null) { println(extraTreat(5)) } return treat } } fun main() { val coins: (Int) -> String = { quantity -> "$quantity quarters" } val treatFunction = trickOrTreat(false, coins) val trickFunction = trickOrTreat(true, null) treatFunction() trickFunction() }
5. Write lambda expressions with shorthand syntax
Omit parameter name
it is used as parameter name:
val coins: (Int) -> String = { "$it quarters" }
Pass a lambda expression directly into a function
fun main() { val treatFunction = trickOrTreat(false, { "$it quarters" }) val trickFunction = trickOrTreat(true, null) treatFunction() trickFunction() }
Use trailing lambda syntax
You can use another shorthand option to write lambdas when a function type is the last parameter of a function.
val treatFunction = trickOrTreat(false) { "$it quarters" }
6. Use the repeat() function
- When a function returns a function or takes a function as an argument, it's called a higher-order function.
- The repeat() function is a concise way to express a for loop with functions.
fun main() { val treatFunction = trickOrTreat(false) { "$it quarters" } val trickFunction = trickOrTreat(true, null) repeat(4) { treatFunction() } trickFunction() }
7.Conclusion
Summary
- Functions in Kotlin are first-class constructs and can be treated like data types.
- Lambda expressions provide a shorthand syntax to write functions.
- You can pass function types into other functions.
- You can return a function type from another function.
- A lambda expression returns the value of the last expression.
- If a parameter label is omitted in a lambda expression with a single parameter, it's referred to with the it identifier.
- Lambdas can be written inline without a variable name.
- If a function's last parameter is a function type, you can use trailing lambda syntax to move the lambda expression after the last parenthesis when you call a function.
- Higher-order functions are functions that take other functions as parameters or return a function.
- The repeat() function is a higher-order function that works similarly to a for loop.
6 Practice: Kotlin Fundamentals
…
7 What's next?
…
pathway_1_kotlin_fundamentals.txt · Last modified: by kalaschnikow
