Introduction to Kotlin
Kotlin is a modern, cross-platform, and easy to learn general purpose programming language. It's a statically typed memory safe programming language designed to interoperate with Java and JVM versions of the Kotlin standard library.
Table of contents
- Getting Started
- Why Kotlin?
- Features of Kotlin
- Coroutines
- Variable Declaration
- Setting Up IDE
- Running Your First Kotlin Program
- String Templates
- Creating Functions
- Defining parameters
- Function with body
- Arguments
- The spread operator
- Extension Function
- Introduction to Classes
- Visibility Modifiers
- Companion Object
- Data Class
- Interface
- Inner and Nested Classes
- Sealed Class
- Decorators
- Delegation
- Lambdas in Kotlin
- Kotlin vs Java
- Running as a Script
- Transpiling to JavaScript
- Conclusion
- Bonus Links
Getting started
Kotlin -- It's a modern general-purpose programming language developed by JetBrains. Every programmer who uses Kotlin not just likes it, but loves it. Kotlin first appeared around 10 years ago on July 22, 2011, by Andrey Breslav and the team of developers behind the language at JetBrains. But just what is the reason for such love and affection? That's what we'll have a look at today!
Write Better Android Apps faster with Kotlin
- Android Developers
Why Kotlin?
Kotlin is like a blend of the best of many different languages–– C++, C#, Java, JavaScript, Python, Scala, and so on which makes it easy to learn and use, with lesser verbosity than many mainstream languages. Kotlin is one of the few languages that can be used for server-side code as ktor, cross-platform, and native android development. It got its biggest uptick at Google I/O 2017 when google announced it as the official language for android development. Though an endorsement from Google is certainly significant, there are more reasons to love Kotlin.
As said earlier Kotlin is one of the few languages that can be used for server-side code, cross-platform, and native android development and it's designed to interoperate with Java and JVM. It also allows transpiling into JavaScript. Kotlin/Native supports targeting several platforms, including iOS, macOS, Linux, Windows, and WebAssembly, to compile your source code to native binaries which means Kotlin is capable of doing full stack development efficiently.
Features of Kotlin
- Though syntactically different, Kotlin is semantically similar to Java, making it easy for Java programmers to adapt.
- Without actually inheriting from a class or use design patterns such as Decorator, Kotlin provides the ability to extend it's classes with new functionality it's called Extension function
- Kotlin is a Multi-Paradigm programming language, what does it mean? It means Kotlin treats the programmer as a adult and lets us pick the best approach
Object-Oriented
orElegant and Asynchronous
orFunctional
orScripting
or even intermix it.
- Just like Java you can create classes and object oriented code but with much less boilerplate code.
- Kotlin provides exceptional support for both the imperative and functional style of programming.
- Statically typed languages offer compile time safety but Kotlin takes it even further and prevents any common errors that might occur in other statically typed languages.
- Kotlin distinguishes between nullable and non-nullable types. It also has very strong type inference like Scala and Haskell.
- Just like
javac
compiles Java source code tojava bytecode
,kotlinc-jvm
compiles Kotlin source code to JVM bytecode to run on any virtual machine and you can target the specific version of the virtual machine that you’d like to use for deployment. This means you may also intermix Kotlin code with Java code—no legacy code has to be left behind. - You can also transpile it to Javascript to run on servers or use Ktor, a Kotlin native library for a web backend.
- Using Kotlin/Native, you can compile code to native binary to run on targeted platforms and to WebAssembly to run within browsers.
- And you can't talk about features and not mention coroutines. Kotlin coroutines makes it a lot easier to create high-performance asynchronous code. It comes in handy when creating server-side, desktop, or mobile applications.
- Finally, Kotlin is a great choice for Android development since it’s an official language for that platform.
Coroutines
Kotlin implements coroutines as first class element. To understand coroutines properly you must understand functions and threads.
- Function is a sequence of instructions that are bundled together to achieve a specific outcome. Functions are good alternative to having a repeating block of code.
- A thread describes in which context the sequence of the instructions should be executed.
- Coroutine makes multi-threading easy
- Let's take a real world example – an Android app that makes a network call and performs a UI update. If the network call happens on the main thread, your app will be stuck for as long as the network call goes on, and in worst case it might even crash.
class sampleClass {
val scope = CoroutineScope()
fun coroutinesAreFun() {
scope.launch() {...}
}
}
- Above is a sample of how to work with coroutines
- You can create a coroutine scope by calling
CoroutineScope()
and assigning it to a variable. - Then you can call the
launch()
method to start a coroutine thread and run your logic. - If you launch a scope you might also want to clean or stop it:
class sampleClass {
val scope = CoroutineScope()
fun coroutineisfun() {...}
fun cleanUp() {
scope.cancel()
}
}
- Now you can just call the
cleanUp()
function whenever you want to stop a coroutine scope - To sum it up coroutines allow Kotlin developers to multitask efficiently.
- If you want to dig deeper into coroutines I would recommend learning about Coroutine Context, Dispatcher, job and more.
Variable Declaration
- Unlike java where you place type before the name of the variable, in Kotlin you place the name of the variable first, then a colon, followed by the type. This places a greater emphasis on variable names than variable types.
val srtVariable: String = ""
var intVariable: Int = 0
- Kotlin uses something called type inference, which means you don't explicitly need to mention the type of the variable. For example:
val strVariable = "some text"
Kotlin will automatically figure out the type of the variable to be a string. You can't change a type of variable once assigned. For example:
var strVariable = "some text"
strVariable = 4 // not allowed
- Now let's talk about
val
andvar
val
is used to define an immutable variable which means the value is an constant and the value can't be modified.val
is similar tofinal
in Java – any attempt to change or reassign a value to variables defined usingval
will result in a compilation error.- Whereas
var
is used to define mutable variable which means you can change the value of the variable when needed
var strVariable = "some text"
print(strVariable) // some text
strVariable = "some other text"
print(strVariable)// some other variable
val
however only makes the variable or reference a constant, not the object referenced. This meansval
only guarantees immutability of the reference and doesn’t prevent the object from changing.
Setting up IDE
Well when it comes to choosing a development environment, it completely depends on taste. If you want a lightweight code editor VSCode is the most popular option. You can also go with Sublime text. Code editors would be a better choice if you are on a low end PC, but if your has anything above intel i3 3rd
gen with minimum of 4GB
of ram, you can go with an IDE.
IDE
stands for Integrated Development Environment -- it contains all the stuff you will ever need with additional option to add extensions to improve your coding efficiency.- IntelliJ Idea is an official IDE made by Jetbrains. It comes in 2 variants, Community Edition (Recommended) and Ultimate Edition.
- Community Edition is free and open source and is recommended for a beginner since it has all the necessary features needed for development.
- Ultimate Edition on the other hand is made and designed for web and Enterprise development with features needed for an enterprise -- it comes with a 30 day free trial but you can get a one year license for free if you have github education available.
- Here we will use the Community Edition
- When you first launch it, you will be greeted with:
- To start a new Kotlin project, click on "new project" and you will be greeted with this little window:
- If you don't have that option, click plugins, and then search for Kotlin:
- While creating a new project, click
console application
and click finish:
- This is the project structure and the source code is available in
src/main/kotlin/Main.kt
- Congratulations, you're all set to run your first Kotlin program!
Running Your First Kotlin Program
- Project comes with a
main()
function that prints Hello World!
fun main(args: Array<String>) {
println("Hello World!")
// Try adding program arguments at Run/Debug configuration
println("Program arguments: ${args.joinToString()}")
}
- To make use of Kotlin's function types and make it simple:
fun main() = println("Hello World!")
Hello World!
- To run the
Main.kt
file you can use keybindingAlt + Shift + f10
or you can press green play button.
- An IDE comes with with a Kotlin compiler bundled into it and we can run our program with just one click.
- It's good to know how to compile and run code on our own. For that you would need a Kotlin compiler installed on your PC. You can download it from here.
- Now add the
bin
directory to the systemPATH
. Thebin
directory contains the scripts needed to compile and run Kotlin on Windows, macOS, and Linux. - You can verify the installation by running running
kotlinc -version
. If you get the below output (the version might be different), it means Kotlin is successfully installed.
info: kotlinc-jvm 1.5.30 (JRE 16.0.1+9-24)
- Since you can convert Kotlin code to java bytecode and run it with a JRE, having a JRE or JDK installed is also recommended.
- So, let's take it for a ride -- there are multiple ways to run Kotlin you can either convert to bytecode and run it with
java
or run it as a script. Kotlin is also capable of transpiling to JavaScript. - We will later run as script and transpile to Javascript. For now, we will run using
java
andkotlin
tool and assuming you named your Kotlin source filefirst.kt
, we can run the following command to run Kotlin source code using thejava
tool:
kotlinc-jvm first.kt -d first.jar
- `You'll now have a
jar
file which you can run this way:
java -jar first.jar
String Templates
If you come from some other programming language, you might know we can concatenate strings with variables using the +
operator which makes code hard to maintain. String templates solve that problem by providing an elegant solution.
- Within double quotes if you just wanna include a variable you can do it by adding the
$
prefix to it. - If the expression is more complex than a simple variable, wrap the expression with
${}
.
val firstName = "Jayesh"
val lastName = "Seth"
val output = "My full name is $firstName $lastName"
println(output)
output:
My full name is Jayesh Seth
- Example of complex expressions:
val price = 12.25
val taxRate = 0.08
val output = "The amount $price after tax comes to ${price * (1 + taxRate)}"
println(output)
Output:
The amount 12.25 after tax comes to $13.23
- Next comes
Escape Character
-- dealing with escape characters makes the code messy. - With Kotlin we use raw strings which start and end with three double quotes to solve this issue.
- With a regular string which starts and ends with a single double quote, we can’t place a variety of characters, like new line or a double quote without using the escape character
\
. Even a simple case of this can be unpleasant to read, like this one:
val simpleString = "Your full name is \"$firstName $lastName\""
- We can do the above using escaped string, which is more readable and less cluttered:
val simpleString = """Your full name is, "$firstName $lastName""""
- For multi-line strings, Java programmers are used the infamous
+
operator to concatenate multi-line strings. Kotlin simplifies it using the same three double quotes.
val name = "Eve"
val memo = """Dear $name, a quick reminder about the
party we have scheduled next Tuesday at
the 'Low Ceremony Cafe' at Noon. | Please plan to..."""
println(memo)
output:
Dear Eve, a quick reminder about the party we have scheduled next Tuesday at the 'Low Ceremony Cafe' at Noon. | Please plan to...
Creating Functions
Kotlin doesn’t insist that you create classes for everything. No one gets praise for duplicating code, but to reuse doesn’t mean building a class hierarchy. Unlike Java where a class is the smallest reusable piece, in Kotlin both standalone functions and classes can be reused. Creating functions, and methods, in Kotlin is different from creating methods in Java.
- Kotlin follows the
KISS
principle.KISS
stands for “Keep it simple, stupid”. - Small functions should be simple to read and write.
- Here’s one of the shortest functions you can write in Kotlin.
fun myName() = "Jayesh"
println(myName())
output:
Jayesh
- The
=
operator is used instead of using the{ }
block syntax. - The
return
keyword isn’t allowed for single-expression functions, which are functions without a block body. - Return type and type inference:
myName()
returns a string , but we didn’t explicitly specify that. That’s because Kotlin can infer the return type of functions with a non-block body. - The return type inference happens at compile time. We can verify it by trying to assign it to another type:
fun myName() = "Jayesh"
val age: Int = myName() // ERROR
// type mismatch: inferred type is String but Int was expected
- Let’s modify the
myName()
function to explicitly specify the return type:
fun myName(): String = "Hello"
- The return type is prefixed with a
:
and goes right after the parameter list. - Kotlin favors expressions over statements. Based on that principle, functions must be treated as expressions instead of statements.
- Kotlin uses a special type called
Unit
that corresponds to thevoid
type of Java. - You can use
Unit
to specify that you’re not returning anything useful. Kotlin will infer the type asUnit
if the function isn’t returning anything. - That was nice, though we haven't taken any Parameters yet in functions.. so let's get going with defining Parameters!
Defining parameters
Languages like Haskell can dive into functions and infer the types of the parameters. Kotlin insists on specifying the types for parameters to functions and methods. Provide the type of the parameter right after the parameter’s name, separated by :
. Let’s change the myName()
function to take a parameter of String type.
fun myName(name: String) = "Hello $name"
println(myName("Jayesh"))
Hello Jayesh
- function declaration to specify the return type, function parameters, and an argument passed to the catch block.
- While the type of the parameter
name
is required, we may omit the return type if we want Kotlin to infer the return type. - If you have more than one parameter, list them comma separated within the parenthesis.
- As we saw earlier, we should prefer immutability over mutability. Kotlin forces you to choose between
val
andvar
when defining local variables. - We’ve seen really short functions so far. Let’s have a look at writing more complex functions.
Functions with body
When a function is small, we can separate the body of the function from the declaration using the =
operator. If the function is more complex than that, place the body in a block { }
and don’t use =
. You have to specify the return type for any function with a block body, otherwise, the return type is inferred as Unit
(void). Let's explain it with an example.
fun max(numbers: IntArray): Int {
var large = Int.MIN_VALUE
for (number in numbers) {
large = if (number > large) number else large
}
return large
}
println(max(intArrayOf(1, 5, 2, 12, 7, 33)))
Output: 33
- The
max()
function takes an array as a parameter, and specifiesInt
as the return type. In this case the return type isn’t optional — you must specify it since the body of the function is a block. Also, thereturn
keyword is required. Int.MIN_VALUE
− This is a constant holding the minimum value an int can have.
Arguments
Overloading of functions is common in Java and is the way to create functions that can take different number and type of arguments. That’s an option in Kotlin as well, but the default arguments feature is a simpler and better way to solve this problem.
fun myName(name: String, msg: String = "Hello") = "$msg $name"
println(myName("Jayesh"))
println(myName("Jayesh", "Howdy"))
Hello Jayesh
Howdy Jayesh
- The existing code that calls
myName()
with only one argument for name continues to work. - Any new call to
greet()
may pass either one argument, for name, or two arguments, forname
andmsg
. - In the first call, since
msg
isn’t provided a value, the default argument Hello is used. - In the second call, the given argument
Howdy
is used and the default argument is overridden. - Before all that, knowing what's the difference between arguments and parameters is very important.
- PARAMETER → PLACEHOLDER (This means a placeholder belongs to the function naming and be used in the function body).
- ARGUMENT → ACTUAL VALUE (This means an actual value which is passed by the function calling). (Source)
The spread operator
Sometimes, we may want to pass values that are in an array or list to a function with vararg
(Variable named argument). Even though the function may take a variable number of arguments, we can’t directly send an array or a list. This is where the spread operator comes in.
- The spread operator is denoted by the
*
symbol. Let's take an example. - Suppose you want to find the sum of an array of numbers. We can do it like:
fun sumOfNumbers(a: Int, b: Int, c: Int, d: Int) {
return a + b + c + d
}
println(sumOfNumbers(1, 2, 3, 4))
Output: 10
sumOfNumbers()
function takes 4 inputs and returns the sum of all inputs, simple.- But what if we don't want to hard code the number of inputs and want it to be any amount of arguments?
- That's where the
vararg
andspread
operators come in handy!
Note:
We could have used an int array and summed it up, but vararg is more readable.
fun sumOfNumbers(varag numbers: Int) {
val sum = 0
for (number in numbers) sum += numbers
return sum
}
val numbers = intArrayOf(1, 2, 3, 4, 5)
println(sumOfNumbers(*numbers))
Output: 15
- Now we can pass as many numbers as we'd like to
sumOfNumbers()
. - By placing a
*
in front of the argument, you’re asking Kotlin to spread the values in that array into discrete values for thevararg
parameter. - One more important thing to know about in functions is Extension Function.
Extension Function
Extension function is a simple but quite effective ability to add functionality to an existing function. For example, you can write new functions for a class from a third-party library that you can't modify. Such functions can be called in the usual way, as if they were methods of the original class.
val input = readLine().toInt()
if (input != null) {
if (input.isPrime()) {
println("$input is a prime number")
} else {
println("$input is not a prime number")
}
}
fun Int.isPrime():boolean {
for (i in 2 until this-1) {
if (this % i == 0) {
return false
}
return true
}
- In the above example,
isPrime()
is an extended function -- it returns aBoolean
value, whether the input is prime or not. this
- a Kotlin expression that refers to the context in question. In this case, since we're extending theInt
class,this
refers to the int thatisPrime
could be called on. For example,3.isPrime()
would have3
asthis
.- In a member of a class,
this
refers to the current object of that class. - In an extension function or a function literal with receiver
this
denotes the receiver parameter that is passed on the left-hand side of a dot.(source).
Introduction to Classes
Kotlin is determined to make your code fluent, concise, and expressive, whether you’re writing procedural, functional, or object-oriented code. You can write classes with less effort, fewer lines of code, and with greater speed and ease. You invoke class constructors like functions — there’s no new
keyword in Kotlin.
- Classes in Kotlin are declared using the keyword
class
.
class <class name> {}
- adding properties to our class is simple too:
class User(val name: String) {}
Note:
It's recommended to start the class name with an uppercase letter (PascalCase).
- Here's another example of a
User
class:
class User(val name: String, val age: Int)
val user = User("Jayesh", 19)
println(user.name)
println(user.age)
Output:
Jayesh
19
- Now let's have a look at
Access modifiers
. They're also called visibility modifiers. There are multiple access modifiers, such aspublic
,private
,protected
, andinternal
. - The properties and methods of a class are public by default in Kotlin.
In Kotlin, an outer class does not see private members of its inner classes.
- Kotlin classes also have an
init
block as a part of class. Theinit
block will execute immediately after the primary constructor. - The initializer blocks are executed in the same order as they appear in the class body.
Visibility Modifiers
public
- all classes and methods are public by default, which means any function or class can access it.private
- makes something private to the class. For example:
class User(private val name: String, ...)
makes name
private to User
class. name
will not be accessible outside of the class definition block.
protected
- It will have same visibility asprivate
but will be accessible/visible to subclasses too.internal
- Any client inside this module who sees the declaring class, sees itsinternal
members. Theinternal
modifier doesn’t have a direct bytecode representation. It’s handled by the Kotlin compiler using some naming conventions without posing any runtime overhead.
Companion object
Companion Object is similar to static
in Java. If a property or a method is needed at the class level and not on a specific instance of the class, we drop them into a companion object
block.
- In object-oriented languages class or instance of a class holds a state, but if we want an object to and share its value among all objects we use companion object for that.
- For example, imagine a use case where a UUID needs to be unique, and should only be created once and shared among the other instances.
- They’re singleton companions of classes.
- In addition, companion objects may implement interfaces and may extend from base classes, and thus are useful with code reuse as well.
Generic Class
Generic parameters are a great way to provide type safety.
We all know how Kotlin provides null safety -- the same way generic classes provide type safety. Often we create a class with specific data types which ends up being a lot of unnecessary code. For example, a book might have title
, author
, publisher
, and so on. Imagine creating separate classes for each of them. That's where generic classes comes in handy.
- All of them are lists, thus making a generic list class would make coding workflow much easier and readable.
- For example:
class PriorityPair(item1: Int, item2: Int) {
val first: Int
val second: Int
init {
if (item1 >= item2) {
first = item1
second = item2
} else {
first = item1
second = item2
}
}
override fun toString() = "${first}, ${second}"
}
fun main() {
println(PriorityPair(1,2))
}
Output: 2, 1
- Here we created a class named
PriorityPair
which compares two given integers. But what if we wanted to compare two given strings? - We can do it this way:
class PriorityPair<T: Comparable<T>>(item1: T, item2: T) {
val first: T
val second: T
init {
if (item1 >= item2) {
first = item1
second = item2
} else {
first = item2
second = item1
}
}
override fun toString() = "${first}, ${second}"
}
- Now we can compare two strings, ints, or any other type that is
Comparable
. Generic classes make code reusable, which is always a plus point.
Data Class
As the name says, a data class is a class that holds data rather than logic. The primary constructor is required to define at least one property, using val
or var
. You may add other properties or methods to the class within the body { }
, if you desire.
- For each data class Kotlin will automatically create the
equals()
,hashCode()
, andtoString()
methods. In addition, it provides acopy()
method to make a copy of an instance while providing updated values for select properties. - It also creates special methods that start with the word
component
—component1()
,component2()
, and so on — to access each property defined through the primary constructor. - An example of a data class would be:
data class Todo (
val id: Int
val name: String
val isCompleted: Boolean
)
- Creating an object from a data class is pretty simple too:
val todo = Todo(1, "task 1", false)
println(todo)
println("todo name: ${Todo.name}")
Output:
Todo(id=1, name=task 1, isCompleted=false)
todo name: task 1
- Destructuring a data class:
val (id, _, isCompleted) = todo1
println("Id: $id isCompleted: $isCompleted")
Output:
Id: 1 isCompleted: false
Inner and Nested Classes
Before getting started, it's important to understand what a nested class is -- multiple classes stacked inside each other.
For example:
- But how do we access them?
- Well, with nested classes, the outermost/topmost class is like a mother class. You can access nested classes from the mother class using the
.
operator, and then calling nested classes. For example:
fun main() {
println(OuterClass.NestedClass....)
}
- That brings up a question – when should inner classes be used as opposed to nested classes?
- When you want to access outer class members, you use inner classes, since a nested class cannot access outer class members.
- You can simply add the
inner
keyword to a class to make it an inner class, and access outer class members freely.
class OuterClass(val text: String) {
inner class NestedClass() {
fun nestedClass() {
println(text)
}
}
}
- However, we are now unable to directly call the class, so we have to do so by referencing it this way:
val outerClass = OuterClass("hey there!!")
println(outerClass.NestedClass())
Output:
hey there!!
Sealed Class
To learn about sealed class, let's first assume you are creating a subscription based application. In this case, you can have two kinds of users – a Pro user, or a Regular user. This can be handled by making use of sealed classes.
enum class SubscriptionApp() {
ProUser,
RegularUser
}
Decorators
Decorators are a design pattern that allow us to add behavior without altering the existing structure. In Kotlin, by default, all classes and functions are final and closed. This was done to enforce Effective Java Principles (Joshua Bloch), Thus enforcing you not to inherit the class but use wrapper patterns or extensions.
Let's understand this by implementing it:
interface Pizza {
fun toppings(): String
}
class Dominos: Pizza {
override fun toppings() = "Chicken Salami Pizza"
}
Here, we can use the Decorate by Composition
method, for which we would need an abstract class that will act as a composer or wrapper.
abstract class Chef {
(private val pizza: Pizza): Pizza {
override fun toppings(): String {
return pizza.toppings()
}
}
}
Now our decorator will extend our abstract Chef
class and will modify its toppings()
method according to our requirements:
class Jalapeno(pizza: Pizza): Chef(pizza) {
override fun toppings(): String {
return super.toppings() + toppingsAddJalapeno()
}
fun toppingsAddJalapeno(): String {
return "with Jalapeno"
}
}
Now we can finally create our delicious pizza:
fun main() {
val pizza = Jalapeno(Dominos())
val deliciousPizza = pizza.toppings()
print(deliciousPizza)
}
Output: Chicken Salami Pizza with Jalapeno
Now you might be wondering how an extension function is different from decorators. If you just want to extend functionality that the original creator didn't intend to add and you can't make changes to the base class, you should use extension functions. If you have access to the base class, decorators/wrappers are the way to go. Extension functions do not replace the decorator pattern. It should be avoided if possible.
Learn more about decorator patterns here.
Delegation
Before we understand delegation, it's important to understand what the word delegate
means.
entrust (a task or responsibility) to another person, typically one who is less senior than oneself.
Delegation is a design pattern that basically means passing a job from one object to another object. It can be done in any object-oriented language, but Kotlin supports it natively using the by
keyword. It can cooperate with mutable as well as static relationships between classes and interfaces.
Kotlin standard library has many useful built-in or standard delegates to make code more readable and efficient, such as:
Lazy
Observable
Vetoable
NotNull
Map
Learn more about them in depth here.
Kotlin vs Java
Kotlin and Java both can compile to Java bytecode and run on the JVM. Kotlin gained the majority of its popularity when Google announced it as the second official language for Android development in 2017 and primary supported language in 2019. A key difference between Kotlin and Java is that Kotlin supports both object-oriented and functional programming paradigms, whereas Java only supports the object-oriented paradigm which makes Kotlin more flexible, and according to modern standards, more readable and expresive. Also, one of the most helpful features Kotlin brings to the table is null safety – Java allows us to assign a null value to any variable, which means referencing it gives us the infamous Null Pointer Exception error!
With Kotlin on the other hand, trying to attribute null values to variables or objects by default will not compile. Kotlin also makes multi-threading simpler with coroutines. Now If you're an Android developer, knowing Kotlin is a must as its the primary supported language and also makes android development way easier. You can learn more about this comparison in Kotlin's offical docs here.
Conclusion
In conclusion, Kotlin is a really expressive and powerful programming language that also makes the developer experience better. It is the recommended language for Android development, and recently also Kotlin went cross-platform with Kotlin Multiplatform and Compose for Web and Desktop. So, the future for Kotlin looks promising.
Make sure to check out the bonus links below, and Happy Coding! :)
Bonus Links
Learn more about Compose:
Run on many platforms with:
Wanna spice up things and try something new? Have a look at Flutter here:
Wondering about AOSP as a platform? Say no more: