====== 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 / else'' statements 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") } } * ''when'' statements * ''when'' statements 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") } } * ''when'' without 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.") } * ''in'' keyword 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.") } * ''is'' keyword 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/else'' and ''when'' as 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. * ''String'' type 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 a ''String'' type 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 a ''NullPointerException'' error 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/else'' expressions 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 ''val'' keyword and a mutable property with the ''var'' keyword. * 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 the ''speakerVolume'' property itself, the code enters an endless loop because the Kotlin runtime tries to update the value for the ''speakerVolume'' property, 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 ''open'' to 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'' / ''var'' keywords for parameters). This means that the ''deviceName'' and ''deviceCategory'' parameters 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 ''override'' keyword 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 ''var'' type, you need to implement the ''ReadWriteProperty'' interface. Similarly, you need to implement the ''ReadOnlyProperty'' interface for the ''val'' type. class RangeRegulator( initialValue: Int, private val minValue: Int, private val maxValue: Int ) : ReadWriteProperty { 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? ===== ...