Kotlin is a Modern language which is fully interoperable with Java (and JavaScript). It targets the JVM, Android, JavaScript and Native.
Features:
- Statically typed
- Cross-platform
- General-purpose
- Concise
- Safe
- Interoperable
Official website : kotlinlang.org
Try Kotlin here : play.kotlinlang.org
- No need of
;
to break statements (unlike Java). - Types are non-null by default (unlike Java). ? marks a type as nullable. String? (nullable string)
- Two types of variable -
var
(mutable) andval
(immutable - like final in java). - Kotlin has type inferences (unlike Java). Inference is also supported by functions.
- Kotlin
when
is similar to switch in Java. - Comments are
// single line comment
and/* multi line comment */
(similar to Java). - You do not need to match your file name to your class name (unlike Java).
- One file may have multiple classes or interfaces
- Functions can be created outside classes (unlike Java). So, no need to put functions as static members of classes like done in Java.
- Kotlin supports string templates (unlike Java).
"My name is $firstName $lastName"
for variables and"${person.age} is ${10 * 4}"
for expressions. - Kotlin has immutable (only read functions) collections like
listOf()
,setOf()
,mapOf()
- Mutable (read & write) collections like
mutableListOf()
/arrayListOf()
,mutableSetOf()
/hashSetOf()
,mutableMapOf()
/hashMapOf()
- We have similar convieniene function for arrays called
arrayOf()
Unit
type is absense of any type (corresponds to thevoid
type in Java)==
for data comparison (objects with same data) |===
for reference comparison (exact same object)
as | break | class | continue | do | else |
false | for | fun | if | in | interface |
is | null | object | package | return | super |
this | throw | true | try | typealias | typeof |
val | var | when | while | Β | Β |
- Two keywords for variable declaration - var and val.
- Use var when the variable value is to be modified and val where the variable value will not change after first assigned.
- val variable must be initialized at declaration.
- Unlike Java or C#, you declare the type of a variable after the name, e.g.
var firstName : String
- Number primitive types are as follows: Double, Float, Long, Int, Short, Byte. There is no automatic conversion between types. You have to explicitly convert them.
- More primitive types: Char, String, Boolean.
- All variable declarations in Kotlin must be initialized.
In Kotlin you have to decide whether a variable can be assigned null or not. This applies to both primitives or class types. A nullable variable is marked by assigning ? after the type, e.g. var firstName: String?
You can assign a value from not-nullable to nullable type without any problem.
fun main() {
val firstname: String = "Tom"
var firstname1: String = "John"
var firstname2 = "Harry"
var lastname1: String = ""
var lastname2: String? = "Thomson"
var greetings: String? = null
firstname1 = "Hardy"
greetings = "Hello"
println(firstname1)
println("Hi $firstname2")
println(greetings + " " + firstname + " " + lastname2)
println("$greetings $firstname $lastname2")
}
fun main(args : Array<String>) {
val firstName : String = "Adam"
val name : String? = firstName
print("$name")
}
The other way around though requires that you declare that this nullable variable does not contain null at the point of assignment with !! operator (which pretty much declares : "I am sure this nullable variable is not null at this point")
fun main(args : Array<String>) {
val name : String? = "Adam"
val firstName : String = name!!
print("$firstName")
}
Kotlin is pretty smart about inferring what type a variable is, whether it is primitives or class. This is similar to the var keyword in C#.
fun main(args : Array<String>) {
val firstName = "Adam"
val middle = 'c'
val lastName = "Brown"
val age = 15
println("$firstName $middle $lastNameis $age")
}
Kotlin if statement is similar to other languages
if(surname != null) { println(surnamename) } else {}
val greetinToPrint = if(greeting != null) greeting else "Hi"
println(greetinToPrint)
fun main(args : Array<String>) {
val total = 10
if (total > 10){
println("$total is greater than 10")
}else if (total > 5){
println("$total is greater than 5")
}else{
println("$total is less than 6")
}
}
val a = 12
val b = 5
println("Enter operator either +, -, * or /")
val operator = readLine() // used for input
when (operator) {
"+" -> println("$a + $b = ${a + b}")
"-" -> println("$a - $b = ${a - b}")
"*" -> println("$a * $b = ${a * b}")
"/" -> println("$a / $b = ${a / b}")
else -> println("$operator is invalid")
}
var greeting: String? = null
val greetingToPrint = when(greeting) {
null -> "Hi"
else -> greeting
}
println(greetingToPrint)
val things = arrayOf("Kotlin", "Coding", "Instagram")
println(things.size + ", " + things[0] + ", " + things.get(0))
for(thing in things) { println(thing) } // normal for loop
things.forEach { println(it) } // forEach is a more functional/lambda approach
it
is the default name for each element of the array that is passed in this lambda function.
We can rename it
to anything, for eg. thing
things.forEach { thing -> println(thing) } // we lose index by this method
things.forEachIndexed { index, thing -> println("$thing is at index $index") } // way to use index
if, when and forEach can also be used as expression
By default, a collection is immutable, i.e., new values cannot be added or subtrated once it is created
List syntax is similar to arrays (shown above), but lists have more functions (after dot)
val things = listOf("Kotlin", "Coding", "Instagram") // listOf is used to create a immutable list
println(things.size + ", " + things[0] + ", " + things.get(0))
things.forEach { thing -> println(thing) }
val things = mutableListOf("Kotlin", "Coding", "Instagram") // mutableListOf is used to create a mutable list
things.add("Youtube"); // we have more functions like add in a mutable list
val map = mapOf(1 to "a", 2 to "b", 3 to "c") // mapOf - immutable map | "to" used to create pair of key & value for map
map.forEach { key, value -> println("$key -> $value") } // map forEach returns both key & value
val map2 = mutableMapOf(1 to "a", 2 to "b", 3 to "c") // mutableMapOf is used to create a mutable map
map2.put(4, "d")
We are going to spend a considerable time in discussing function because it has many different forms and subtleties. Here is a list of facilities that Kotlin provides for functions
- Single expression function
- Optional parameter
- Positional argument and named argument
- Variable argument
- Function type
- Function literals
- Callable references
- Extension functions
- Infix function call
- Local function
- Closure
- Generic function
- Operator overloading
Below is an example of functions
fun getGreeting(): String { return "Hello Kotlin"}
fun main() {
println(getGreeting())
}
- Functions can exists on their own.
- It is marked by fun keyword.
- If a function returns value, you declare it after the function name.
englishGreeting()
is a single expression function.- A void function such as
greet()
returns Unit type but you are not required to declare it. - All parameters in a Kotlin function are read only. You are actually not allowed to mark it with either
val
orvar
keyword.
fun main(args : Array<String>) {
greet(englishGreeting())
greet(italianGreeting())
}
fun greet(msg : String){
println(msg)
}
fun englishGreeting() : String = "Hello world"
fun italianGreeting() : String{
return "bon giorno"
}
Idea behind Lambda syntax is that if you have a function and its only param is another param then you can omit the pranthesis altogether and you can pass that function in by specifying this open & close parenthesis
Higher order Functions are fun that either return a fun or take fun as param
Varargs & Spread Operator
fun sayHello5(greeting:String, vararg itemsToGreet:String) {
itemsToGreet.forEach { itemToGreet ->
println("$greeting $itemToGreet")
}
}
sayHello5("Hi")
sayHello5("Hi", "Kot", "Lin", "Code")
sayHello5("Hi", *things) // spread out the array
There is nothing wrong in including a collection param in your func however, functions in kt provide an additional funtionality that can satisfy this usecase & provide lil additional flexibility
we have spread operator which is *
for eg -> *things
This is a shorthand form in defining a function when you only have a single expression to be executed.
fun main(args : Array<String>) {
val res = add(1,1)
show("$res")
}
fun add(a : Int, b : Int) = a + b
fun show(msg : String) = println("$msg")
As you can see above, in a single expression function, the function return type is inferred. You can declare the return type if you want to such as below.
fun main(args : Array<String>) {
val res = add(1,1)
show("$res")
}
fun add(a : Int, b : Int) : Int = a + b
fun show(msg : String) : Unit = println("$msg")
Kotlin allows you to assign default values for your parameters, making them optional.
fun main(args : Array<String>) {
show()
show("Good morning")
}
fun show (msg : String = "Hello World"){
println("$msg")
}
If you are mixing mandatory parameter and optional parameter, the mandatory parameters must be listed first.
fun main(args : Array<String>) {
greet(firstName = "Frasensco", lastName = "Merini")
greet(lastName = "John", firstName = "Stamos")
greet("Borat", "Ismail")
greet("Crystal", lastName = "Stamos")
call("Xavier", age = 20, location = "Portugal")
}
fun greet(firstName : String, lastName : String){
println("Good morning $firstName $lastName")
}
fun call(name : String, location : String, age : Int){
println("Call $name who lives at $location and he is $age old")
}
Kotlin allows positional argument, named argument and the mix between the two. When you mix named and positional argument, you must start with positional argument.
Use the keyword vararg.
fun main(args : Array<String>) {
names("John", "Adam", "Joy")
}
fun names(vararg names : String){
for(n in names){
println("$n")
}
}
If vararg parameter is not the last parameter, named argument must be used to supply the function argument.
fun main(args : Array<String>) {
names("John", "Adam", "Joy", age = 20)
}
fun names(vararg names : String, age : Int){
for(n in names){
println("$n is $age old")
}
}
fun main(args : Array<String>) {
names("John", "Adam", "Joy")
}
fun names(vararg names : String){
println("Argument length is ${names.size}")
println("${names[0]}")
val nns : Array<String> = names
println("${nns[1]}")
}
Use the * operator in front of the array variable
fun main(args : Array<String>) {
val n = array("John", "Adam", "Joy")
names(*n)
}
fun names(vararg names : String){
println("Argument length is ${names.size}")
println("${names[0]}")
val nns : Array<String> = names
println("${nns[1]}")
}
fun main(args : Array<String>) {
val n = array("John", "Adam", "Joy")
fugitives(*n)
}
,
fun fugitives(vararg escapees: String){
names(*escapees)
}
fun names(vararg names : String){
println("Argument length is ${names.size}")
println("${names[0]}")
val nns : Array<String> = names
println("${nns[1]}")
}
Since vararg creates an array, you simply use the * operator to pass one vararg to another.
A function type is a type consisted of a function signature and function return type that are separated by -> operator. In its simplest form, it looks as follows:
() -> Unit
Above is a type for a function that takes no parameter and returns a Unit (void in other language parlance)
() -> String
Above is a type for a function that takes no parameter and return a String
(String) -> Unit
Above is a type for a function that takes a string and returns nothing.
(String, Float) -> Unit
Above is a type for a function that takes two parameters (String and Float) and returns nothing.
Because a function type is just a type, it means that you can assign it to a variable, you can pass it as a function argument and you can return it from a function.
val m = { (x : String) -> println("$x") }
val n : (String) -> Unit = { x -> println("$x") }
val o : (String) -> Unit = { (x : String) -> println("$x") }
fun main(args : Array<String>) {
m("good morning")
n("good morning")
o("good morning")
}
Above code is an example of function literals. All m
, n
and o
represent the same function.
Below is a function that returns a function type
fun main(args : Array<String>) {
val greet = greetingFrom("Cairo, Egypt")
greet("Brown")
}
fun greetingFrom(location : String) : (String) -> Unit{
return { name -> println ("Hello $name from $location")}
}
Below shows that you can specify a function type as an argument and supply it with function literal with corresponding function signature and return type.
fun evening(): String = "Good Evening"
fun main(args : Array<String>){
say({ "good morning"})
say { val msg = "good afternoon" msg }
say({evening()})
}
fun say(greet : () -> String){
println("${greet()}")
}
How about if you already have a function that you want to pass as a parameter? You prefix the function name with '::'
fun main(args : Array<String>) {
calcAndShow(10,10, ::add) //20
calcAndShow(10,10, ::multiply) /100
calcAndShow(10,19, { x, y -> x - y }) //-9
}
fun calcAndShow(a : Int, b : Int, func : (a : Int, b : Int) -> Int){
val result = func (a, b)
println("$result")
}
fun add(a : Int, b : Int) : Int = a + b
fun multiply (a : Int, b : Int) : Int = a * b
When you call a function which has a function type as the last argument, you can expand it by { }
fun main(args : Array<String>) {
val a = calculate(1) { x -> 10 + x } //11
val b = calculate(2) { x -> 20 * x } //40
println("a = $a, b = $b")
}
fun calculate(a : Int, calc : (Int) -> Int) : Int{
return calc(a)
}
Kotlin support Closure as highlighted by the example below
fun main(args : Array<String>) {
val total = add(1)(2)
println("Total value is $total")
}
fun add(a : Int) : (Int) -> Int{
return { x -> a + x }
}
You can declare a function inside a function. It will have access to the local variable at the parent function.
fun main(args : Array<String>){
accumulate()
}
fun accumulate(){
var i = 0
fun add(){
i++
}
for (i in 1..10){
add()
}
println("i is now $i")
}
//It prints "i is now 10"
Extension function enables a function to be accessed from the type function. It works in the form of type.function
Inside the function, the keyword this
refers to the instance.
For example
fun Int.show(){
println("This number is $this")
}
fun main(args : Array<String>){
3.show()
}
Above example shows how the Int
built in type has been enriched by show
extension function. Notice the use of this
keyword that refers to the 3
number.
Notice You can extend a function on a nullable type and it will be accessible for both nullable and non nullable type. The reverse though does not apply.
fun Int?.show(){
println("This number is $this")
}
fun Int.show2(){
println("This number is $this")
}
fun main(args : Array<String>){
var number : Int? = null
number.show()
5.show()
//number.show2() will not compile
}
val show = { Int.() -> println("This is number $this") }
val add = { Int.(number : Int) : Int ->
val now = this + number
now
}
fun main(args : Array<String>){
5.add(10).show()
}
Both show
and add
extension functions are expressed in literal format. Please notice that add
function returns an Int
.
fun main(args : Array<String>) {
val res = 1 add 2
println("$res")
}
fun Int.add (one : Int) : Int = this + one
If the extension function only takes one argument, you can call them in infix form (you drop the . between the type and the function). So instead of 1.add(2)
, you can call it in the form of 1 add 2
. This makes certain constructs looks natural (more like an operator than a function call) and especially useful in construction DSL in Kotlin.
vararg
parameter can also be naturally combined with a function type parameter.
fun main(args : Array<String>) {
names("John", "Adam", "Joy"){
name -> println ("$name")
}
}
fun names(vararg names : String, print : (String) -> Unit){
for(n in names){
print(n)
}
}
above code can also be expressed in this matter (using named argument)
fun main(args : Array<String>) {
names("John", "Adam", "Joy", print = {name -> println ("$name")})
}
fun names(vararg names : String, print : (String) -> Unit){
for(n in names){
print(n)
}
}
- In a class, property must be initialized or abstract
- An init block is a code that is run anytime an instance of this class is run
- We can have multiple init blocks that will be proessed in the order that is defined within class body
- We do not need getters/setters for property access
- Property getters/setters are automatially generated by the compiler - getters for val, getters & setters for var
- Visibility of classes, properties, methods, etc are public by default
- Visibility modifiers: public, internal (public within the module), private (within the file), protected (within class or subclasses)
- Interfaces can provide default implementations of methods
- We can provide properties in interfaces, but default value is not allowed
- We use override keyword to override methods & properties
- By default, classes are closed (like final in Java), i.e., it cannot be extended. We need to add open keyword to allow inheritence.
- Also, to override a property in inherited class, it must be marked open
fun main(args : Array<String>) {
class local (val x : Int)
val y = local(10)
println("${y.x}")
}
Above code is a sample of Local Class, one of many support that Kotlin has for Object Oriented Programming.
- enum class
- sealed class
- object class
- data class
- Abstract classes
- Primary constructor
- Delegation
- Generic classes
- Class objects
- Nested classes
- Local classes
- Object expressions
- Traits
- Anonymous Analyzer
- Anonymous Objects
Kotlin classes
Kotlin classes does not have:
- Static member (methods or properties)
- Secondary constructors
- No fields, just properties
class Person
fun main(args : Array<String>) {
val p = Person()
val name = javaClass<Person>().getSimpleName()
println("$name")
}
The class Person is as simple as you can get to declare a class
by default, a Kotlin class is final. So to make a class inheritable, you must you the keyword open in front of it
open class Person
class Hero : Person()
fun main(args : Array<String>) {
val name = javaClass<Person>().getSimpleName()
println("$name")
val name2 = javaClass<Hero>().getSimpleName()
println("$name2")
}
Kotlin has four visibilities:
- private
- protected
- internal
- public
If you do not declare a visibility modifier, it is assumed to be internal
visibility.
fun main(args : Array<String>) {
val x = Visibility()
}
class Visibility{
public var name : String = ""
private var age : Int = 0
protected var address : String = ""
internal var friends : String = ""
var status : String = "Single"
}
An empty class is off course useless. Let's add some properties to it so it can hold data
open class Person
class Hero : Person(){
public var name : String = ""
public var age : Int = 30
}
fun main(args : Array<String>) {
val h = Hero()
h.name = "Superman"
h.age = 30
println("${h.name} is ${h.age} years old")
}
Rule
- Every declared property must be initialized, without exception.
- a var property means it can be modified
- a val property is a constant
Unlike many other OO language, Kotlin only allows one single constructor
open class Person
class Hero (n: String, a : Int) : Person(){
public var name : String = n
public var age : Int = a
}
fun main(args : Array<String>) {
val h = Hero("Superman", 30)
println("${h.name} is ${h.age} years old")
}
As you can see, the constructor parameter n and a are being used to initialized their respective properties.
Data classes are kotlins way of providing concise immutable data types... it is going to generate equals hashcoded to string... they will be considered equal if data they contain is equal... also, they give us effective copy construcors... x.copy()
sayHello1()
sayHello2()
fun sayHello1(): Unit { println("Hello")}
fun sayHello2() { println("Hello")}
fun getGreeting(): String = "Hello Kotlin" //single expression function
fun getGreeting() = "Hello Kotlin" //also, single expression function
fun sayHello3(itemToGreet: String) {
val msg = "Hello " + itemToGreet // or val msg = "Hello $itemToGreet"
println(msg )
}
fun sayHello3(itemToGreet: String) { println("Hello $itemToGreet")}
fun sayHello3(itemToGreet: String) = println("Hello $itemToGreet")
fun sayHello3(greeting:String, itemToGreet: String) = println("$greeting $itemToGreet")
fun sayHello4(greeting:String, itemsToGreet: List<String>) {
itemsToGreet.forEach { itemToGreet ->
println("$greeting $itemToGreet")
}
}
sayHello4("Hi", things2)
// varargs, named arguments, default param values
sayHello4("Hi", listOf())
fun greetPerson(greeting:String, name: String) = println("$greeting $name")
greetPerson(greeting:"hi", name:"John")
greetPerson(greeting = "hi", name = "John") // named arguments syntax
greetPerson(name = "John", greeting = "hi") // any order possible, but must take both arguments; can also be used forr varargs
fun greetPerson2(greeting:String = "Hello", name: String = "Kotlin") = println("$greeting $name") // default param values
greetPerson2(name = "John")
//This helps us replicate builder pattern without writing getters, seetings, private coonstructors, etc
class Person // constructor not required
class Person constructor() // empty primary construtor
class Person() // empty primary construtor
---
val person = Person() // new keyword not required
class Person2(firstName:String, lastName:String)
---
val person = Person("John", "Thomson")
class Person2(_firstName:String, _lastName:String) { // primary contrutor
val firstName: String // properties
val lastName: String
init {
firstName = _firstName
lastName = _lastName
}
}
or
class Person2(_firstName:String, _lastName:String) { // primary contrutor
val firstName: String = _firstName
val lastName: String = _lastName
}
or
class Person2(val firstName:String, val lastName:String) // primary contrutor
val person = Person2("John", "Thomson")
println(person.lastName)
class Person2(val firstName:String, val lastName:String) {
init { println("init 1") }
construtor(): this("Peter", "Parker") { // secondary constructor with calling primary constructor
println("seconndary constructor")
}
init { println("init 2") }
}
or
class Person2(val firstName:String = "Peter", val lastName:String = "Parker")
val person = Person2() // output init1 init2 seconndary constructor
class Person2(val firstName:String = "Peter", val lastName:String = "Parker") {
var nickName: String? = null
}
person.nickName = "Shades"
class Person2(val firstName:String = "Peter", val lastName:String = "Parker") {
var nickName: String? = null
set(value) { // overriding setter
field = value;
}
get() { // overriding getter
println("returned value is $field")
return field;
}
fun printInfo() {
val nick = if(nikName != null) nickName else "no nickname"
val nick = nickName ?: "no nickname" // same as above using elvis operator
}
}
person.printInfo()
interface PersonInfoProvider
class BasicInfoProvider: PersonInfoProvider
interface PersonInfoProvider {
fun printInfo(person: Person)
}
abstract class BasicInfoProvider: PersonInfoProvider
class BasicInfoProvider2: PersonInfoProvider {
override fun printInfo(person: Person) {
// super.printInfo() // not compulsory in this case
}
}
val provider = BasicInfoProvider2()
class D: interface A, interface B, interface C
if(infoProvider is SessionInfoProvider) ...
else ...
if(infoProvider !is SessionInfoProvider) ...
else ...
if(infoProvider is SessionInfoProvider) {
(infoProvider as SessionInfoProvider).getSessionId // manual type casting
infoProvider.getSessionId // smart casting
}
else ...
class Fancy: BasicInfoProvider() // inheritance
val provider = object : PersonInfoProvider { // object express anonymous inner class; for eg. can be used for click listener
...
new fun
}
suppose we want to create factory to create entity - we can use companion object
a companion object is scoped to an instance of another class
class Entity private construtor(val id: String) {
companion object { // can be rename like "comapanion object Factory"
fun create() = Entity("id")
}
}
val entity = Entity.Companion.create() // same as Entity.create(), writing COmpanion is not required
val entity = Entity.Factory.create() // suppose we have named it Factory
companion objects are like any other class, an implement interface or inherit or have properties or methods
object declaration is a convenient way of creating threaad safe singletons within kt
object EntityFacctory {
fun create() = Entity("id")
}
class Entity (val id: String) {
}
enum class EntityType {
EASY, MEDIUM, HARD
}
val id = UUID.randomID().toString()
sealed classes allow us too define related class hierarhies... for eg. can be used where result is "success or failure" or "easy, medium or hard"
with sealed classes we can have different propeerties (key diff bw enum & sealed)... compiler will use smart casting
sealed class Entity() {
data class Easy(val id: String, val name: String): Entity() // data classes
data class Medium(val id: String, val name: String): Entity()
data class Hard(val id: String, val name: String, val multiplier: Float): Entity()
}
fun Entity.Medium.printInfo() { // extension function
...
}
fun printFilteredStrings(list: List<String>, predicate: (String) -> Boolean) { // higher order funstion
list.forEach {
if(predicate(it)) {
println(it)
}
}
}
val list = listOf("Kotlin", "Java", "C++", "Javascript")
printFilteredStrings(list, {it.startsWith("K")}) // we have passed our lambda withing parenthesis
printFilteredStrings(list) { // we can take use of lambda syntax, same as above
it.startsWith("K")
}
val list = listOf("Kotlin", "Java", "C++", "Javascript", null, null)
list
.filterNotNull()
.take(3) // or takeLast(3) to take that many elements
.filter {
it.startsWith("J")
}
.map {
it.length
}
.forEach {
println(it)
}
output: 4 or 10 (for last 3)
list
.filterNotNull()
.associate {it t it.length}
.forEach {
println("${it.value}, ${it.key}")
}
val map = list
.filterNotNull()
.associate {it t it.length}
the kt standard lib also provides a variety of fun to help us pull out individual elements from a collection
val language = list.first() // or .last()
val language = list.filterNotNull().find{it.startsWith("Java")} // or .findLast {}
strings in kt have a useful fun called orEmpty
val language = list.filterNotNull().find{it.startsWith("foo")}.orEmpty() // to default collection to empty instead of null
kt has 1st class supp for fun, inluding fun types and higher order fun... and kt standard lib builds upon those tools and provides a rich set of fun operator for us to use... this allows us to build powerful functional chains to transform our data and make complex workflows much simpler
model data
Kotlin proj default
--
top level variables and functions & var and fun associated with class
--
toString(), hashCode(), ...
we have extension function or extension properties on existing class
higher order funtions & functional data type
invoke() method....
functional types...
these types of functions will be useful in click & event listeners etc
kotlin standard library
βοΈ Course Contents βοΈ
β¨οΈ (0:00:50β) Create Your First Kotlin Project
β¨οΈ (0:04:23β) Hello World
β¨οΈ (0:06:33β) Working With Variables
β¨οΈ (0:11:04β) Type System
β¨οΈ (0:15:00β) Basic Control Flow
β¨οΈ (0:21:31β) Basic Kotlin Functions
β¨οΈ (0:27:12β) Function Parameters
β¨οΈ (0:32:52β) Arrays
β¨οΈ (0:35:28β) Iterating with forEach
β¨οΈ (0:41:17β) Lists
β¨οΈ (0:42:47β) Maps
β¨οΈ (0:45:05β) Mutable vs Immutable Collections
β¨οΈ (0:49:24β) Vararg Parameters
β¨οΈ (0:54:21β) Named Arguments
β¨οΈ (0:56:26β) Default Parameter Values
β¨οΈ (1:00:27β) Create A Simple Class
β¨οΈ (1:03:35β) Adding Class Properties
β¨οΈ (1:05:15β) Class Init Block
β¨οΈ (1:06:40β) Accessing Class Properties
β¨οΈ (1:07:32β) Primary Constructor Properties
β¨οΈ (1:08:17β) Secondary Constructors
β¨οΈ (1:09:50β) Working With Multiple Init Blocks
β¨οΈ (1:11:30β) Default Property Values
β¨οΈ (1:11:59β) Properties With Custom Getters/Setters
β¨οΈ (1:16:52β) Class Methods
β¨οΈ (1:20:12β) Visibility Modifiers - Public/Private/Protected/Public
β¨οΈ (1:22:30β) Interfaces
β¨οΈ (1:24:21β) Abstract Classes
β¨οΈ (1:26:13β) Implementing An Interface
β¨οΈ (1:26:35β) Overriding Methods
β¨οΈ (1:28:30β) Default Interface Methods
β¨οΈ (1:29:30β) Interface Properties
β¨οΈ (1:31:40β) Implementing Multiple Interfaces
β¨οΈ (1:32:57β) Type Checking And Smart Casts
β¨οΈ (1:36:18β) Inheritance
β¨οΈ (1:43:07β) Object Expressions
β¨οΈ (1:45:06β) Companion Objects
β¨οΈ (1:49:51β) Object Declarations
β¨οΈ (1:52:41β) Enum Classes
β¨οΈ (1:58:16β) Sealed Classes
β¨οΈ (2:00:07β) Data Classes
β¨οΈ (2:12:25β) Extension Functions/Properties
β¨οΈ (2:16:40β) Higher-Order Functions
β¨οΈ (2:29:07β) Using The Kotlin Standard Library
private lateinit var // because late initialization, private so that it can be referenced
viewholder provides access to all views of one elemenet of recycler view
kotlin.math//
companion object are singleton varible defined constant //similar to static in java
we an access its memebers directly through the ontaining class
.shuffled()
data class
!!
interface//
delegate
color shades
setup recyclerview//
let