Personal revision notes

Kotlin & Android, arranged to actually revise from.

Every point from your original notes β€” reordered into one sequential curriculum, in English and Hinglish, with runnable code and real output β€” plus new topics (Generics, Sequences, MVVM, Retrofit, Compose, Hilt, Memory/ANR, WorkManager) commonly asked at 4+ YOE interviews.

πŸ‘€ Zulfikar β€” Android Developer, 4+ yrs πŸ“š 32 topics πŸŒ“ Dark mode by default, switch anytime
01

val vs var vs const val, and core syntax

val vs var

In Kotlin, val and var both declare variables but differ in mutability: val = read-only reference (cannot be reassigned), var = mutable (can be reassigned).

const val

const val is a compile-time constant β€” its value must be known at compile time and can only be a top-level or object-level property of a primitive type or String (not inside a function, not a class instance).

const val ek aisa constant hai jiski value compile time par hi known honi chahiye aur baad mein change nahi ho sakti.

Example: const val BASE_URL = "https://api.example.com" β€” compiler ko pehle se value pata hai.
Example
fun main() {
    val name = "Alice"      // cannot be reassigned
    var age = 25
    age = 26                // OK

    println("Name: $name")
    println("Age: $age")
}
Output
Name: Alice
Age: 26

Type Inference & Type Conversion

Kotlin infers a variable's type from its assigned value. Convert between types with toInt(), toDouble(), toString(), etc.

Default & Named Arguments

fun createUser(name: String, city: String = "Delhi") β†’ call as createUser("John") or createUser("John", city = "LA").

Vararg parameters

Example
fun greetAll(greeting: String, vararg names: String) {
    for (name in names) println("$greeting, $name!")
}
fun main() = greetAll("Hello", "Alice", "Bob", "Charlie")
Output
Hello, Alice!
Hello, Bob!
Hello, Charlie!

Tail-recursive functions

A tail-recursive function's recursive call is the last operation before returning. Marked tailrec, Kotlin compiles it into a loop, avoiding StackOverflowError on deep recursion.

tailrec fun countDown(n: Int) {
    if (n == 0) return
    println(n)
    countDown(n - 1)   // last operation β†’ tail call
}

Inline Function

A higher-order function call has overhead: each lambda is an object, each call creates a stack frame. inline copies the function body to the call site at compile time, removing that overhead β€” used heavily for small utility HOFs like let, run, forEach.

inline fun myInlineFunc(action: () -> Unit) {
    println("Before action")
    action()
    println("After action")
}

Function Types & it

Functions can be stored, passed, and returned like data. When a lambda has a single parameter, Kotlin auto-names it it.

val sum: (Int, Int) -> Int = { a, b -> a + b }
listOf(1, 2, 3).forEach { println(it) }

Late Initialization (lateinit)

lateinit declares a non-null var without initializing it immediately β€” useful for Android Views, dependency injection. Accessing it before init throws UninitializedPropertyAccessException. Works only with non-null var of a class/reference type (not primitives, not val).

lateinit var recyclerView: RecyclerView

== vs ===

== checks structural equality (calls equals()). === checks referential equality (same object in memory).

02

Null Safety in Kotlin

Kotlin's type system distinguishes nullable and non-nullable references at compile time, preventing most NullPointerExceptions.

  • ? β€” Nullable Type β€” val age: Int? = null
  • ?. β€” Safe Call β€” val length = name?.length (returns null instead of crashing if name is null)
  • ?: β€” Elvis Operator β€” val displayName = name ?: "Guest" (fallback if left side is null)
  • !! β€” Not-Null Assertion β€” forces a NPE if the value is null; use only when you're certain it can't be null
  • let β€” commonly combined with ?. for null-safe scoped operations: name?.let { println(it) }
