Kotlin Roadmap for Android Development (Only Kotlin)

1. Setup

Objective

Set up the development environment to write and run Kotlin code for Android applications.

Steps

  1. Install Java Development Kit (JDK):
    • Kotlin compiles to JVM bytecode, requiring a compatible JDK (Java Development Kit).
    • Download and install the latest stable JDK (e.g., JDK 17 or higher) for your operating system from Oracle or OpenJDK.
    • Verify installation:

  2. Configure Kotlin in Android Studio:
    • Create a new Android project and select Kotlin as the programming language.
    • Alternatively, add Kotlin support to an existing project by adding the Kotlin plugin in build.gradle:
      gradle
      // Project-level build.gradle
      buildscript {
      dependencies {
      classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0"
      }
      }
      // App-level build.gradle
      plugins {
      id 'kotlin-android'
      }
  3. Set Up an Emulator or Physical Device:
    • Configure an Android Virtual Device (AVD) in Android Studio or connect a physical Android device for testing.

Key Notes

  • Ensure your system meets Android Studio’s requirements (e.g., 8GB RAM minimum, 16GB recommended).
  • Keep the Kotlin plugin updated in Android Studio for the latest features and bug fixes.

2. Basics of Kotlin

Objective

Understand Kotlin’s fundamental concepts, syntax, and features, with a focus on Android development.

2.1 Data Types

Primitive (Built-in) Data Types

Kotlin provides the following primitive types, which are similar to Java but optimized for null safety:

  • Byte: 8-bit signed integer (-128 to 127)
  • Short: 16-bit signed integer (-32768 to 32767)
  • Int: 32-bit signed integer (-2^31 to 2^31-1)
  • Long: 64-bit signed integer (-2^63 to 2^63-1)
  • Float: 32-bit floating-point number
  • Double: 64-bit floating-point number
  • Char: 16-bit Unicode character
  • Boolean: true or false

Non-Primitive (User-Generated) Data Types

  • String: Immutable sequence of characters
  • Array: Fixed-size collection of elements
  • List, Set, Map: Kotlin collection types
  • Classes and Objects: User-defined types

Code Example

kotlin
val myInt: Int = 42
val myDouble: Double = 3.14
val myChar: Char = 'A'
val myString: String = "Hello, Kotlin!"
val myArray: Array<Int> = arrayOf(1, 2, 3)
val myList: List<String> = listOf("Apple", "Banana", "Orange")

2.2 Variables

Kotlin uses val and var for variable declarations, promoting immutability and type inference.

  • val: Immutable (read-only) variable, cannot be reassigned after initialization.
  • var: Mutable variable, can be reassigned.
  • Type Inference: Kotlin infers the type if not explicitly specified.
  • Local Variables: Declared within a function or block, scoped to that block.
  • Instance Variables: Declared inside a class, associated with an instance.
  • No Static Variables: Use companion object for static-like behavior.

Code Example

kotlin
class Example {
var instanceVar: String = "I am an instance variable"
companion object {
val staticLikeVar: String = "I am like a static variable"
}
}
fun main() {
val immutable = 10 // Cannot be reassigned
var mutable = 20
mutable = 30 // Valid
// immutable = 15 // Error: Val cannot be reassigned
println(Example.staticLikeVar) // Accessing companion object variable
}

2.3 Null Safety

Kotlin eliminates null pointer exceptions (NPEs) by design through its type system.

  • Nullable Types: Use ? to declare a variable that can hold null.
  • Safe Call Operator (?. ): Access properties or methods only if the object is non-null.
  • Elvis Operator (?: ): Provide a default value if the expression is null.
  • Non-null Assertion Operator (!!): Assert that a value is non-null (use cautiously, as it can throw NPE).

Code Example

kotlin
fun main() {
val nullableString: String? = null
// Safe Call Operator
println(nullableString?.length) // Prints: null
// Elvis Operator
val length = nullableString?.length ?: 0
println(length) // Prints: 0
// Non-null Assertion Operator (use with caution)
val nonNullString: String = "Kotlin"
println(nonNullString!!.length) // Prints: 6
}

2.4 Control Flow

If-Else Statement

Kotlin’s if-else can be used as an expression, returning a value.

kotlin
val max = if (a > b) a else b

When Statement

Similar to Java’s switch, but more powerful. It supports ranges, multiple conditions, and can be used as an expression.

kotlin
fun describe(number: Int): String {
return when (number) {
1 -> "One"
in 2..5 -> "Between 2 and 5"
else -> "Other"
}
}

Loops

  • For Loop: Iterates over ranges, collections, or anything with an iterator.
  • While Loop: Executes as long as a condition is true.
  • Do-While Loop: Executes at least once, then checks the condition.
  • Break and Continue with Labeled Loops: Use labels to control flow in nested loops.

Code Example

kotlin
fun main() {
// For Loop
for (i in 1..5) {
println(i) // Prints: 1, 2, 3, 4, 5
}
// While Loop
var i = 0
while (i < 3) {
println(i) // Prints: 0, 1, 2
i++
}
// Labeled Loop
outer@ for (i in 1..3) {
for (j in 1..3) {
if (j == 2) break@outer
println("i: $i, j: $j")
}
}
}

2.5 Operators

Unary Operators

  • +, -, !, ++, --

