Introduction to Kotlin

Kotlin is a modern, cross-platform, and easy to learn general purpose programming language. Learn more about it here!

Kotlin thumbnail
Kotlin Logo

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

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 or Elegant and Asynchronous or Functional or Scripting 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 to java 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 and var
  • 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 to final in Java – any attempt to change or reassign a value to variables defined using val 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 means val 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 keybinding Alt + 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 system PATH. The bin 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 and kotlin tool and assuming you named your Kotlin source file first.kt, we can run the following command to run Kotlin source code using the java 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 the void type of Java.
  • You can use Unit to specify that you’re not returning anything useful. Kotlin will infer the type as Unit 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 and var 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 specifies Int 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, the return 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, for name and msg.
  • 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.
  • PARAMETERPLACEHOLDER (This means a placeholder belongs to the function naming and be used in the function body).
  • ARGUMENTACTUAL 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 and spread 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 the vararg 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 a Boolean 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 the Int class, this refers to the int that isPrime could be called on. For example, 3.isPrime() would have 3 as this.
  • 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 as public, private, protected, and internal.
  • 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. The init 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 as private but will be accessible/visible to subclasses too.
  • internal -  Any client inside this module who sees the declaring class, sees its internal members. The internal modifier doesn’t have a direct bytecode representation. It’s handled by the Kotlin compiler using some naming conventions without posing any runtime overhead.
class MainClass() {
    val a = 1 // by default a variable is public
    private val b = 2
    protected val c = 3 
    internal val d = 4
}

class SubClass: MainClass() {
    // a, c, d are visible 
    // b is not visibile
}
modifiers.kt

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(), and toString() methods. In addition, it provides a copy() 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 componentcomponent1(), 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:

class OuterClass() {
    class NestedClass() {
    	class AnotherNestedClass() {
            class AndSoOn() {...}
        }
    }
}
nested.kt
  • 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:

Getting Started with Jetpack Compose
Jetpack Compose is a modern UI toolkit by Google which replaces traditional XML layouts with a way to do it using Kotlin, which simplifies and accelerates UI development with less and maintainable code, allowing us to make powerful and beautiful apps in lesser time.

Run on many platforms with:

Kotlin Multiplatform | Kotlin

Wanna spice up things and try something new? Have a look at Flutter here:

Going from Native Android to Flutter
Flutter is an open-source UI toolkit created by Google to build and ship beautiful yet powerful applications across platforms with a single codebase. It supports platforms like iOS, macOS, Android, Web, Linux, Windows, and Fuchsia. I have been doing native Android development since 2019, starting w…

Wondering about AOSP as a platform? Say no more:

Introduction to AOSP
The Android Open Source Project is an open source development project managed by Google, and anyone is welcome to evaluate and add code and fixes to the source code. Android System Architecture Let’s first talk about the layers of an Android device’s architecture before delving into the build sys…