Q: When should you actually use !!?
Rarely β€” only when you have stronger guarantees than the compiler (e.g. right after a null-check the compiler can't smart-cast). Overuse defeats the purpose of null safety.
03

Lambda & Higher-Order Functions

A lambda is an anonymous function you can store in a variable or pass as an argument.

Higher-Order Function

A function that takes another function as a parameter, returns a function, or both β€” e.g. map, filter, forEach.

Unit

Unit is Kotlin's equivalent of Java's void β€” it means the function returns no meaningful value.

Q: Trailing lambda kya hai?
When a lambda is the last parameter of a function, Kotlin lets you move it outside the parentheses.
fun calculate(x: Int, op: (Int) -> Int) = op(x)
fun main() = println(calculate(10) { it * 2 })  // trailing lambda
Output
20
04

Extension Functions

Lets you add a new function to an existing class without modifying or inheriting from it β€” resolved statically at compile time based on the declared type.

Existing class mein bina modify/inherit kiye naya function add karna. Code ko clean, reusable, readable banane ke liye Android dev mein bahut use hota hai.

fun String.isValidEmail(): Boolean =
    android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()

fun main() = println("test@mail.com".isValidEmail())
Output
true
Q: Member function vs extension function priority?
A member function always wins over an extension function with the same signature.
05

Generics new

Generics let you write classes/functions that work with any type while keeping compile-time type safety, avoiding unchecked casts.

class Box(val item: T)
fun  printItem(item: T) = println(item)

fun main() {
    val intBox = Box(10)
    val strBox = Box("Hello")
    println(intBox.item)
    println(strBox.item)
}
Output
10
Hello

Variance: out / in

  • out (covariant) β€” the type can only be produced/returned, not consumed. List<out T> means List<Dog> is a List<Animal>.
  • in (contravariant) β€” the type can only be consumed, not produced. Comparable<in T>.

out = "produce only" (returns), in = "consume only" (takes as parameter). Ye Java ke ? extends / ? super jaisa hai.

Reified type parameters

Normally generic type info is erased at runtime (type erasure). Marking an inline function's type parameter reified keeps the type available at runtime.

inline fun  isInstance(value: Any) = value is T
fun main() = println(isInstance("hello"))  // true
06

Sequences vs Collections new

Collection operations (map, filter on List) are eager β€” each operator processes the whole list and creates an intermediate list. A Sequence is lazy β€” elements are processed one at a time, only computed when a terminal operation (toList(), first(), sum()) is called.

List operations turant poora list process karte hain har step pe. Sequence lazy hai β€” element-by-element chalti hai, sirf jab terminal operation call ho tab kaam hota hai. Bade datasets ya chained operations (map+filter+...) mein Sequence zyada efficient hai.

val result = listOf(1, 2, 3, 4, 5)
    .asSequence()
    .map { println("map $it"); it * 2 }
    .filter { println("filter $it"); it > 4 }
    .first()
println("Result: $result")
Output
map 1
filter 2
map 2
filter 4
map 3
filter 6
Result: 6
Notice it stops as soon as first() finds a match β€” with a regular List, every element would go through map first, then every result through filter.
07

OOPs Concepts β€” the 4 pillars

1. Class & Object

A class is a blueprint defining properties and functions. An object is a real instance of that class.

Class = ghar ka naksha (blueprint). Object = naksha se bana actual ghar.

2. Inheritance

A child/derived class inherits properties and functions from a parent/base class.

3. Encapsulation

Internal state is hidden from outside and accessible only through defined methods (getters/setters).

Data = ATM ke andar ka paisa. Methods = ATM screen β€” sirf PIN daal ke paisa nikaal sakte ho.

4. Polymorphism

Objects of different classes can be treated via a common supertype, and each class responds to the same call differently.

Ek naam, multiple kaam β€” draw() Circle alag draw karega, Rectangle alag.

Q: Method overloading vs overriding?
Overloading (compile-time): same name, different parameters, same class. Overriding (runtime): same name & parameters, parent-child classes, decided by actual object type at runtime. Return type can vary in overloading; must match (or be a subtype, i.e. covariant return) in overriding.

5. Abstraction

Hiding unnecessary implementation details, exposing only essential features.

Q: Abstract class vs Interface?
Abstract class: can hold state, can have a constructor, can mix abstract & concrete methods, single inheritance only. Interface: cannot hold backing-field state, no constructor, supports multiple inheritance, and (since Kotlin/Java 8) can have default method bodies.
08

Inheritance (final / open) & the object keyword

  • final (default): All Kotlin classes/members are final by default β€” cannot be extended/overridden.
  • open: Add open to a class or member to allow it to be extended/overridden.
  • object declaration: a lazily-created, thread-safe singleton.
  • companion object: a named object tied to a class, used like Java's static members.
  • object expression: an anonymous, one-off object, often implementing an interface inline.
Q1: object vs companion object?
object is a top-level global singleton. companion object lives inside a class, associated with that class, and is what gives Kotlin classes a "static"-like member.
Q2: object expression vs object declaration?
Expression β†’ anonymous, one-off use (e.g. an inline click-listener implementation). Declaration β†’ exactly one shared global instance (config, utility, cache).
Q3: Can a companion object be named?
Yes β€” companion object Factory { }, accessed as User.Factory; unnamed it's accessed as User.Companion.
09

object, companion object & Singleton β€” in depth

object AppConstants {
    const val BASE_URL = "https://api.example.com"
    const val TIMEOUT = 30
}

class User {
    companion object {
        const val TYPE = "USER"
        fun create(name: String) = User().also { it.name = name }
    }
    var name: String = ""
}
ConceptMeaning
objectStandalone, lazily-initialized singleton
companion objectClass-level members inside a class β€” can hold functions & properties
static (Java)No direct equivalent in Kotlin β€” use object / companion object / top-level functions
Reaching for object for every singleton isn't automatically "clean architecture" β€” it makes testing and lifecycle control harder. Modern Android apps prefer DI (Hilt/Dagger) to manage singleton scope instead of hardcoding it via object.
10

data class, enum class & sealed class

1) data class

Used to hold data. The compiler auto-generates equals(), hashCode(), toString(), copy(), and componentN() (for destructuring) from the primary-constructor properties.

The primary constructor must have at least one parameter, and those must be val/var.
data class User(val name: String, val age: Int)
fun main() {
    val u1 = User("Zulfikar", 25)
    val u2 = u1.copy(age = 26)
    println(u1)
    println(u2)
    println(u1 == u2)
}
Output
User(name=Zulfikar, age=25)
User(name=Zulfikar, age=26)
false

2) enum class

A fixed, closed set of predefined constants β€” can't be extended at runtime.

enum class PaymentStatus { PENDING, SUCCESS, FAILED }

3) sealed class

A restricted class hierarchy β€” all direct subclasses are known at compile time, so a when over them can be exhaustive without an else branch. Perfect for modelling UI/result states where each state carries different data.

sealed class PaymentResult {
    object Loading : PaymentResult()
    data class Success(val transactionId: String) : PaymentResult()
    data class Error(val message: String) : PaymentResult()
}

fun handle(result: PaymentResult) = when (result) {
    is PaymentResult.Loading -> "Loading..."
    is PaymentResult.Success -> "Success: ${result.transactionId}"
    is PaymentResult.Error -> "Error: ${result.message}"
}
enum: fixed options, same shape (MONDAY, TUESDAY…). sealed: fixed types, but each carries different data (Loading vs Success(id) vs Error(msg)).
Q: Enum vs sealed class?
Enum = fixed set of constant values, all the same shape. Sealed class = fixed hierarchy of types, where each subtype can hold its own distinct data β€” better suited to modelling states.
11

Collections in Kotlin

Kotlin has three core collection types: List (ordered, duplicates allowed), Set (unique elements), Map (key-value pairs).

val fruits = listOf("Apple", "Banana", "Orange")       // immutable
val mutableFruits = mutableListOf("Apple", "Banana")
mutableFruits.add("Grapes")

val numbers = setOf(1, 2, 2, 3)                         // duplicates removed
val user = mapOf("name" to "Zulfikar", "city" to "Delhi")
println(fruits); println(mutableFruits); println(numbers); println(user["name"])
Output
[Apple, Banana, Orange]
[Apple, Banana, Grapes]
[1, 2, 3]
Zulfikar

Common operations

  • forEach β€” loop; filter β€” condition-based subset; map β€” transform each element; contains β€” membership check.
Q: Mutable vs Immutable collections?
listOf/setOf/mapOf are read-only interfaces β€” safe to share across threads since nothing can modify them through that reference. mutableListOf etc. expose add/remove/update. Note: an immutable reference to a mutable object can still have its contents changed via another mutable reference β€” only the reference type is read-only, not necessarily the underlying object.
12

Scope Functions

Execute a block of code within an object's context β€” five functions differing in receiver (this/it) and return value (object/result).

FunctionReceiverReturnsTypical use
letitlambda resultnull-checks, transforming
runthislambda resultobject config + compute a result
withthislambda resultgrouping calls on an existing object
applythisthe object itselfconfiguring an object (builder-style)
alsoitthe object itselfside effects: logging, debugging
val intent = Intent(this, MainActivity::class.java).apply {
    putExtra("USER_ID", 101)
}.also { println("Intent created: $it") }

val name = "Zulfikar"
name.let { println(it) }
Q: apply vs also?
Both return the same object. apply uses "this" (implicit, best for setting properties). also uses "it" (explicit, best for side effects like logging that reference the object by name).
Q: let vs apply?
let returns the lambda's result and uses "it". apply returns the same object and uses "this".
13

Delegation in Kotlin

One object hands responsibility for a task to another object, using the by keyword β€” favouring composition over inheritance.

ProblemDelegation solves it by
Inheritance = tight couplingComposition (loose coupling)
No multiple inheritanceDelegate to multiple objects
Code duplicationReuse a single delegate implementation

Class Delegation

interface Printer { fun print() }
class ConsolePrinter : Printer { override fun print() = println("Printing...") }
class Document(printer: Printer) : Printer by printer

Property Delegation: lazy, observable, vetoable

val lazyValue: String by lazy {
    println("Computing lazy value")
    "Hello, Kotlin!"
}
fun main() {
    println(lazyValue)   // "Computing..." printed, then value
    println(lazyValue)   // cached, no recompute
}
Output
Computing lazy value
Hello, Kotlin!
Hello, Kotlin!
Q: lazy vs lateinit?
lazy β†’ for val, computed once on first access, thread-safe by default. lateinit β†’ for var, must be assigned manually before use (e.g. Android Views set in onCreate).
Q: observable vs vetoable?
observable always accepts the new value, then notifies you after the change. vetoable lets you inspect the proposed change and reject it (return false) before it's applied.
14

Annotations

Metadata attached to code that the compiler or a runtime/annotation-processor can read, without changing the code's own logic.

  • @Deprecated β€” flags an API as outdated, with a replacement suggestion.
  • @Target β€” restricts where a custom annotation can be applied.
  • @JvmStatic β€” exposes a companion-object member as a true static method for Java callers.
  • @JvmOverloads β€” generates Java-callable overloads for a function with default parameters.
  • @Volatile β€” ensures a property's writes are immediately visible to other threads.
15

Reflection

Inspecting classes, functions, and properties at runtime via kotlin.reflect.

  • ::class β€” a KClass reference to a type at runtime.
  • KClass β€” exposes a class's properties/functions/constructors.
  • KFunction β€” lets you inspect and dynamically invoke a function.
16

DSL (Domain-Specific Languages)

A small, expressive mini-language built with normal Kotlin syntax (lambdas with receivers) for a specific domain β€” configuration, UI building, scripting.