Binary Operators

  • Arithmetic: +, -, *, /, %
  • Comparison: ==, !=, <, >, <=, >=
  • Logical: &&, ||
  • Assignment: =, +=, -=, *=, /=, %=

Code Example

kotlin
fun main() {
val a = 10
val b = 5
println(a + b) // Prints: 15
println(a > b) // Prints: true
println(a == b) // Prints: false
}

2.6 Methods (Functions)

Function as Expression

Functions can return values directly, making them concise.

kotlin
fun square(num: Int): Int = num * num

Named Parameters, Default Parameters, and Vararg

  • Named Parameters: Improve readability by specifying parameter names.
  • Default Parameters: Provide default values for parameters.
  • Vararg: Allow a variable number of arguments.

Code Example

kotlin
fun greet(name: String = "Guest", prefix: String = "Hello"): String {
return "$prefix, $name!"
}
fun sum(vararg numbers: Int): Int {
return numbers.sum()
}
fun main() {
println(greet(name = "Alice", prefix = "Hi")) // Prints: Hi, Alice!
println(greet()) // Prints: Hello, Guest!
println(sum(1, 2, 3, 4)) // Prints: 10
}

Lambda and Higher-Order Functions

  • Lambda: Anonymous functions defined using {}.
  • Higher-Order Functions: Functions that take other functions as parameters or return them.

Code Example

kotlin
fun main() {
val lambda: (Int) -> Int = { x -> x * 2 }
println(lambda(5)) // Prints: 10
fun operateOnNumber(num: Int, operation: (Int) -> Int): Int {
return operation(num)
}
println(operateOnNumber(5, lambda)) // Prints: 10
}

Extension Functions

Allow adding functionality to existing classes without modifying their source code.

Code Example

kotlin
fun String.addExclamation(): String {
return "$this!"
}
fun main() {
println("Hello".addExclamation()) // Prints: Hello!
}

3. Practical Use Cases for Android Development

Use Case 1: Android UI with Kotlin

Use Kotlin to handle button clicks and update UI elements.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.myButton)
val textView: TextView = findViewById(R.id.myTextView)
button.setOnClickListener {
textView.text = "Button Clicked!"
}
}
}

Use Case 2: Null Safety in Android

Handle nullable views safely to prevent crashes.

kotlin
class MainActivity : AppCompatActivity() {
private var textView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.myTextView)
textView?.text = "Safe Text Update"
}
}

Use Case 3: Using Collections

Filter and manipulate data for a RecyclerView.

kotlin
val fruits = listOf("Apple", "Banana", "Orange")
val filtered = fruits.filter { it.startsWith("A") }
println(filtered) // Prints: [Apple]


