In short
Android app development with Kotlin is the building of applications for the Android operating system using Kotlin, the statically typed JVM language that Google declared "Kotlin-first" for Android in 2019 and which is now the default language of Android Studio templates. A student must know Kotlin's language features (null safety, data classes, coroutines), the Android component model (Activity, Service, BroadcastReceiver, ContentProvider), lifecycle handling, UI with XML layouts and Jetpack Compose, local storage with Room, and networking with Retrofit. The topic carries heavy weight in MCA mobile-computing papers, in lab vivas and in placement rounds at Indian product and services firms — Flipkart, Swiggy, Zomato, Paytm, PhonePe, Razorpay, Jio, Zoho, TCS, Infosys, Accenture — where Android questions are usually lifecycle, memory-leak and coroutine-concurrency questions in disguise. India's large Android user base (the overwhelming majority of smartphones here run Android) means almost every Indian consumer product ships an Android app first.
Explained
Kotlin language essentials examined in Android papers
- Static, JVM-targeted, 100% interoperable with Java: Kotlin compiles to the same bytecode (or to DEX via D8/R8), so Java libraries and Kotlin code mix in one module.
- **
valvsvar:**valis a read-only reference (not a deep-immutable object);varis reassignable. Type inference removes most type declarations. - Null safety: types are non-nullable by default;
String?is nullable. Operators — safe call?., Elvis?:(default value), not-null assertion!!(throwsNullPointerException), safe castas?,letfor scoped null handling. This moves most NPEs from runtime to compile time;lateinit vardefers initialisation of non-nullvarproperties (views, injected objects). - **
data class:** auto-generatesequals(),hashCode(),toString(),copy()andcomponentN()for destructuring — used for API models and UI state. - **
sealed class/sealed interface:** a closed hierarchy, so awhenover it is exhaustive withoutelse— the standard way to modelLoading / Success / Errorscreen states. - **
object= singleton;companion object** = class-level members (Kotlin has nostatic); extension functions add methods to existing types without inheritance; higher-order functions and lambdas with trailing-lambda syntax give DSLs such as Gradle Kotlin DSL and Compose. - Scope functions:
let,run,with,apply(returns receiver — configuring objects),also(returns receiver — logging/side effects). - Collections:
listOf/mutableListOf,map,filter,groupBy,sortedBy,fold; sequences evaluate lazily for long chains. - Delegation:
by lazy { }for lazy init,by viewModels()for ViewModel scoping,by Delegates.observable.
Android platform architecture and components
- Stack (bottom-up): Linux kernel → hardware abstraction layer (HAL) → native libraries + ART (Android Runtime, AOT+JIT with DEX bytecode) → Java/Kotlin API framework → system and user apps.
- **Four app components, all declared in
AndroidManifest.xml:**- Activity — one screen with a UI; entry point via an intent filter with
MAIN/LAUNCHER. - Service — long work without UI; a foreground service shows a mandatory notification; background limits since Android 8 push most work to WorkManager or JobScheduler.
- BroadcastReceiver — responds to system or app broadcasts (boot completed, connectivity change); implicit broadcasts mostly cannot be declared in the manifest since Android 8.
- ContentProvider — exposes structured data across apps via a
content://URI (Contacts, MediaStore).
- Activity — one screen with a UI; entry point via an intent filter with
- Intents: explicit (names the target component) and implicit (declares an action, category and data; resolved against intent filters). Extras are passed as key-value pairs;
startActivity,startService,sendBroadcast, and the Activity Result APIs (registerForActivityResult) replace the deprecatedonActivityResult. - Fragment — a reusable, lifecycle-owning portion of UI hosted by an Activity; managed by
FragmentManager, navigated with Jetpack Navigation (nav graph,NavController, Safe Args). Single-Activity architecture is the modern default. - Resources:
res/layout,res/values/strings.xml,drawable,mipmap; qualifiers (values-hi,layout-land,drawable-xxhdpi,values-sw600dp) give configuration-specific resources — the mechanism for Hindi and other Indian-language localisation and for RTL (Urdu). - Density independence: dp for lengths, sp for text (scales with the user's font-size setting); buckets ldpi/mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi with mdpi = 160 dpi as baseline.
- Permissions: normal permissions granted at install; dangerous ones (camera, location, contacts, nearby devices) requested at runtime since Android 6 (
ActivityCompat.requestPermissionsorActivityResultContracts.RequestPermission).targetSdkVersiongoverns which behaviour changes apply; Play Store enforces a minimum target SDK that rises each year.
Lifecycles — the most examined area
- Activity lifecycle:
onCreate→onStart→onResume→ (running) →onPause→onStop→onDestroy;onRestartafteronStopwhen returning.onSaveInstanceStatestores a smallBundlebefore the process may be killed. - Configuration change (rotation, dark mode, locale, window resize) destroys and recreates the Activity by default; survive it with a ViewModel and
SavedStateHandle, not with static fields. - Fragment lifecycle adds
onAttach,onCreateView,onViewCreated,onDestroyView,onDetach; the view lifecycle is shorter than the fragment's, so observe withviewLifecycleOwnerand null out the binding inonDestroyView. - Process death: Android may kill a backgrounded process under memory pressure; state must be restorable from
SavedStateHandleor disk. - Lifecycle-aware components:
LifecycleOwner,DefaultLifecycleObserver,lifecycleScope,repeatOnLifecycle(Lifecycle.State.STARTED)for collecting flows safely. - Main thread rule: UI work only on the main (UI) thread; blocking it beyond ~5 s triggers an ANR (Application Not Responding); network or disk I/O on the main thread throws
NetworkOnMainThreadException/ strict-mode violations.
Concurrency with coroutines
- Coroutine = a suspendable computation, far cheaper than a thread; a **
suspendfunction** can pause without blocking its thread (compiled via a continuation-passing state machine). - Dispatchers:
Dispatchers.Main(UI),Dispatchers.IO(disk/network, large elastic pool),Dispatchers.Default(CPU-bound, pool ≈ number of cores),Unconfined(rarely used). - Structured concurrency: every coroutine belongs to a
CoroutineScope; cancelling the scope cancels children. Android scopes:viewModelScope(cleared inonCleared),lifecycleScope,GlobalScope(avoid — leaks). - Builders:
launch(fire-and-forget, returnsJob),async/await(returnsDeferred, for parallel work),withContext(dispatcher)(switch thread, returns a value),coroutineScope/supervisorScope(child failure isolation). - Flow: a cold asynchronous stream;
StateFlow(hot, always has a value — replacesLiveDatain new code),SharedFlow(events), operatorsmap,filter,combine,debounce,flatMapLatest,catch,stateIn. - Cancellation is cooperative: long CPU loops must check
isActiveor callyield();CancellationExceptionmust not be swallowed by a blanketcatch (e: Exception).
UI: XML views and Jetpack Compose
- View system:
ViewGrouplayouts —LinearLayout,FrameLayout,ConstraintLayout(flat hierarchy, constraints, guidelines, chains — the recommended XML layout),CoordinatorLayout; widgetsTextView,EditText,Button,ImageView. - **
RecyclerView:** recycles item views through aViewHolder; needs anAdapter(onCreateViewHolder,onBindViewHolder,getItemCount) and aLayoutManager;ListAdapter+DiffUtilcomputes minimal updates;Paging 3for paged lists. - View binding generates a typed binding class per layout and replaces
findViewById; data binding adds expressions in XML (now de-emphasised). - Jetpack Compose: declarative UI in Kotlin —
@Composablefunctions describe UI as a function of state; recomposition re-runs only affected composables.- State:
mutableStateOf+remember,rememberSaveable(survives configuration change), state hoisting (stateless composable + state in the caller/ViewModel) for testability. - Building blocks:
Column,Row,Box,LazyColumn/LazyRow(the Compose RecyclerView),Modifierchain (order matters:padding().background()≠background().padding()),Scaffold, Material 3 theming with dynamic colour. - Side effects:
LaunchedEffect(key),rememberCoroutineScope,DisposableEffect,derivedStateOf.
- State:
- Material Design 3 components and accessibility: content descriptions, 48 dp minimum touch target, adequate colour contrast, TalkBack support.
Architecture, data layer and dependency injection
- Recommended layering (Google's guide to app architecture): UI layer (Compose/Views + ViewModel) → domain layer (optional use cases) → data layer (repository + data sources). Single source of truth, unidirectional data flow, UI state as an immutable data/sealed class.
- MVVM: View observes
StateFlow/LiveDataexposed by the ViewModel, which survives configuration changes and holds noContext/Viewreference. MVI/MVP are variants asked in comparison questions. - Room: compile-time-verified SQLite ORM —
@Entity,@Dao(@Query,@Insert,@Update,@Delete,@Transaction),@Database; returnsFlowfor observable queries; migrations are mandatory when the schema version changes. - Other storage:
DataStore(Preferences/Proto, coroutine-based; replacesSharedPreferences), internal/external files,MediaStoreand the Storage Access Framework under scoped storage, EncryptedFile/EncryptedSharedPreferencesand the Android Keystore for secrets. - Networking: Retrofit over OkHttp with a converter (Moshi/kotlinx.serialization, or Gson); interceptors for auth headers and logging;
suspendinterface functions; HTTPS enforced by the default network security config, certificate pinning for payment apps. - Dependency injection: Hilt (annotation-processed Dagger wrapper —
@HiltAndroidApp,@AndroidEntryPoint,@Inject,@Module,@Provides, scopes@Singleton,@ViewModelScoped) or Koin (service-locator DSL); manual constructor injection for small apps. - Background work: WorkManager for deferrable guaranteed work (constraints on network, charging;
OneTimeWorkRequest,PeriodicWorkRequestwith a minimum interval of 15 minutes), AlarmManager for exact time, FCM for push.
Build, testing, release and Indian compliance
- Gradle with the Android Gradle Plugin;
build.gradle.ktsholdscompileSdk,minSdk,targetSdk,versionCode,versionName,buildTypes(debug/release),productFlavors,signingConfigs; version catalogues (libs.versions.toml) centralise dependencies. - Build pipeline: Kotlin/Java sources → class files → D8 desugaring to DEX → R8 (shrink, obfuscate, optimise —
minifyEnabled truewith ProGuard/R8 rules) → resources via AAPT2 → signed APK or AAB (Android App Bundle, mandatory for new Play Store apps; Play generates per-device split APKs). - Testing: unit tests (JUnit, MockK,
kotlinx-coroutines-testwithrunTestandTestDispatcher, Turbine for flows), instrumented tests (AndroidX Test, Espresso, Compose UI test withcreateComposeRule),InstantTaskExecutorRulefor LiveData, Firebase Test Lab / emulators for device matrix. - Profiling and quality: Android Studio Profiler (CPU, memory, network, energy), LeakCanary for leaks, StrictMode, Baseline Profiles and App Startup for cold-start time, Crashlytics and Play Vitals for field crash/ANR rates.
- Indian regulatory and market context:
- Digital Personal Data Protection Act, 2023 governs processing of personal digital data — notice, consent, purpose limitation, data-principal rights, breach notification; its rules were notified in 2025 with phased compliance timelines, so check the current stage before relying on a specific date. Practically: ask for the minimum permissions, declare them in the Play Data Safety form, and keep a privacy policy.
- RBI rules shape fintech apps: tokenisation of card data (no raw card storage on device/app servers), two-factor authentication, UPI apps built on NPCI specifications and the UPI Deep Link/Intent flow, and RBI's digital-lending directions on app-based lending.
- Aadhaar-based eKYC and DigiLocker/Account Aggregator integrations are common; Aadhaar authentication by private entities is restricted and permission-based, so verify current eligibility before designing it in.
- Device reality in India: entry-level RAM, intermittent 4G, large Hindi/regional-language user base and data-cost sensitivity — hence APK/AAB size budgets, offline-first Room caching, image compression and
Lite/Gobuilds.
Key points
- Kotlin-first - Google's preferred Android language since 2019; Kotlin compiles to the same bytecode as Java and interoperates fully.
- Null safety - non-nullable by default;
?.,?:,as?,leteliminate most NullPointerExceptions at compile time;!!reintroduces them. - data class - generates
equals,hashCode,toString,copy, destructuring; the standard API/UI-state model. - sealed class - closed hierarchy giving exhaustive
when; used for Loading/Success/Error UI states. - Four components - Activity (screen), Service (no UI), BroadcastReceiver (events), ContentProvider (cross-app data), all manifest-declared.
- Intent - explicit names a component; implicit declares action/data and is matched to intent filters.
- Activity lifecycle - onCreate, onStart, onResume, onPause, onStop, onDestroy (+onRestart); rotation destroys and recreates by default.
- ViewModel - survives configuration change, holds UI state, never holds a View or Activity Context; scoped via
viewModelScope. - Coroutines - lightweight suspendable tasks;
withContext(Dispatchers.IO)for I/O, structured concurrency cancels children with the scope. - StateFlow vs LiveData - both lifecycle-friendly observables; StateFlow is pure Kotlin, always has a value, and needs
repeatOnLifecycleto collect safely. - RecyclerView - ViewHolder recycling + Adapter + LayoutManager;
DiffUtilupdates only changed items. Compose equivalent:LazyColumn. - Jetpack Compose - declarative UI; recomposition on state change,
remember/rememberSaveable, state hoisting, orderedModifierchain. - Room - compile-time-checked SQLite layer with
@Entity/@Dao/@Database; schema bumps need migrations. - Retrofit + OkHttp - typed REST client with
suspendfunctions, converters and interceptors; HTTPS by default. - WorkManager - guaranteed deferrable background work with constraints; periodic minimum 15 minutes.
- dp and sp - dp for layout (density-independent, mdpi 160 dpi baseline), sp for text so it respects user font scaling.
- AAB over APK - Play Store requires App Bundles for new apps; R8 shrinks and obfuscates release builds.
- ANR - main thread blocked about 5 seconds (or ~10 s for a broadcast); avoided by moving work off the main thread.
Formulas and examples
res/layout-sw600dp applies when min(widthDp, heightDp) ≥ 600dp, where widthDp = widthPx × 160 / dpiasync/await in parallel, total ≈ max(tᵢ) + overheadExample 1 — dp to px on three Indian handsets. A button is specified 48 dp tall (the Material minimum touch target).
- mdpi (160 dpi): px = 48 × 160/160 = 48 px
- hdpi (240 dpi): px = 48 × 240/160 = 48 × 1.5 = 72 px
- xxhdpi (480 dpi): px = 48 × 480/160 = 48 × 3.0 = 144 px
Same physical size (48/160 = 0.3 inch ≈ 7.6 mm) on every device — which is the whole point of dp.
Example 2 — density bucket and tablet layout decision. A phone is 1080 × 2400 px on a 6.5-inch diagonal.
- Diagonal in px = √(1080² + 2400²) = √(1,166,400 + 5,760,000) = √6,926,400 ≈ 2631.8 px
- dpi = 2631.8 / 6.5 ≈ 404.9 dpi → falls in the xxhdpi (480) bucket's neighbourhood; Android reports density = 2.5 (
DENSITY_400, i.e. 400/160), so usedrawable-xxhdpiassets. - widthDp = 1080 × 160 / 404.9 ≈ 426.8 dp → less than 600 dp, so
res/layout-sw600dpdoes not apply; the phone uses the default layout.
Example 3 — parallel vs sequential coroutines. A dashboard needs profile (300 ms), orders (500 ms) and offers (400 ms) from three endpoints.
// Sequential: 300 + 500 + 400 = 1200 ms
val p = api.profile(); val o = api.orders(); val f = api.offers()
// Parallel: bounded by the slowest call
suspend fun load() = coroutineScope {
val p = async { api.profile() } // 300 ms
val o = async { api.orders() } // 500 ms
val f = async { api.offers() } // 400 ms
Dashboard(p.await(), o.await(), f.await()) // ≈ 500 ms
}
- Sequential = 1,200 ms; parallel ≈ 500 ms (max of 300, 500, 400) + small overhead.
- Saving = 1200 − 500 = 700 ms → 58.3% faster perceived load.
Example 4 — ViewModel + StateFlow + Room, a complete vertical slice.
@Entity(tableName = "orders")
data class Order(
@PrimaryKey val id: String,
val amountPaise: Long, // store money in paise, never Double
val placedAt: Long
)
@Dao
interface OrderDao {
@Query("SELECT * FROM orders ORDER BY placedAt DESC")
fun observeAll(): Flow<List<Order>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(orders: List<Order>)
}
sealed interface OrdersUiState {
data object Loading : OrdersUiState
data class Success(val orders: List<Order>, val totalRupees: String) : OrdersUiState
data class Error(val message: String) : OrdersUiState
}
@HiltViewModel
class OrdersViewModel @Inject constructor(
private val repo: OrderRepository
) : ViewModel() {
val state: StateFlow<OrdersUiState> = repo.observeOrders()
.map { list ->
val paise = list.sumOf { it.amountPaise }
OrdersUiState.Success(list, "₹%.2f".format(paise / 100.0))
}
.catch { emit(OrdersUiState.Error(it.message ?: "Unknown error")) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), OrdersUiState.Loading)
fun refresh() = viewModelScope.launch {
withContext(Dispatchers.IO) { repo.syncFromNetwork() } // Retrofit suspend call
}
}
@Composable
fun OrdersScreen(vm: OrdersViewModel = hiltViewModel()) {
val state by vm.state.collectAsStateWithLifecycle()
when (val s = state) {
OrdersUiState.Loading -> CircularProgressIndicator()
is OrdersUiState.Error -> Text(s.message)
is OrdersUiState.Success -> LazyColumn {
items(s.orders, key = { it.id }) { Text("${it.id} — ${s.totalRupees}") }
}
}
}
- Money check: three orders of 49,900, 129,950 and 25,000 paise → total 204,850 paise = ₹2,048.50.
Example 5 — release size after R8. A debug build is 18.4 MB; R8 shrinking removes 42% of DEX and resource shrinking removes 30% of a 6.0 MB resource set; DEX was 9.0 MB, native libs 3.4 MB for two ABIs.
- DEX after R8 = 9.0 × 0.58 = 5.22 MB; resources after shrinking = 6.0 × 0.70 = 4.20 MB
- Universal APK = 5.22 + 4.20 + 3.40 = 12.82 MB
- As an AAB, the device downloads one ABI (1.7 MB) and one density slice (say 55% of resources = 2.31 MB): 5.22 + 2.31 + 1.70 = 9.23 MB → 28% smaller than the universal APK.
| Aspect | XML Views | Jetpack Compose |
|---|---|---|
| Paradigm | Imperative — inflate then mutate views | Declarative — UI = f(state) |
| Lists | RecyclerView + Adapter + DiffUtil | LazyColumn with key |
| State restore | onSaveInstanceState, ViewModel | rememberSaveable, ViewModel |
| Styling | themes/styles XML, Material components | Material 3 theming in Kotlin |
| Boilerplate | High (adapters, bindings) | Low |
| Interop | Hosts Compose via ComposeView | Hosts views via AndroidView |
| Concern | LiveData | StateFlow | SharedFlow |
|---|---|---|---|
| Library | Android-only (lifecycle) | Pure Kotlin coroutines | Pure Kotlin coroutines |
| Initial value | Optional (may be null) | Mandatory | None |
| Lifecycle awareness | Built in | Needs collectAsStateWithLifecycle / repeatOnLifecycle | Same |
| Conflation | Latest value only | Latest value, distinct-until-changed | Configurable replay |
| Best for | Legacy code, Java interop | Screen state | One-shot events (toast, navigation) |
| Storage option | Use for | Notes |
|---|---|---|
| DataStore (Preferences) | Small key-value settings | Coroutine/Flow API; replaces SharedPreferences |
| Room | Structured relational data | Compile-time SQL checks, Flow queries, migrations |
| Internal files | App-private files | Auto-deleted on uninstall, no permission needed |
| MediaStore / SAF | User media and documents | Scoped storage; no broad file permission |
| EncryptedSharedPreferences / Keystore | Tokens, keys | Hardware-backed keys where available |
Where it is used
- Consumer apps and e-commerce: Flipkart, Myntra, Meesho, Swiggy, Zomato, Nykaa — offline-first caching, Paging 3 feeds, Compose migrations, deep links from notifications and ads.
- Fintech and payments: PhonePe, Paytm, Google Pay, Razorpay, CRED, Groww, Zerodha Kite — UPI intent flows on NPCI specs, RBI tokenisation, certificate pinning, root/tamper detection, biometric authentication via
BiometricPrompt. - Government and public services: DigiLocker, UMANG, mAadhaar, IRCTC Rail Connect, DigiYatra, state transport and utility apps — multilingual resources, low-end device support, DPDP-compliant consent screens.
- IT services and product engineering roles: TCS, Infosys, Wipro, Accenture, LTIMindtree, Zoho, Jio Platforms hire Android engineers (typical fresher titles: Android Developer, Mobile Application Developer, SDE-1 Mobile) for client apps, SDK work and app modernisation from Java to Kotlin/Compose.
- Enterprise and field apps: logistics and last-mile delivery apps, field-force and insurance survey apps, EdTech (BYJU'S, Unacademy, PhysicsWallah) with downloadable video and WorkManager sync; Android Enterprise managed devices and kiosk mode.
- Beyond phones: Wear OS, Android TV (OTT apps like JioHotstar, SonyLIV), Android Automotive, and Kotlin Multiplatform sharing business logic with iOS.
Interview questions
- Q: Why did Google make Kotlin the preferred language for Android? A: Kotlin gives compile-time null safety, far less boilerplate (data classes, extension functions), coroutines for asynchronous code, and full interoperability with existing Java code and libraries.
- Q: What exactly happens to an Activity when the device is rotated? A: By default the Activity is destroyed (
onPause→onStop→onDestroy) and recreated (onCreate→onStart→onResume); transient state is restored from theonSaveInstanceStateBundle and larger state from a ViewModel. - Q: Why must a ViewModel never hold a reference to an Activity or View? A: The ViewModel outlives configuration changes, so holding a destroyed Activity leaks it and its whole view hierarchy; use the Application context or inject dependencies instead.
- Q: Difference between
launchandasync? A:launchstarts a coroutine for side effects and returns aJob;asyncreturns aDeferred<T>whoseawait()gives a result, so it is used for parallel work that produces values. - Q: What is structured concurrency and why does it matter on Android? A: Every coroutine has a parent scope, so cancelling the scope cancels all children —
viewModelScopeandlifecycleScopetherefore stop in-flight work automatically when the screen dies, preventing leaks and crashes. - Q: StateFlow or LiveData for new code, and why? A: StateFlow — it is pure Kotlin (testable, multiplatform), always has a current value and composes with Flow operators; it only needs
collectAsStateWithLifecycle/repeatOnLifecycleto be lifecycle-safe. - Q: How does RecyclerView improve on ListView? A: It recycles a small pool of ViewHolders through a pluggable LayoutManager and updates only changed items via DiffUtil, instead of re-inflating and re-binding rows wholesale.
- Q: What is recomposition in Compose, and how do you avoid doing it too often? A: Recomposition is re-executing composables whose state changed; keep state low and read it late, hoist state, use stable/immutable types,
keyin lazy lists, andderivedStateOffor computed values. - Q: Explain
lateinitversusby lazy. A:lateinitis for a non-nullvarinitialised later by the framework (throws if read first) and cannot be used on primitives;by lazyis a thread-safe read-onlyvalcomputed on first access. - Q: What causes an ANR and how do you diagnose one? A: Blocking the main thread for roughly 5 seconds (input dispatch) — caused by disk/network/JSON work or lock contention on the UI thread; diagnose from the ANR trace in Logcat/Play Vitals and the CPU profiler, and fix by moving work to
Dispatchers.IO/Default. - Q: Why does Play require an AAB instead of an APK? A: The bundle lets Google Play generate optimised split APKs per device — one ABI, one screen density, one language — so users download much less than a universal APK.
- Q: How would you keep a payment app's data compliant in India? A: Collect the minimum personal data with clear consent and a privacy policy (DPDP Act, 2023 and its rules), never store raw card data (RBI tokenisation), keep tokens in EncryptedSharedPreferences/Keystore, enforce HTTPS with certificate pinning, and declare everything accurately in the Play Data Safety form.
- Q: What is the difference between a Service, a foreground service and WorkManager? A: A plain background Service is heavily restricted since Android 8; a foreground service does user-visible ongoing work with a mandatory notification; WorkManager schedules deferrable guaranteed work with constraints and survives process death and reboots.
Common mistakes
- Writing the Activity lifecycle without
onRestart, or claimingonDestroyis always called — the process can be killed without it. - Doing network, database or heavy JSON work on the main thread, then blaming the library when the app shows an ANR.
- Leaking context: static/companion references to an Activity, a non-static inner
Handler, or a Fragment binding not nulled inonDestroyView. - Sprinkling
!!to silence the compiler, which converts Kotlin's compile-time null safety back into runtime NullPointerExceptions. - Using
GlobalScope(or a bareCoroutineScope) instead ofviewModelScope/lifecycleScope, so work outlives the screen; and swallowingCancellationExceptionin a broadcatch (e: Exception). - Confusing dp with px or using dp for text size, so the layout breaks on different densities and ignores the user's font-scale setting.
- Bumping the Room database version without writing a migration (or relying on
fallbackToDestructiveMigrationin production, which wipes user data). - Hardcoding strings, API keys and base URLs in code instead of
strings.xml, resource qualifiers for localisation, and build config / secure storage for keys.
Related topics
Cross-platform apps: Flutter and React Native · Object-oriented programming in Java · RESTful web services and API design · SQL and relational database design · Software architecture and design patterns · Information security fundamentals