Example β€” a simple HTML DSL
class Body { private val children = mutableListOf()
    fun p(text: String) { children.add("

$text

") } fun content() = children.joinToString("") } class HTML { private val elements = mutableListOf() fun body(block: Body.() -> Unit) { val body = Body(); body.block() elements.add("${body.content()}") } fun render() = "${elements.joinToString("")}" } fun html(block: HTML.() -> Unit): HTML { val h = HTML(); h.block(); return h } fun main() { val page = html { body { p("Hello from Kotlin DSL!"); p("Second paragraph.") } } println(page.render()) }
Output
<html><body><p>Hello from Kotlin DSL!</p><p>Second paragraph.</p></body></html>

Real-life DSL examples

  • Gradle Kotlin DSL β€” android {}, dependencies {}
  • Jetpack Compose β€” Column {}, Text()
  • Kotlinx HTML, SQL DSL libraries (Exposed)
17

Coroutines β€” the basics

Lightweight, non-blocking concurrency: coroutines can suspend without blocking the underlying thread, so thousands can run on a handful of OS threads.

Ek thread mein bahut saare tasks non-blocking tarike se chalte hain β€” CPU time save hota hai, app responsive rehta hai.

Suspend Functions

suspend marks a function that can pause (e.g. during delay or a network call) and resume later, without blocking the thread. Callable only from a coroutine or another suspend function.

suspend fun doLongTask() {
    println("Task start hua")
    delay(1000)
    println("Task complete hua")
}

Coroutine Builders

fun main() = runBlocking {
    val deferred = async { delay(1000); "Result mil gaya" }
    println("Wait kar raha hoon")
    println(deferred.await())
}
Output
Wait kar raha hoon
Result mil gaya
  • launch β€” fire-and-forget, returns a Job, no result.
  • async/await β€” returns a Deferred<T>, use when you need a result.
  • runBlocking β€” blocks the current thread until its coroutine finishes (mainly for main() / tests β€” never use in production Android UI code).
GlobalScope launches a coroutine detached from any lifecycle β€” it keeps running even if the screen closes (leak risk). Prefer a lifecycle-bound scope (viewModelScope, lifecycleScope) instead.
18

Coroutine Context & Dispatchers

A CoroutineContext is an immutable, indexed set of elements defining how/where a coroutine runs β€” includes a Job, a Dispatcher, and optionally a CoroutineName.

  • Dispatchers.Main β€” the UI thread; needed for touching Views.
  • Dispatchers.IO β€” optimized, elastic pool for blocking I/O (network, disk, DB).
  • Dispatchers.Default β€” CPU-bound work, sized to the number of cores (sorting, parsing, heavy computation).
  • Dispatchers.Unconfined β€” starts on the caller's thread, resumes on whatever thread the suspension point returns on; mostly for internal/testing use.
suspend fun loadData() {
    val data = withContext(Dispatchers.IO) { fetchFromNetwork() }
    withContext(Dispatchers.Main) { updateUI(data) }
}

withContext is a suspend function that switches the coroutine's dispatcher for a block, then switches back automatically.

19

Job, Cancellation & Structured Concurrency

A Job is a cancellable handle to a coroutine's lifecycle.

  • cancel() β€” requests cancellation (cooperative β€” the coroutine must check isActive or hit a suspension point to actually stop).
  • join() β€” suspends the caller until the coroutine completes.
  • isActive β€” whether the coroutine is still running.

Structured Concurrency

A parent coroutine scope doesn't complete until all its children complete, and cancelling the parent automatically cancels all children β€” this prevents leaked/orphaned coroutines.

fun main() = runBlocking {
    val job = launch {
        repeat(5) { i ->
            if (!isActive) return@launch
            println("Working: $i")
            delay(500)
        }
    }
    delay(1300)
    job.cancel()
    job.join()
    println("Completed: ${!job.isActive}")
}
Output
Working: 0
Working: 1
Working: 2
Completed: true
20

Flow, StateFlow & SharedFlow

Flow represents an asynchronous stream of values, built on coroutines.

A water tap dripping slowly β€” each drop is a value. Nothing flows until you put your cup under it (collect).

  • Cold stream β€” code only runs when a collector subscribes; each collector gets its own independent run.
  • Hot stream β€” emits regardless of listeners (StateFlow, SharedFlow, Channel).
fun simpleFlow(): Flow = flow {
    for (i in 1..5) { delay(100); emit(i) }
}
fun main() = runBlocking {
    simpleFlow().collect { println("Received $it") }
}
Output
Received 1
Received 2
Received 3
Received 4
Received 5
FeatureStateFlowSharedFlow
Holds a value?Yes β€” always has a current valueNo β€” pure event stream by default
ReplayAlways replays the latest valueConfigurable (0 or more)
Use caseUI state (loading, form data)One-off events (toast, navigation)

Flow Operators

  • map β€” transform; filter β€” condition; catch β€” handle upstream exceptions; onEach β€” side effects like logging.
21

Channels & SharedFlow β€” communication between coroutines

A Channel is a hot, queue-like primitive: one coroutine send()s, another receive()s (or iterates with for). close() stops further sends.

val channel = Channel()
launch {
    for (i in 1..3) { channel.send(i) }
    channel.close()
}
launch {
    for (v in channel) println("Received $v")
}
Output
Received 1
Received 2
Received 3

SharedFlow broadcasts one stream to many subscribers, with a configurable replay of past values for late subscribers β€” good for events multiple screens need.

22

Room

Room is Jetpack's persistence library β€” an abstraction over SQLite with compile-time query validation, less boilerplate, and Coroutine/Flow/LiveData support.

ComponentDescriptionAnnotation
EntityRepresents a table@Entity
DAOContains SQL queries@Dao
DatabaseConnects app to SQLite@Database
@Entity(tableName = "user")
data class User(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String, val email: String
)

@Dao
interface UserDao {
    @Insert suspend fun insertUser(user: User)
    @Query("SELECT * FROM user") fun getAllUsers(): Flow>
    @Delete suspend fun deleteUser(user: User)
}

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

Singleton database instance

object RoomDB {
    @Volatile private var INSTANCE: AppDatabase? = null
    fun getDatabase(context: Context): AppDatabase =
        INSTANCE ?: synchronized(this) {
            INSTANCE ?: Room.databaseBuilder(
                context.applicationContext, AppDatabase::class.java, "my_database"
            ).build().also { INSTANCE = it }
        }
}

Migration

Safely updates the schema without wiping data.

val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE user ADD COLUMN age INTEGER DEFAULT 0 NOT NULL")
    }
}
23

Dagger 2 & Hilt (Dependency Injection)

Dependency Injection: an object receives its dependencies from outside instead of creating them itself β€” improves testability, reduces coupling.
  • @Inject β€” tells Dagger where to inject a dependency (constructor/field).
  • @Module β€” a class that provides dependencies.
  • @Provides β€” a method describing how to create an object.
  • @Component β€” connects providers to consumers, builds the dependency graph.

Manual DI means creating and wiring objects yourself β€” messy at scale. Dagger builds a compile-time dependency graph, catching wiring errors at build time instead of runtime.

Hilt new

Hilt is Google's opinionated layer on top of Dagger, built specifically for Android. It removes most of Dagger's boilerplate (components, modules for Android classes) via annotations.

@HiltAndroidApp
class MyApp : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity()

@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel()
Q: Dagger vs Hilt?
Hilt is built on Dagger β€” same compile-time graph and performance β€” but predefines standard Android components (Application, Activity, Fragment, ViewModel scopes) so you don't hand-write Components/Modules for each one. Most new Android projects use Hilt over raw Dagger today.
24

LiveData & Data Binding

LiveData is a lifecycle-aware observable holder β€” it only delivers updates to active (STARTED/RESUMED) observers, avoiding crashes/leaks from updating a destroyed UI.