3.1 Object
### Definition
An **object** is an instance of a class, representing a specific entity with attributes (properties) and behaviors (methods). In Kotlin, objects are created from classes and can also be declared as singleton instances using the `object` keyword.
### Use Case
- In Android, objects are used to represent UI elements (e.g., a `Button` object) or data entities (e.g., a `User` object).
### Code Example
```kotlin
class User(val name: String, val age: Int)
fun main() {
val user = User("Alice", 25) // Object creation
println("Name: ${user.name}, Age: ${user.age}") // Prints: Name: Alice, Age: 25
}

3.2 Class

Definition

A class is a blueprint for creating objects, defining their properties and methods. In Kotlin, classes are concise and support features like primary constructors and default visibility.

Use Case

  • Classes in Android are used to define data models (e.g., User) or UI components (e.g., Activity).

Code Example

kotlin
class Car {
var brand: String = ""
var speed: Int = 0
fun accelerate() {
speed += 10
}
}
fun main() {
val car = Car()
car.brand = "Toyota"
car.accelerate()
println("Brand: ${car.brand}, Speed: ${car.speed}") // Prints: Brand: Toyota, Speed: 10
}

3.3 Constructor

Definition

A constructor is a special function used to initialize objects of a class. Kotlin supports two types:

  • Primary Constructor: Defined in the class header, used for initializing properties directly.
  • Secondary Constructor: Defined inside the class body using the constructor keyword, allowing additional initialization logic.

Use Case

  • In Android, constructors are used to initialize data models or configure UI components with initial values.

Code Example

kotlin
class Person(val name: String, var age: Int) { // Primary constructor
var address: String = ""
// Secondary constructor
constructor(name: String, age: Int, address: String) : this(name, age) {
this.address = address
}
fun display() {
println("Name: $name, Age: $age, Address: $address")
}
}
fun main() {
val person1 = Person("Bob", 30)
val person2 = Person("Alice", 25, "123 Main St")
person1.display() // Prints: Name: Bob, Age: 30, Address:
person2.display() // Prints: Name: Alice, Age: 25, Address: 123 Main St
}

3.4 Inheritance

Definition

Inheritance allows a class (subclass) to inherit properties and methods from another class (superclass). In Kotlin, classes are final by default (cannot be inherited). Use the open keyword to allow inheritance.

Use Case

  • In Android, inheritance is used when extending base classes like AppCompatActivity for activities or Fragment for UI components.

Code Example

kotlin
open class Animal(val name: String) {
open fun makeSound() {
println("$name makes a sound")
}
}
class Dog(name: String) : Animal(name) {
override fun makeSound() {
println("$name barks")
}
}
fun main() {
val dog = Dog("Rex")
dog.makeSound() // Prints: Rex barks
}

3.5 Polymorphism

Definition

Polymorphism allows objects of different classes to be treated as objects of a common superclass. Kotlin supports:

  • Overloading: Defining multiple functions with the same name but different parameters.
  • Overriding: Redefining a superclass’s method in a subclass.

Use Case

  • In Android, polymorphism is used to handle different UI components (e.g., View subclasses like Button, TextView) uniformly or to customize behavior in subclasses.

3.5.1 Overloading

Definition

Method overloading allows multiple methods with the same name but different parameter lists (number, type, or order).

Code Example

kotlin
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
fun add(a: Double, b: Double): Double {
return a + b
}
fun add(a: Int, b: Int, c: Int): Int {
return a + b + c
}
}
fun main() {
val calc = Calculator()
println(calc.add(2, 3)) // Prints: 5
println(calc.add(2.5, 3.5)) // Prints: 6.0
println(calc.add(1, 2, 3)) // Prints: 6
}

3.5.2 Overriding

Definition

Method overriding allows a subclass to provide a specific implementation of a method defined in its superclass. The superclass method must be marked open, and the subclass method must use override.

Code Example

kotlin
open class Vehicle {
open fun start() {
println("Vehicle started")
}
}
class Car : Vehicle() {
override fun start() {
println("Car started with key")
}
}
fun main() {
val vehicle: Vehicle = Car()
vehicle.start() // Prints: Car started with key
}

3.6 Abstraction

Definition

Abstraction hides implementation details and exposes only the necessary functionality. In Kotlin, abstraction is achieved using:

  • Abstract Classes: Cannot be instantiated; used to define common behavior.
  • Interfaces: Define contracts that classes can implement, supporting multiple inheritance.

Use Case

  • In Android, abstraction is used to define reusable components (e.g., an interface for click listeners) or abstract base classes for activities.

Code Example

kotlin
interface Clickable {
fun onClick()
}
abstract class Shape {
abstract fun draw()
}
class Button : Shape(), Clickable {
override fun draw() {
println("Drawing a button")
}
override fun onClick() {
println("Button clicked")
}
}
fun main() {
val button = Button()
button.draw() // Prints: Drawing a button
button.onClick() // Prints: Button clicked
}

3.7 Access Modifiers

Definition

Access modifiers control the visibility of classes, properties, and methods. Kotlin provides:

  • private: Visible only within the same class or file.
  • protected: Visible within the same class and its subclasses.
  • public: Visible everywhere (default in Kotlin).
  • internal: Visible within the same module (a set of Kotlin files compiled together).

Use Case

  • In Android, access modifiers ensure encapsulation, such as making view references private in an Activity or using internal for module-specific utilities.

Code Example

kotlin
class Example {
private val privateVar = "Private"
protected val protectedVar = "Protected"
internal val internalVar = "Internal"
val publicVar = "Public"
}
class SubExample : Example() {
fun display() {
println(protectedVar) // Accessible
println(publicVar) // Accessible
// println(privateVar) // Error: privateVar is not accessible
}
}
fun main() {
val example = Example()
println(example.publicVar) // Prints: Public
println(example.internalVar) // Prints: Internal
// println(example.privateVar) // Error: privateVar is not accessible
}

3.8 Data Class

Definition

A data class is a special class designed to hold data, automatically providing implementations for toString(), equals(), hashCode(), and copy(). It is defined using the data keyword.

Use Case

  • In Android, data classes are used for data models, such as API responses or database entities, simplifying data manipulation.

Code Example

kotlin
data class User(val id: Int, val name: String, val email: String)
fun main() {
val user1 = User(1, "Alice", "alice@example.com")
val user2 = user1.copy(email = "alice@new.com")
println(user1) // Prints: User(id=1, name=Alice, email=alice@example.com)
println(user2) // Prints: User(id=1, name=Alice, email=alice@new.com)
println(user1 == user2) // Prints: false
}

4. Practical Use Cases for Android Development

Use Case 1: Data Class for API Response

Use a data class to model a JSON response from an API.

kotlin
data class Product(val id: Int, val name: String, val price: Double)
class ProductActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product)
val product = Product(1, "Laptop", 999.99)
val textView: TextView = findViewById(R.id.productTextView)
textView.text = product.toString()
}
}

Use Case 2: Inheritance and Polymorphism in Android

Extend AppCompatActivity and override lifecycle methods.

kotlin
open class BaseActivity : AppCompatActivity() {
open fun setupUI() {
// Default implementation
}
}
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupUI()
}
override fun setupUI() {
val textView: TextView = findViewById(R.id.textView)
textView.text = "MainActivity UI"
}
}

Use Case 3: Interface for Click Handling

Define a reusable click listener interface.

kotlin
interface OnItemClickListener {
fun onItemClick(item: String)
}
class ItemAdapter(private val listener: OnItemClickListener) {
fun onItemSelected(item: String) {
listener.onItemClick(item)
}
}
class MainActivity : AppCompatActivity(), OnItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = ItemAdapter(this)
adapter.onItemSelected("Item 1")
}
override fun onItemClick(item: String) {
Toast.makeText(this, "Clicked: $item", Toast.LENGTH_SHORT).show()
}
}

5. Interview Preparation Tips

  1. Explain OOP Concepts Clearly:
    • Define each concept (e.g., inheritance, polymorphism) and provide Kotlin-specific examples.
    • Highlight differences from Java (e.g., open keyword, data classes).
  2. Demonstrate Android Relevance:
    • Show how OOP is applied in Android (e.g., extending Activity, using interfaces for event handling).
  3. Null Safety Integration:
    • Explain how Kotlin’s null safety complements OOP (e.g., using nullable types in data classes).
  4. Practical Scenarios:
    • Be prepared to write code for common Android tasks, such as creating a data class for a REST API response or implementing a custom View with polymorphism.
  5. Common Questions:
    • Difference between open and final classes.
    • When to use a data class vs. a regular class.
    • How to implement multiple inheritance using interfaces.






4. Kotlin Collections
Kotlin provides a powerful and flexible collection framework for handling groups of data. Collections in Kotlin are divided into **immutable** (read-only) and **mutable** (modifiable) types. This section covers the main collection types (`List`, `Set`, `Map`) and common operations (`all`, `any`, `count`, `find`, `filter`, `map`), with a focus on their use in Android development.
---
## 4.1 List and MutableList
### Definition
- **List**: An immutable, ordered collection of elements that allows duplicates. Use `listOf()` to create.
- **MutableList**: A mutable version of `List`, allowing additions, removals, and modifications. Use `mutableListOf()` to create.
### Key Features
- Elements are accessed by index (0-based).
- Maintains insertion order.
- Suitable for ordered data, such as a list of items in a RecyclerView.
### Use Case
- In Android, `List` is used to store static data (e.g., predefined menu items), while `MutableList` is used for dynamic data (e.g., a list of tasks in a to-do app).
### Code Example
```kotlin
fun main() {
// Immutable List
val fruits: List<String> = listOf("Apple", "Banana", "Orange")
println(fruits[1]) // Prints: Banana
// fruits.add("Mango") // Error: Immutable list
// Mutable List
val mutableFruits: MutableList<String> = mutableListOf("Apple", "Banana")
mutableFruits.add("Mango")
mutableFruits[1] = "Grape"
println(mutableFruits) // Prints: [Apple, Grape, Mango]
}

Android Example

Populating a RecyclerView with a MutableList.

kotlin
class MainActivity : AppCompatActivity() {
private val items: MutableList<String> = mutableListOf("Item 1", "Item 2")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.adapter = ItemAdapter(items)
recyclerView.layoutManager = LinearLayoutManager(this)
// Add item dynamically
val button: Button = findViewById(R.id.addButton)
button.setOnClickListener {
items.add("Item ${items.size + 1}")
recyclerView.adapter?.notifyDataSetChanged()
}
}
}

4.2 Set and MutableSet

Definition

  • Set: An immutable, unordered collection of unique elements. Use setOf() to create.
  • MutableSet: A mutable version of Set, allowing additions and removals. Use mutableSetOf() to create.

Key Features

  • No duplicate elements (duplicates are automatically removed).
  • No index-based access due to unordered nature.
  • Ideal for scenarios requiring unique items, such as a collection of unique tags.

Use Case

  • In Android, Set is used for unique data (e.g., unique user IDs), while MutableSet is used when items need to be added or removed dynamically (e.g., selected filter options).

Code Example

kotlin
fun main() {
// Immutable Set
val colors: Set<String> = setOf("Red", "Blue", "Red") // Duplicate "Red" ignored
println(colors) // Prints: [Red, Blue]
// colors.add("Green") // Error: Immutable set
// Mutable Set
val mutableColors: MutableSet<String> = mutableSetOf("Red", "Blue")
mutableColors.add("Green")
mutableColors.remove("Red")
println(mutableColors) // Prints: [Blue, Green]
}

Android Example

Managing unique tags in a filter system.

kotlin
class FilterActivity : AppCompatActivity() {
private val selectedTags: MutableSet<String> = mutableSetOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_filter)
val addTagButton: Button = findViewById(R.id.addTagButton)
val tagInput: EditText = findViewById(R.id.tagInput)
val tagTextView: TextView = findViewById(R.id.tagTextView)
addTagButton.setOnClickListener {
val tag = tagInput.text.toString()
if (tag.isNotEmpty()) {
selectedTags.add(tag)
tagTextView.text = "Tags: $selectedTags"
tagInput.text.clear()
}
}
}
}

4.3 Map and MutableMap

Definition

  • Map: An immutable collection of key-value pairs, where keys are unique. Use mapOf() to create.
  • MutableMap: A mutable version of Map, allowing modifications to key-value pairs. Use mutableMapOf() to create.

Key Features

  • Keys are unique; values can be duplicated.
  • Access values using keys (not indices).
  • Suitable for key-based data, such as user preferences or configuration settings.

Use Case

  • In Android, Map is used for static configurations (e.g., settings), while MutableMap is used for dynamic key-value data (e.g., form input data).

Code Example

kotlin
fun main() {
// Immutable Map
val userInfo: Map<String, String> = mapOf("name" to "Alice", "email" to "alice@example.com")
println(userInfo["name"]) // Prints: Alice
// userInfo["name"] = "Bob" // Error: Immutable map
// Mutable Map
val mutableUserInfo: MutableMap<String, String> = mutableMapOf("name" to "Bob")
mutableUserInfo["email"] = "bob@example.com"
mutableUserInfo.remove("name")
println(mutableUserInfo) // Prints: {email=bob@example.com}
}