  • Lifecycle-aware β€” auto unsubscribes on destroy.
  • Thread-safe writes from a background thread via postValue() (vs setValue() on the main thread).

Data Binding

Binds UI components directly to data sources in XML layouts, removing findViewById boilerplate and keeping view logic declarative.

25

RxJava 2

A Reactive Extensions library for composing asynchronous, event-based programs using observable streams β€” predates widespread coroutine adoption, still common in legacy codebases.

  • Observable, Observer, subscribe(), Operators, Schedulers (thread management)
Example
Observable observable = Observable.just(1, 2, 3);
observable.subscribe(new Observer() {
    public void onSubscribe(Disposable d) { System.out.println("Subscribed!"); }
    public void onNext(Integer n) { System.out.println("Received: " + n); }
    public void onError(Throwable e) { System.out.println("Error: " + e.getMessage()); }
    public void onComplete() { System.out.println("Completed!"); }
});
Output
Subscribed!
Received: 1
Received: 2
Received: 3
Completed!
Q: RxJava vs Coroutines/Flow?
Both handle async streams. Coroutines+Flow are Kotlin-native, generally lighter weight, and integrate directly with structured concurrency β€” most new code prefers Flow. RxJava remains widely used in older/larger codebases and has a more mature operator ecosystem in some areas.
26

Multi-Module apps

Splitting a big app into smaller, independently-buildable modules by responsibility β€” improves build times (parallel/incremental compilation), enforces boundaries, and enables code reuse.

  • App Module β€” assembles everything into the final APK.
  • Feature Module β€” a specific feature (login, profile).
  • Library/Core Module β€” shared utilities, network, DI, base classes.
MyApp/
β”œβ”€β”€ app/
β”œβ”€β”€ core/
β”œβ”€β”€ network/
β”œβ”€β”€ feature_login/
└── feature_profile/
27

MVVM & ViewModel new

MVVM (Model-View-ViewModel) separates UI (View) from business/UI logic (ViewModel) and data (Model/Repository). The View observes state exposed by the ViewModel; it never talks to the Model directly.

  • View (Activity/Fragment/Compose) β€” renders state, forwards user actions.
  • ViewModel β€” holds UI state, survives configuration changes (rotation), exposes state via LiveData/StateFlow.
  • Model/Repository β€” single source of truth, abstracts data sources (Room, network).
class UserViewModel(private val repo: UserRepository) : ViewModel() {
    private val _state = MutableStateFlow(UiState.Loading)
    val state: StateFlow = _state