Android Example

Storing form data in a MutableMap.

kotlin
class FormActivity : AppCompatActivity() {
private val formData: MutableMap<String, String> = mutableMapOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_form)
val nameInput: EditText = findViewById(R.id.nameInput)
val emailInput: EditText = findViewById(R.id.emailInput)
val submitButton: Button = findViewById(R.id.submitButton)
val resultTextView: TextView = findViewById(R.id.resultTextView)
submitButton.setOnClickListener {
formData["name"] = nameInput.text.toString()
formData["email"] = emailInput.text.toString()
resultTextView.text = "Form Data: $formData"
}
}
}

4.4 Predicates: all, any, count, find

Definition

Predicates are functions that test elements in a collection and return a boolean or a count. Common predicates include:

  • all: Returns true if all elements satisfy the condition.
  • any: Returns true if at least one element satisfies the condition.
  • count: Returns the number of elements that satisfy the condition.
  • find: Returns the first element that satisfies the condition or null if none found.

Use Case

  • In Android, predicates are used to validate data (e.g., checking if all form fields are filled) or to search collections (e.g., finding a specific item in a list).

Code Example

kotlin
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// all: Check if all numbers are positive
val allPositive = numbers.all { it > 0 }
println(allPositive) // Prints: true
// any: Check if any number is even
val hasEven = numbers.any { it % 2 == 0 }
println(hasEven) // Prints: true
// count: Count numbers greater than 3
val countGreaterThanThree = numbers.count { it > 3 }
println(countGreaterThanThree) // Prints: 2
// find: Find the first number divisible by 3
val firstDivisibleByThree = numbers.find { it % 3 == 0 }
println(firstDivisibleByThree) // Prints: 3
}

Android Example

Validating a list of form inputs.

kotlin
class ValidationActivity : AppCompatActivity() {
private val inputs: List<String> = listOf("Alice", "alice@example.com", "")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_validation)
val resultTextView: TextView = findViewById(R.id.resultTextView)
// Check if all inputs are non-empty
val allValid = inputs.all { it.isNotEmpty() }
resultTextView.text = if (allValid) "All inputs valid" else "Some inputs empty"
}
}

4.5 filter and map

Definition

  • filter: Creates a new collection containing only elements that satisfy a condition.
  • map: Transforms each element in a collection into a new value, creating a new collection.

Use Case

  • In Android, filter is used to display a subset of data (e.g., filtering a list of products by category), and map is used to transform data (e.g., extracting names from a list of users).

Code Example

kotlin
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
// filter: Get even numbers
val evenNumbers = numbers.filter { it % 2 == 0 }
println(evenNumbers) // Prints: [2, 4]
// map: Square each number
val squaredNumbers = numbers.map { it * it }
println(squaredNumbers) // Prints: [1, 4, 9, 16, 25]
// Combined: Filter even numbers and square them
val evenSquared = numbers.filter { it % 2 == 0 }.map { it * it }
println(evenSquared) // Prints: [4, 16]
}

Android Example

Filtering and mapping data for a RecyclerView.

kotlin
data class Product(val id: Int, val name: String, val category: String)
class ProductActivity : AppCompatActivity() {
private val products = listOf(
Product(1, "Laptop", "Electronics"),
Product(2, "Shirt", "Clothing"),
Product(3, "Phone", "Electronics")
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
val electronics = products.filter { it.category == "Electronics" }
.map { it.name }
recyclerView.adapter = ItemAdapter(electronics)
recyclerView.layoutManager = LinearLayoutManager(this)
}
}

5. Interview Preparation Tips

  1. Understand Immutable vs. Mutable Collections:
    • Explain the difference between List and MutableList, Set and MutableSet, Map and MutableMap.
    • Highlight when to use immutable collections for safety and mutable collections for flexibility.
  2. Master Collection Operations:
    • Be prepared to write code using filter, map, all, any, count, and find.
    • Explain how these operations improve code readability and efficiency.
  3. Android-Specific Applications:
    • Demonstrate how collections are used in Android (e.g., populating a RecyclerView with a filtered List or storing user preferences in a Map).
  4. Performance Considerations:
    • Discuss the performance implications of large collections (e.g., Set for unique elements is faster than checking duplicates in a List).
  5. Common Questions:
    • How do you filter a list of objects in Kotlin?
    • What’s the difference between find and firstOrNull?
    • How would you use a MutableMap to store form data in an Android app?


 



5.1 Kotlin Coroutines
### Definition
Kotlin **Coroutines** are a concurrency design pattern used to simplify asynchronous programming, such as network calls or database operations, by allowing code to run in a non-blocking manner. They provide a way to write asynchronous code that looks sequential.
### Key Features
- **Suspend Functions**: Functions marked with `suspend` can pause execution without blocking the thread.
- **Coroutine Scope**: Defines the lifecycle of coroutines (e.g., `GlobalScope`, `viewModelScope` in Android).
- **Dispatchers**: Control the thread where coroutines run (e.g., `Dispatchers.Main` for UI, `Dispatchers.IO` for I/O operations).
### Use Case
- In Android, coroutines are used for tasks like fetching data from an API, updating the UI, or performing database operations without freezing the app.
### Code Example
```kotlin
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
// Launch coroutine in Main scope
CoroutineScope(Dispatchers.Main).launch {
val result = fetchData() // Suspend function
textView.text = result
}
}
private suspend fun fetchData(): String {
// Simulate network call on IO thread
return withContext(Dispatchers.IO) {
delay(1000) // Simulate delay
"Data fetched!"
}
}
}

Interview Tip

  • Explain the difference between launch and async, and when to use Dispatchers.Main vs. Dispatchers.IO.
  • Highlight how coroutines replace older Android patterns like AsyncTask.

5.2 Java Interoperability

Definition

Kotlin is fully interoperable with Java, allowing seamless use of Java libraries and classes in Kotlin code and vice versa. Kotlin code compiles to JVM bytecode, making it compatible with Java-based Android APIs.

Key Features

  • Call Java methods from Kotlin and Kotlin functions from Java.
  • Kotlin’s null safety works with Java’s nullable types using annotations (e.g., @Nullable, @NotNull).
  • Kotlin properties can be accessed as getters/setters in Java.

Use Case

  • In Android, Java interoperability allows using Android’s Java-based APIs (e.g., Activity, View) or third-party Java libraries in Kotlin projects.

Code Example

kotlin
// Kotlin class
class KotlinClass {
fun greet(name: String): String {
return "Hello, $name!"
}
}
// Java class calling Kotlin
public class JavaClass {
public static void main(String[] args) {
KotlinClass kotlinClass = new KotlinClass();
System.out.println(kotlinClass.greet("Alice")); // Prints: Hello, Alice!
}
}

Android Example

Using Java-based Android API in Kotlin.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Using Java-based Toast API
Toast.makeText(this, "Welcome!", Toast.LENGTH_SHORT).show()
}
}

Interview Tip

  • Explain how Kotlin handles Java’s lack of null safety (e.g., using platform types like String!).
  • Discuss converting a Java Android project to Kotlin incrementally.

5.3 Sealed Class

Definition

A sealed class is a restricted class hierarchy where all subclasses must be defined within the same file. It is used to represent a fixed set of types, often in conjunction with when expressions for type-safe handling.

Use Case

  • In Android, sealed classes are used to model UI states (e.g., loading, success, error) or API response types.

Code Example

kotlin
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println("Data: ${result.data}")
is Result.Error -> println("Error: ${result.message}")
Result.Loading -> println("Loading...")
}
}
fun main() {
handleResult(Result.Success("User data"))
handleResult(Result.Error("Failed to load"))
handleResult(Result.Loading)
}

Android Example

Managing UI state in an Activity.

kotlin
sealed class UiState {
data class Success(val data: String) : UiState()
data class Error(val message: String) : UiState()
object Loading : UiState()
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
val state: UiState = UiState.Success("Profile loaded")
when (state) {
is UiState.Success -> textView.text = state.data
is UiState.Error -> textView.text = state.message
UiState.Loading -> textView.text = "Loading..."
}
}
}

Interview Tip

  • Explain why sealed classes are preferred over enums for complex type hierarchies.
  • Discuss their use with when for exhaustive type checking.

5.4 Enums

Definition

An enum class defines a fixed set of constants, each representing an instance of the enum class. Enums can have properties and methods.

Use Case

  • In Android, enums are used for fixed categories, such as user roles, screen orientations, or button states.

Code Example

kotlin
enum class Direction(val degrees: Int) {
NORTH(0),
EAST(90),
SOUTH(180),
WEST(270);
fun getDescription(): String {
return "$name is at $degrees degrees"
}
}
fun main() {
val direction = Direction.NORTH
println(direction.getDescription()) // Prints: NORTH is at 0 degrees
}

Android Example

Using enums for button states.

kotlin
enum class ButtonState {
ENABLED, DISABLED
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.button)
val state = ButtonState.DISABLED
button.isEnabled = state == ButtonState.ENABLED
}
}

Interview Tip

  • Compare enums and sealed classes (e.g., enums for simple constants, sealed classes for complex hierarchies).

5.5 Exception Handling

Definition

Exception handling in Kotlin uses try, catch, and finally blocks to handle runtime errors gracefully. Kotlin does not have checked exceptions, unlike Java.

Use Case

  • In Android, exception handling is used for network failures, file operations, or invalid user inputs.

Code Example

kotlin
fun divide(a: Int, b: Int): Int {
return try {
a / b
} catch (e: ArithmeticException) {
println("Error: ${e.message}")
0
} finally {
println("Operation completed")
}
}
fun main() {
println(divide(10, 2)) // Prints: 5, Operation completed
println(divide(10, 0)) // Prints: Error: / by zero, 0, Operation completed
}

Android Example

Handling network call exceptions.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
CoroutineScope(Dispatchers.Main).launch {
try {
val data = fetchData()
textView.text = data
} catch (e: Exception) {
textView.text = "Error: ${e.message}"
}
}
}
private suspend fun fetchData(): String = withContext(Dispatchers.IO) {
throw Exception("Network failure") // Simulated error
}
}

Interview Tip

  • Explain the absence of checked exceptions in Kotlin and its benefits in Android.
  • Discuss combining coroutines with exception handling.

5.6 Companion Object

Definition

A companion object is a singleton object declared inside a class using the companion object keyword. It is used to define static-like members (properties or methods) in Kotlin, as there are no static members.

Use Case

  • In Android, companion objects are used for constants, utility functions, or factory methods (e.g., creating an Intent).

Code Example

kotlin
class DatabaseHelper {
companion object {
const val DATABASE_NAME = "app.db"
fun getInstance(): DatabaseHelper {
return DatabaseHelper()
}
}
}
fun main() {
println(DatabaseHelper.DATABASE_NAME) // Prints: app.db
val db = DatabaseHelper.getInstance()
}

Android Example

Using companion object for Intent creation.

kotlin
class MainActivity : AppCompatActivity() {
companion object {
fun createIntent(context: Context): Intent {
return Intent(context, MainActivity::class.java)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
// Starting activity from another context
fun startMainActivity(context: Context) {
context.startActivity(MainActivity.createIntent(context))
}

Interview Tip

  • Explain why Kotlin uses companion objects instead of static.
  • Discuss their use in Android for utility methods or constants.

5.7 Interfaces

Definition

An interface defines a contract of methods and properties that implementing classes must provide. Kotlin interfaces can have default implementations and support multiple inheritance.

Use Case

  • In Android, interfaces are used for event listeners (e.g., click listeners) or defining contracts for components.

Code Example

kotlin
interface Clickable {
fun onClick()
fun onLongClick() = println("Default long click")
}
class Button : Clickable {
override fun onClick() {
println("Button clicked")
}
}
fun main() {
val button = Button()
button.onClick() // Prints: Button clicked
button.onLongClick() // Prints: Default long click
}

Android Example

Implementing a click listener interface.

kotlin
interface ItemClickListener {
fun onItemClick(item: String)
}
class ItemAdapter(private val listener: ItemClickListener) {
fun selectItem(item: String) {
listener.onItemClick(item)
}
}
class MainActivity : AppCompatActivity(), ItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adapter = ItemAdapter(this)
adapter.selectItem("Item 1")
}
override fun onItemClick(item: String) {
Toast.makeText(this, "Selected: $item", Toast.LENGTH_SHORT).show()
}
}

Interview Tip

  • Compare interfaces with abstract classes in Kotlin.
  • Explain their use in Android for decoupling components.

5.8 Scope Functions: apply, also, let, with, and run

Definition

Kotlin’s scope functions (apply, also, let, with, run) provide concise ways to operate on objects within a specific scope, improving code readability and reducing boilerplate.

Key Features

  • apply: Configures an object and returns it (this).
  • also: Performs additional actions on an object and returns it (it).
  • let: Executes a block on a nullable object and returns the block’s result.
  • with: Executes a block on a non-null object and returns the block’s result.
  • run: Executes a block on an object and returns the block’s result (similar to with but as an extension function).

Use Case

  • In Android, scope functions are used to initialize views, handle nullable objects, or simplify object configuration.

Code Example

kotlin
data class Person(var name: String, var age: Int)
fun main() {
// apply: Configure and return the object
val person1 = Person("Alice", 25).apply {
age = 26
}
println(person1) // Prints: Person(name=Alice, age=26)
// also: Perform side effects and return the object
val person2 = Person("Bob", 30).also {
println("Created person: ${it.name}")
}
println(person2) // Prints: Person(name=Bob, age=30)
// let: Handle nullable objects
val nullableName: String? = "Charlie"
val length = nullableName?.let { it.length } ?: 0
println(length) // Prints: 7
// with: Work with a non-null object
val person3 = Person("Dave", 40)
with(person3) {
name = "David"
age = 41
}
println(person3) // Prints: Person(name=David, age=41)
// run: Execute a block and return its result
val result = person3.run { "$name is $age years old" }
println(result) // Prints: David is 41 years old
}

Android Example

Using scope functions for view initialization.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// apply: Configure a view
findViewById<TextView>(R.id.textView).apply {
text = "Welcome!"
textSize = 18f
setTextColor(Color.BLACK)
}
// let: Handle nullable intent data
val data: String? = intent.getStringExtra("key")
data?.let {
findViewById<TextView>(R.id.textView).text = it
}
}
}

Interview Tip

  • Explain the differences between scope functions and when to use each.
  • Demonstrate their use in Android for view setup or nullable handling.

5.9 Type Checking and Smart Casting

Definition

Type checking uses the is operator to check an object’s type, and smart casting automatically casts the object to that type within a safe scope (e.g., if or when).

Use Case

  • In Android, type checking and smart casting are used when handling different types in a collection or polymorphic objects.

Code Example

kotlin
interface Animal {
fun makeSound()
}
class Dog : Animal {
override fun makeSound() = println("Bark")
fun fetch() = println("Fetching ball")
}
class Cat : Animal {
override fun makeSound() = println("Meow")
}
fun main() {
val animals: List<Animal> = listOf(Dog(), Cat())
for (animal in animals) {
if (animal is Dog) {
animal.fetch() // Smart cast to Dog
}
animal.makeSound()
}
}

Android Example

Handling different view types in a RecyclerView.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val view: View = findViewById(R.id.button)
if (view is Button) {
view.text = "Click Me" // Smart cast to Button
}
}
}

Interview Tip

  • Explain how smart casting reduces explicit casting compared to Java.
  • Discuss its use with sealed classes or interfaces.

5.10 Getters and Setters in Kotlin (Backing Field)

Definition

Kotlin properties automatically generate getters and setters. A backing field (field) is used to store the property’s value when custom getter/setter logic is needed.

Key Features

  • Default getters/setters are generated for var and val properties.
  • Use field in custom getters/setters to avoid recursive calls.

Use Case

  • In Android, custom getters/setters are used for data validation or UI updates.

Code Example

kotlin
class Person {
var name: String = ""
get() = field.uppercase()
set(value) {
field = if (value.isNotEmpty()) value else "Unknown"
}
}
fun main() {
val person = Person()
person.name = "Alice"
println(person.name) // Prints: ALICE
person.name = ""
println(person.name) // Prints: UNKNOWN
}

Android Example

Validating input in a ViewModel.

kotlin
class UserViewModel {
var username: String = ""
get() = field
set(value) {
field = if (value.length >= 3) value else "Invalid"
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = UserViewModel()
val textView: TextView = findViewById(R.id.textView)
viewModel.username = "Al"
textView.text = viewModel.username // Prints: Invalid
}
}

Interview Tip

  • Explain the role of field and how it prevents infinite recursion.
  • Discuss when to use custom getters/setters in Android.

5.11 lateinit Keyword for Variables

Definition

The lateinit keyword delays the initialization of a non-null var property until later. It is used only with var (not val) and non-primitive types.

Use Case

  • In Android, lateinit is used for variables initialized after object creation, such as views in an Activity or dependencies in dependency injection.

Code Example

kotlin
class Example {
lateinit var name: String
fun initializeName(value: String) {
name = value
}
fun isInitialized(): Boolean {
return ::name.isInitialized
}
}
fun main() {
val example = Example()
println(example.isInitialized()) // Prints: false
example.initializeName("Alice")
println(example.name) // Prints: Alice
}

Android Example

Using lateinit for view initialization.

kotlin
class MainActivity : AppCompatActivity() {
lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView)
if (::textView.isInitialized) {
textView.text = "Initialized!"
}
}
}

Interview Tip

  • Explain why lateinit is used and its risks (e.g., accessing uninitialized properties).
  • Discuss alternatives like nullable types or lazy initialization.

5.12 Safe Call and Elvis Operator

Definition

  • Safe Call Operator (?. ): Accesses properties or methods of a nullable object only if it is non-null.
  • Elvis Operator (?: ): Provides a default value if an expression is null.

Use Case

  • In Android, safe calls and the Elvis operator are used to handle nullable views, intent extras, or API responses safely.

Code Example

kotlin
fun main() {
val nullableString: String? = null
// Safe Call
println(nullableString?.length) // Prints: null
// Elvis Operator
val length = nullableString?.length ?: 0
println(length) // Prints: 0
}

Android Example

Handling nullable intent data.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
val data: String? = intent.getStringExtra("key")
textView.text = data ?: "No data provided"
}
}

Interview Tip

  • Compare safe call/Elvis operator with Java’s null checks.
  • Explain their role in preventing NullPointerExceptions in Android.

6. Practical Use Cases for Android Development

Use Case 1: Coroutines with Sealed Classes

Fetch data asynchronously and update UI based on state.

kotlin
sealed class ApiResult {
data class Success(val data: String) : ApiResult()
data class Error(val message: String) : ApiResult()
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
CoroutineScope(Dispatchers.Main).launch {
val result = fetchData()
textView.text = when (result) {
is ApiResult.Success -> result.data
is ApiResult.Error -> result.message
}
}
}
private suspend fun fetchData(): ApiResult = withContext(Dispatchers.IO) {
try {
delay(1000) // Simulate network call
ApiResult.Success("Data loaded")
} catch (e: Exception) {
ApiResult.Error("Failed to load")
}
}
}

Use Case 2: Scope Functions for View Setup

Configure multiple views concisely.

kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.button).apply {
text = "Submit"
isEnabled = false
setOnClickListener { Toast.makeText(this@MainActivity, "Clicked", Toast.LENGTH_SHORT).show() }
}
}
}

Use Case 3: Companion Object for Constants

Define app-wide constants.

kotlin
class AppConfig {
companion object {
const val API_BASE_URL = "https://api.example.com"
const val TIMEOUT_SECONDS = 30L
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView: TextView = findViewById(R.id.textView)
textView.text = AppConfig.API_BASE_URL
}
}

7. Interview Preparation Tips

  1. Master Coroutines:
    • Explain suspend functions, dispatchers, and structured concurrency.
    • Demonstrate Android examples (e.g., API calls with Retrofit and coroutines).
  2. Java Interoperability:
    • Discuss calling Java APIs in Kotlin and handling nullability.
  3. Sealed Classes vs. Enums:
    • Explain when to use each and their benefits in Android.
  4. Scope Functions:
    • Provide examples of apply vs. let vs. run in Android contexts.
  5. Exception Handling:
    • Show how to handle errors in coroutines and UI updates.
  6. Common Questions:
    • How do coroutines improve Android development compared to AsyncTask?
    • When would you use lateinit vs. a nullable property?
    • How do scope functions improve code readability?

8. Additional Resources

No comments:

Post a Comment