    fun loadUser(id: Int) = viewModelScope.launch {
        _state.value = UiState.Loading
        _state.value = try {
            UiState.Success(repo.getUser(id))
        } catch (e: Exception) {
            UiState.Error(e.message ?: "Unknown error")
        }
    }
}

viewModelScope is automatically cancelled when the ViewModel is cleared β€” no manual cleanup needed.

Q: Why does ViewModel survive rotation?
The ViewModel is scoped to the Activity/Fragment's lifecycle via ViewModelStore, which the framework retains across a configuration-change recreation β€” only the View is destroyed and recreated, not the ViewModel.
28

Retrofit & networking new

Retrofit is a type-safe HTTP client for Android/Java β€” it turns an interface with annotations into a working REST API client, backed by OkHttp.

interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") id: Int): User
}

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val api = retrofit.create(ApiService::class.java)

A suspend Retrofit function runs on Dispatchers.IO internally and can be called directly from a coroutine, without callbacks.

Q: Interceptors?
OkHttp interceptors hook into every request/response β€” commonly used for logging, adding auth headers, or retry logic.
29

Jetpack Compose basics new

Compose is Android's modern, declarative UI toolkit β€” you describe what the UI should look like for a given state, and Compose handles updating the actual UI when that state changes (instead of manually mutating Views).

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) { Text("Increment") }
    }
}
  • @Composable β€” marks a function as UI-emitting.
  • remember β€” keeps a value alive across recompositions.
  • mutableStateOf β€” an observable value; changing it triggers recomposition of anything reading it.
  • Recomposition β€” Compose re-runs only the composables that read changed state, not the whole tree.
Q: XML Views vs Compose?
XML/View system is imperative β€” you fetch a View and mutate it. Compose is declarative β€” UI is a function of state, and the framework diffs and updates automatically, reducing whole classes of state-sync bugs.
30

Memory Leaks, ANR & Performance new

Memory leaks

Happens when an object (often an Activity/Fragment/View) is kept referenced longer than its intended lifecycle, so the garbage collector can't reclaim it.

  • Common cause: a static field, singleton, or long-lived listener holding an Activity/Context reference.
  • Common cause: a coroutine on GlobalScope or an unmanaged callback still holding a UI reference after the screen is destroyed.
  • Fix: use applicationContext where a long-lived reference is needed, use lifecycle-scoped coroutines, and unregister listeners in onDestroy.

ANR (Application Not Responding)

Triggered when the main thread is blocked for too long (~5s for input events) β€” usually from heavy work (I/O, big loops, synchronous network calls) running on the main thread instead of a background dispatcher.

Fix pattern: move blocking work to Dispatchers.IO/Default, keep the main thread only for UI updates via withContext(Dispatchers.Main).
Q: How would you detect a memory leak?
Tools like LeakCanary (auto-detects retained Activity/Fragment instances), Android Studio Profiler's memory view, and heap dumps analyzed for unexpected retained references.
31

WorkManager & background work new

WorkManager is Jetpack's API for deferrable, guaranteed background work that should survive app restarts and device reboots (e.g. syncing data, uploading logs) β€” it picks the best underlying mechanism (JobScheduler, AlarmManager, etc.) for the OS version automatically.

class SyncWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
    override suspend fun doWork(): Result {
        return try {
            syncData()
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

val request = OneTimeWorkRequestBuilder()
    .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
    .build()
WorkManager.getInstance(context).enqueue(request)
Q: WorkManager vs a coroutine in viewModelScope?
viewModelScope work dies with the ViewModel/process. WorkManager persists across process death and reboot, and supports constraints (network, charging, idle) β€” use it for guaranteed deferred work, not for work tied to a visible screen.
32

Self introduction

"Hello, my name is Zulfikar. I am an Android Developer with over 4+ years of experience building mobile applications using Java and Kotlin. I've worked on API integration, push notifications, pagination, ViewModel-LiveData/StateFlow architecture, and payment-related modules. I focus on writing clean, maintainable code and improving app performance. I enjoy learning new technologies, solving problems, and building user-friendly apps. I'm excited about this opportunity because I really enjoy working on scalable products that impact millions of users."