Android Deep — Kotlin 2, Jetpack Compose, and the Android Platform Stack

Android development in 2026 is the synthesis of three rapid platform shifts: Kotlin’s K2 compiler and multiplatform expansion, Jetpack Compose’s maturation into the production-default UI toolkit, and the Play Store/Play Console rules that increasingly drive what apps can ship. The framework surface is larger than iOS because Android has accreted multiple eras of API — AsyncTask, Loader, Service, BroadcastReceiver, ContentProvider, Fragment, JobScheduler, WorkManager, AlarmManager — most still supported, but only a subset (the AndroidX / Jetpack subset) carries modern recommendations. This note treats the language, the UI framework, persistence, background work, distribution mechanics, and the Android Studio + Gradle build system as one system, with explicit attention to which legacy APIs to migrate off and which patterns the AOSP team currently considers canonical for new apps.

See also

1. Kotlin in 2026

1.1 K2 compiler

Kotlin 2.0 (May 2024) made the K2 frontend stable and default. K2 is a complete rewrite of the type checker, type inference, and IR generation pipeline; the original Kotlin frontend (K1) is deprecated and slated for removal in 2.3. Benefits:

  • 1.5-2× faster clean compilation, 1.5-3× faster incremental.
  • Unified FIR (Frontend Intermediate Representation) across JVM/JS/Native/WASM backends; consistent diagnostics.
  • New language features (context receivers, multi-dollar string templates, smart-cast on private mutable properties from the same module) gate on K2.

Kotlin 2.1 (November 2024) shipped K2 multiplatform stability; Kotlin 2.2 (mid-2025) added stabilised context parameters (the successor to context receivers, simpler syntax) and stable WASM/WASI. Build-tool integration: the Kotlin Gradle Plugin auto-uses K2 above 2.0; Android Studio Iguana+ ships matching IDE support.

1.2 Coroutines and structured concurrency

kotlinx.coroutines (since 2017, stable since 1.3 in 2019) is now the canonical concurrency primitive. Core concepts:

  • Suspend functions. Compile down to a state machine with a Continuation<T> parameter; Kotlin’s CPS transform predates Swift’s adoption of the same model.
  • CoroutineScope. Owns a job tree; cancellation propagates to children. viewModelScope (lifecycle-aware to ViewModel.onCleared), lifecycleScope (Activity/Fragment lifecycle), GlobalScope (discouraged for app code).
  • Dispatchers. Dispatchers.Main (UI thread, requires kotlinx-coroutines-android), Dispatchers.Default (CPU pool, sized to processor count), Dispatchers.IO (large pool for blocking I/O, default 64 threads), Dispatchers.Unconfined.
  • Flow. Cold async stream. flow { emit(...) } builder, map/filter/flatMapMerge operators, terminal collect. StateFlow (always-has-current-value, conflated, replays 1) and SharedFlow (configurable replay + buffer) are the hot variants.
  • collectAsStateWithLifecycle. Compose-side adapter that automatically stops collection when the lifecycle drops below STARTED, avoiding the well-known background-collection battery drain.

1.3 Kotlin Multiplatform (KMP) and Compose Multiplatform

Kotlin Multiplatform Mobile became stable in November 2023; the unifying brand “Kotlin Multiplatform” now covers JVM, Android, iOS, JS, WASM, native (Linux/macOS/Windows/Linux ARM/iOS). Shared code lives in commonMain source sets; platform-specific glue in androidMain, iosMain etc. expect/actual declarations bridge to platform APIs.

Compose Multiplatform (JetBrains, version 1.7+ in 2025) extends Jetpack Compose to iOS, desktop (Windows/macOS/Linux), and web (via Compose for Web compiling to JS or WASM). The iOS target reached stable in May 2024 with version 1.6; production usage is still early but increasing (Cash App, Toursprung, 9GAG case studies). Render path on iOS is Skia-based (similar to Flutter), not native UIKit views; Apple-platform native-feel is the open frontier.

1.4 Language features that matter for Compose

  • Trailing lambdas. Compose’s DSL relies on @Composable block-receiver syntax: Column { Text("hi") }.
  • Default and named arguments. Compose APIs expose many optional parameters; default-argument elision keeps call sites readable.
  • Inline classes / value classes. Zero-cost wrappers used by Compose for Color, Dp, Sp, Offset.
  • Context parameters (2.2+). Replace context receivers; let Compose pass Density, LayoutDirection, etc. implicitly.

2. Jetpack Compose

2.1 The Compose runtime

Compose is a “tree-diffing” UI library: composable functions describe the UI, the runtime tracks reads of State<T> (and StateFlow, MutableState, derivedStateOf), and a snapshot-based observer system re-invokes only the composables whose read state changed. The compiler plugin (kotlinx.compose.compiler) rewrites @Composable functions to thread a Composer parameter through, enabling positional memoisation: each call site gets a stable identity in the slot table that survives recompositions.

Skipping and stability are the performance levers. A composable can skip recomposition if all its inputs are @Stable or @Immutable (annotation-driven, or inferred for data class with val primitives). @NonRestartableComposable and @ReadOnlyComposable are tuning knobs for non-restartable scopes (CompositionLocal providers, theme functions).

2.2 Compose 1.7+ and Material 3

Jetpack Compose 1.7 (August 2024) and 1.8 (early 2025) added:

  • Strong skipping mode (compiler-level), enabled by default; recomposition skipping for parameters that are unstable but referentially equal.
  • Shared element transitions (SharedTransitionScope, Modifier.sharedElement) for screen-to-screen navigation.
  • Pull-to-refresh in Material 3 (PullToRefreshBox).
  • TextField overhauls (BasicTextField rewrite, TextFieldState).
  • Lazy list contentType improvements.

Material 3 (since Compose 1.4) implements Material You: dynamic colour from wallpaper (Android 12+), expressive type ramp, redesigned components (Button, Card, NavigationBar, TopAppBar). The androidx.compose.material3 package is the supported entry; legacy androidx.compose.material is in long-term maintenance only.

2.3 Adaptive layouts

The androidx.compose.material3.adaptive library (stable 1.0 in mid-2024) provides:

  • WindowSizeClass. Three bucket widths (Compact <600dp, Medium 600-840, Expanded ≥840) and heights (Compact, Medium, Expanded). Returned from currentWindowAdaptiveInfo().
  • PaneScaffold. ListDetailPaneScaffold and SupportingPaneScaffold automatically switch between single-pane (phone) and multi-pane (tablet, foldable, ChromeOS) layouts.
  • NavigationSuiteScaffold. Auto-switches between bottom bar (compact), navigation rail (medium), and navigation drawer (expanded).

These are required surfaces for the “Large screens” Play Store badge that ranks tablet and foldable searches higher.

2.4 Type-safe navigation (Navigation Compose 2.8+)

Pre-2.8 Compose Navigation used string routes ("profile/{userId}") parsed at runtime. Version 2.8 (October 2024) added Kotlin-Serialization-based type-safe destinations:

@Serializable data class Profile(val userId: String)
 
NavHost(navController, startDestination = Home) {
    composable<Home> { HomeScreen(...) }
    composable<Profile> { backStackEntry ->
        val profile: Profile = backStackEntry.toRoute()
        ProfileScreen(profile.userId)
    }
}

Compiler-checked routes, no manual argument parsing. Deep links via NavDeepLink and intent filters in AndroidManifest.xml.

2.5 State holders: ViewModel, StateFlow, collectAsStateWithLifecycle

The canonical pattern is:

class FeedViewModel(repo: Repo) : ViewModel() {
    private val _state = MutableStateFlow(FeedUiState.Loading)
    val state: StateFlow<FeedUiState> = _state.asStateFlow()
    init {
        viewModelScope.launch {
            _state.value = FeedUiState.Loaded(repo.load())
        }
    }
}
 
@Composable
fun Feed(vm: FeedViewModel = viewModel()) {
    val state by vm.state.collectAsStateWithLifecycle()
    when (state) { ... }
}

collectAsStateWithLifecycle is mandatory for non-trivial flows — collectAsState keeps collecting in background, draining battery and wasting work.

2.6 Modifier order and CompositionLocal

Compose’s Modifier chain is order-sensitive (like SwiftUI). Modifier.padding().background() paints the padded frame; Modifier.background().padding() paints only content. CompositionLocal provides ambient values (LocalContext, LocalDensity, LocalConfiguration, custom compositionLocalOf<T>() for app-wide DI). Use sparingly — they bypass stability tracking and force recomposition of every reading composable.

3. Dependency injection and modularisation

3.1 Hilt

Hilt (since 2020) is the Google-blessed DI framework, layered on Dagger 2. @HiltAndroidApp, @AndroidEntryPoint, @HiltViewModel, @Inject, @Module/@Provides/@Binds, @InstallIn(SingletonComponent::class). Compose integration via hiltViewModel(). Hilt 2.50+ supports KSP code generation, replacing kapt (faster builds, no JavaPoet).

3.2 Alternatives

  • Koin. Service-locator / DSL-based; no codegen; idiomatic Kotlin. Performance lower than Hilt at startup (reflective resolution); popular in KMP because it works on iOS/JVM/JS.
  • Pure Dagger 2. Used by very large codebases that need component customisation beyond Hilt’s pre-defined scopes.
  • Manual constructor injection + factory. Smaller apps; Compose’s viewModel(factory = ...) makes this viable.

3.3 Modularisation

Standard pattern: :app:feature:home, :feature:profile:core:ui, :core:data, :core:domain, :core:network. Dynamic feature modules (DFM) for on-demand delivery via Play Asset Delivery. Gradle’s org.jetbrains.kotlin.android + com.android.library plugins are the building blocks; the Android Gradle Plugin (AGP 8.x) enforces compileSdk/targetSdk consistency.

4. Persistence

4.1 Room

Room is the SQLite ORM in Jetpack. Entities (@Entity), DAOs (@Dao with @Query, @Insert, @Update, @Delete), database class (@Database). Compile-time-checked SQL strings; returns Flow/PagingSource/LiveData. Migrations via Migration objects or auto-migrations from schema diffs (Room 2.4+). KSP plugin (androidx.room:room-compiler-processing) supersedes kapt for faster builds. Room 2.7 (2025) added Kotlin Multiplatform support, sharing DAOs across Android and iOS.

4.2 DataStore

Replaces SharedPreferences for key-value (Preferences DataStore) and Protocol-Buffer-typed (Proto DataStore) storage. Backed by a Flow; non-blocking reads/writes; transactional updates via updateData { current -> new }.

4.3 Realm and ObjectBox

  • Realm Kotlin (MongoDB). Object database; cross-platform via Realm Mobile Database. Sync to MongoDB Atlas Device Sync. Used by Starbucks, BMW.
  • ObjectBox. Lightweight object database, faster than Room/Realm for raw object I/O; smaller mindshare.

4.4 SQLDelight

Cross-platform SQLite wrapper from Square (now Cash App). SQL-first: write .sq files; the plugin generates type-safe Kotlin/Swift/Multiplatform APIs. Popular for KMP shared persistence layers.

5. Background work and lifecycle

5.1 WorkManager

The canonical background-work API since 2018, mandatory for new code:

  • OneTimeWorkRequest, PeriodicWorkRequest (minimum 15 min).
  • Constraints: NetworkType, BatteryNotLow, RequiresCharging, DeviceIdle, StorageNotLow.
  • Backoff policy on failure.
  • Survives process death and reboot (persisted in SQLite).
  • Chained work via then(); parallel via WorkContinuation.combine().
  • Expedited work (setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)) for time-sensitive jobs; subject to per-app quota.

5.2 Foreground services and Android 14+ types

Android 14 (API 34) made foregroundServiceType mandatory for foreground services. Eleven types: dataSync, mediaPlayback, phoneCall, location, connectedDevice, mediaProjection, camera, microphone, health, remoteMessaging, shortService, specialUse, systemExempted. Apps must declare matching permissions and use cases; Play Store reviews validate at submission. Android 15 (API 35) tightened further: mediaProjection requires per-session user consent for screen recording.

5.3 AlarmManager and JobScheduler

  • AlarmManager for exact-time scheduling (alarms, calendar reminders). SCHEDULE_EXACT_ALARM and USE_EXACT_ALARM permissions for Android 13+; Play Console flags abuse.
  • JobScheduler is the lower-level OS API that WorkManager wraps; rarely used directly except for very specific power-management cases.

5.4 BroadcastReceiver and ContentProvider

Both are legacy IPC primitives still required for system events (BOOT_COMPLETED, intent broadcasts, content URI exposure). Android 14 enforces RECEIVER_EXPORTED / RECEIVER_NOT_EXPORTED flags on dynamic receivers; missing flags throw at runtime.

Intents are the IPC mechanism for activities/services/broadcasts. Implicit intents (ACTION_VIEW + URI) are handled by any registered app; explicit intents target a specific component. Deep links via intent filters in manifest; App Links (Android 6+) require a assetlinks.json at the verified domain, ensuring only your app handles https://your.com/* URLs without the disambiguator chooser.

5.6 Notifications

Channel-based since Android 8 (API 26): every notification belongs to a channel that the user can independently mute/style. POST_NOTIFICATIONS runtime permission required since Android 13. NotificationCompat for back-compat. Notification trampolines (starting an Activity through an intermediate service) blocked since Android 12.

6. Accessibility

  • TalkBack. Screen reader. ViewCompat.setAccessibilityDelegate, Modifier.semantics { contentDescription = "..." } in Compose. The Compose semantics tree is a parallel tree to the layout tree; nodes describe role, state, actions.
  • Switch Access. External-switch single-button navigation.
  • Voice Access. Spoken commands (“tap submit”); matches against semantics labels.
  • Magnification. System-wide zoom; respect Modifier.semantics(mergeDescendants = true) to provide single-target focus.
  • Live Caption (Android 10+). OS-level real-time captions on any audio; respect setAccessibilityLiveRegion for dynamic content.

Accessibility Scanner (Google Play app) and Espresso Accessibility checks (AccessibilityChecks.enable() in tests) catch missing labels and contrast failures.

7. Performance and benchmarking

7.1 Baseline Profiles

A Baseline Profile is a list of methods + classes precompiled at install time by ART, sidestepping JIT warmup for hot paths. Generated by running a Macrobenchmark test that exercises critical user journeys; the resulting baseline-prof.txt ships in the AAB. Median startup wins: 15-30% on first launch. Production apps (Twitter/X, Reddit, Instagram, Lyft) report consistent improvements. The androidx.benchmark:benchmark-macro-junit4 library and the BaselineProfileRule automate generation in CI.

7.2 Macrobenchmark vs Microbenchmark

  • Macrobenchmark. End-to-end timing (cold start, scroll perf, navigation). Runs on a real device or emulator; measures from a separate process. Outputs traceable runs viewable in Perfetto.
  • Microbenchmark. Microsecond-level method timing in unit-test process. JIT-warmed; reports per-op latency with confidence intervals.

7.3 R8 and ProGuard

R8 is the default code shrinker/obfuscator since AGP 3.4 (2019), replacing ProGuard. Performs whole-program optimisation, tree shaking (dead code removal), inlining, devirtualisation, identifier minification. ProGuard rules (proguard-rules.pro) still authoritative for keep rules. Library authors ship consumer-rules (consumer-rules.pro) that propagate to consumers. R8 full-mode (default since AGP 8.0) is more aggressive than compatibility-mode and removes more code.

7.4 APK Analyzer, bundletool

  • APK Analyzer (in Android Studio) inspects the contents and sizes of an APK/AAB.
  • bundletool generates APK splits from an AAB for testing (bundletool build-apks --bundle=foo.aab --output=foo.apks --device-spec=device.json). Required for local testing of dynamic delivery splits.

7.5 Perfetto and systrace

Perfetto is the modern unified tracing system on Android (replacing systrace + atrace). Open-source, OS-level trace ingestion with the Trace Processor SQL query layer. Capture from Android Studio’s profiler or via adb shell perfetto -o trace.pftrace. View on the perfetto.dev UI. Critical for diagnosing frame drops, ANRs, binder transactions, jank.

8. Play Console, AAB, distribution

8.1 Android App Bundles

AAB (Android App Bundle) has been mandatory for new apps on Play since August 2021 and for updates since 2023. The bundle contains the full app code + per-density/language/ABI resources; Play generates and signs per-device APK splits at install. Reduces installed size 15-25% over universal APKs. Play App Signing manages the upload + app signing keys (the upload key is replaceable; the app signing key is held by Google).

8.2 Play Asset Delivery and Dynamic Delivery

  • Play Asset Delivery. Up to 4 GB of asset packs per app: install-time (delivered with app), fast-follow (delivered after install), on-demand (downloaded at runtime). Game studios use heavily.
  • Dynamic Delivery (Feature modules). Download whole feature modules on demand via SplitInstallManager. Modularised apps can ship a small base APK and pull in features as needed.

8.3 Play Integrity API

Replaces deprecated SafetyNet (sunset January 2025). Returns attestation tokens proving:

  • The app binary is genuine (matches Play-signed).
  • The device is genuine and not rooted/emulated (basic + meets-device-integrity + meets-strong-integrity).
  • The user account is licensed for the app.

Verify server-side; never trust the client-side decode. Critical for banking, gambling, payment apps to gate sensitive actions.

8.4 Play Billing v7

Google Play Billing Library v7 (2024) revised the subscription model:

  • Multi-product subscriptions (one subscription with multiple base plans + offers).
  • Prepaid plans (non-renewing duration).
  • Server-side purchases.subscriptionsv2.get API for canonical subscription state.
  • 15% commission on subscriptions after 12 months; 30% before (15% for Small Business Program <$1M/yr).
  • DMA / Korea / India alternative billing options (EU users can pay via the developer’s own payment processor with 17% / 10% commission reduction).

8.5 Play Console policies 2024-26

Recurring policy changes drive ongoing work:

  • Foreground service type declarations (Android 14).
  • Data Safety form (privacy disclosures; required since 2022, audited continuously).
  • Target API level requirements: new apps Aug 2024 = API 34; updates Nov 2024 = API 33+. Annual ratchet.
  • Predatory loan apps banned (2023+).
  • AI-generated content disclosures (image-gen apps must disclose).
  • Account deletion in-app + on-web requirements (since May 2024).

8.6 Predictive back gesture

Android 13 introduced; Android 14 made it default. Apps opt in via android:enableOnBackInvokedCallback="true" in manifest + OnBackInvokedDispatcher in code. Compose’s BackHandler composable wraps it. Provides a system-rendered preview of the previous activity/screen during the swipe.

9. Android Studio and Gradle

9.1 Android Studio releases (alphabetised animal-name cadence)

Hedgehog (2023.1) → Iguana (2023.2) → Jellyfish (2024.1) → Koala (2024.2) → Ladybug (2024.3) → Meerkat (2025.1). Each major release ships matching AGP version (Iguana → 8.3, Jellyfish → 8.4, Koala → 8.5, Ladybug → 8.6, Meerkat → 8.7). The IDE itself is JetBrains IntelliJ Community + Android plugin; updated quarterly.

Gemini in Android Studio (since 2024) provides AI code completion and refactoring; opt-in, runs on Google’s hosted models.

9.2 Gradle and AGP

Android Gradle Plugin 8.x requires Gradle 8.x and JDK 17+. Build features:

  • Build cache (local + remote) for incremental builds across machines (Gradle Enterprise / Develocity).
  • Configuration cache (stable in Gradle 8.1) caches the configured task graph; eliminates configuration-phase work on incremental builds.
  • KSP (Kotlin Symbol Processing) for compile-time codegen, replacing kapt; ~2× faster.
  • Compose Compiler Gradle Plugin (Kotlin 2.0+) — Compose compiler moved out of Kotlin’s mainline; the Gradle plugin manages versioning.

9.3 Build variants

Product flavours × build types (debug/release) → variants. Each variant gets its own source set, resources, dependencies. Used for free/paid, staging/prod, branded white-label builds.

10. The 2024-2026 form factor expansion

10.1 Foldables

Pixel Fold, Samsung Galaxy Z Fold/Flip, Honor Magic V, Huawei Mate X. The Jetpack WindowManager library provides FoldingFeature (hinge location, orientation, posture) and DisplayFeature APIs. Adaptive layouts (Section 2.3) handle most cases; tabletop posture (half-fold horizontal) is the differentiator for media apps.

10.2 Wear OS 5

Wear OS 5 (mid-2024, atop Android 14) standardised the watch face format (XML-defined, declarative; replaces the legacy native watch face). Health Services API for sensors. Tiles for glanceable info. Compose for Wear OS is the recommended UI toolkit.

10.3 Android Auto and Automotive OS

Two distinct platforms:

  • Android Auto. Phone-projected UI on car infotainment screen. Limited template-based UI; navigation, messaging, media app categories.
  • Android Automotive OS (AAOS). Full Android running on the car’s head unit. Polestar 2, Volvo EX30, Renault Megane E-Tech, GM Ultium platform vehicles. Same APK as phone with manifest declarations for car-feature compatibility; CarApp library for car-specific UI.

10.4 Android TV and Google TV

Compose for TV (stable 2024) provides TV-specific composables (TvLazyRow, Carousel, focus management). Leanback library is the predecessor; new apps should use Compose for TV.

11. Gemini Nano and on-device AI

Pixel 8 Pro (Oct 2023) shipped Gemini Nano on-device; expanding to Pixel 8a, 9 series, Samsung Galaxy S24+ (via Galaxy AI partnership). Access via AICore system app + the Google AI Edge SDK. Capabilities: smart reply, summarisation, proofreading. Third-party apps consume via:

  • ML Kit GenAI APIs (summarisation, proofreading, rewriting, image description) — abstracts away the model.
  • AICore direct API for advanced use cases.

LiteRT (TensorFlow Lite renamed late 2024) and MediaPipe remain the supported paths for shipping custom on-device models. See inference-optimization for the cross-platform mobile inference landscape including NNAPI deprecation (Android 15 marks NNAPI for removal in favour of LiteRT’s per-vendor delegate path).

12. Architecture patterns

Google’s guidance (refreshed 2024) is a three-layer split:

  • UI layer. Composables + ViewModels. State exposed as StateFlow/SharedFlow.
  • Domain layer (optional). Use-cases / interactors. Plain Kotlin, framework-free.
  • Data layer. Repositories that abstract data sources (network, database, in-memory). Single source of truth.

Each layer owns its threading; ViewModels use viewModelScope, repositories use Dispatchers.IO (or coroutine-friendly Retrofit/Ktor). UnidirectionalData Flow: events upstream (UI → ViewModel → repo), state downstream (repo → ViewModel → UI).

12.2 MVI, MVVM, Redux-style

  • MVVM (default). ViewModel exposes StateFlow; UI observes; events via function calls on VM.
  • MVI. Single Intent channel + reducer pattern; pure-function state transitions. Popular for highly testable architectures.
  • Redux-style (Orbit, MVIKotlin). Less common; KMP-friendly via MVIKotlin.

12.3 Network

  • Retrofit + OkHttp. Annotation-based REST client; the canonical Android networking library since 2013.
  • Ktor Client. Multiplatform; works on iOS/JVM/JS. Used in KMP shared modules.
  • Apollo Kotlin. GraphQL client with codegen.

13. Testing

  • JUnit 4 + Espresso. Long-standing standard for unit + UI tests.
  • JUnit 5. Supported on Android via the jupiter-android plugin; uptake slow.
  • Compose Test. createComposeRule() and semantic-tree assertions (onNodeWithText("submit").performClick()).
  • Robolectric. JVM-based Android tests without an emulator; faster than Espresso for unit-scope Activity tests.
  • Macrobenchmark. Performance tests (Section 7.2).
  • Maestro, Appium. Cross-platform UI testing via accessibility tree; preferred for KMP / RN / Flutter projects.
  • Firebase Test Lab. Hosted device farm for Espresso + Robo + game-loop tests.

13b. Android runtime (ART): the JVM heritage and what differs

Android Runtime (ART) is the Android-specific JVM-derived execution environment. Differences from desktop JVM:

  • DEX bytecode. Android compiles .class files to .dex (Dalvik EXecutable) format via D8 (R8’s predecessor as dexer). DEX uses register-based instructions (vs JVM’s stack-based) for smaller bundle size and faster cold execution on ARM.
  • AOT + JIT hybrid since ART 7.0. App is installed; the system compiles on first run (or after Play Install) using profile-guided AOT (PGO) sourced from cloud profiles + on-device JIT samples. The compiled .oat file lives alongside the APK.
  • Concurrent compacting GC. ART’s collector (Concurrent Copying GC, CC; Concurrent Mark Compact, CMC since Android 14) achieves <2 ms pauses on most devices. CMC’s mark-compact phase eliminates fragmentation that CC’s copying approach left in older versions.
  • Baseline Profiles + Cloud Profiles. A Baseline Profile shipped in the AAB primes initial AOT; Cloud Profiles aggregated by Play from active installations tune subsequent recompilation.
  • NDK + JNI. Native code via NDK (Native Development Kit) using C/C++; called from Kotlin/Java via JNI (Java Native Interface). Heavyweight calls; minimise crossings.

See garbage-collection for the broader landscape; ART’s CMC is conceptually similar to Shenandoah’s concurrent compacting strategy.

13c. KSP and the codegen ecosystem

KSP (Kotlin Symbol Processing) is the modern annotation-processing API for Kotlin code, replacing kapt (Kotlin Annotation Processing Tool). Differences:

  • kapt. Generates Java stubs for Kotlin code so legacy javac-based annotation processors can read them. Adds 30-50% to build time on annotation-heavy modules.
  • KSP. Reads Kotlin source directly via FIR-based symbol API; ~2× faster than kapt; supports multiplatform (kapt is JVM-only).

Major libraries with KSP support: Room, Hilt, Moshi, Glide, Koin (codegen), Dagger 2 (since 2.51), kotlinx.serialization (compile plugin, not KSP, but conceptually similar). For new projects, prefer KSP; migrate from kapt incrementally as each library’s KSP processor stabilises.

13d. Network stack: OkHttp, Retrofit, Ktor

  • OkHttp (Square, 2013+). HTTP/1.1 + HTTP/2 + WebSocket client; connection pooling; transparent gzip; certificate pinning; interceptor chain. The substrate for nearly every Android networking library. Tracks HTTP/3 via the optional cronet engine (Chrome Networking Stack via NDK).
  • Retrofit (Square, 2013+). Annotation-driven REST client built atop OkHttp. @GET("users/{id}") suspend fun user(@Path("id") id: String): User. Type-safe; deserialisation via Moshi/Kotlinx.serialization/Gson/Jackson converters.
  • Ktor Client (JetBrains, 2018+). Kotlin-native HTTP client with multiplatform support. Used in KMP shared modules.
  • Apollo Kotlin (Apollo GraphQL, 2019+). GraphQL client with build-time query codegen; produces type-safe Kotlin classes from .graphql files.
  • gRPC-Kotlin. gRPC bindings for coroutines. Used for performance-critical RPC and Google Cloud APIs.

Certificate pinning + Network Security Configuration (res/xml/network_security_config.xml) restrict which CAs the app trusts; mandatory for banking apps to defeat user-installed-CA MITM.

13e. Push notifications and FCM

Firebase Cloud Messaging (FCM) is the standard push delivery service. Architecture:

  • App registers via FirebaseMessaging.getInstance().token.await(); gets a token.
  • Server stores token; sends push via FCM HTTP v1 API.
  • Device receives via Google Play Services; delivers to app’s FirebaseMessagingService.onMessageReceived().

Payload types: notification messages (system displays the notification when app is backgrounded; SDK delivers when foregrounded) and data messages (always delivered to the SDK; app composes the notification). For maximum control, use data-only messages and compose notifications in onMessageReceived.

Topic subscriptions (FirebaseMessaging.subscribeToTopic("news")) batch users into pub/sub groups for marketing notifications. Per-device delivery for personal updates.

Alternative providers: OneSignal, Airship, Braze, Iterable. All deliver via FCM under the hood but layer marketing/segmentation/automation UIs. For China (where Google Play Services is absent), use Huawei Push Kit, Xiaomi Push Service, OPPO Push, Vivo Push, and FCM HMS interop.

13f. Background dispatch with Hilt and Dispatchers.IO

Idiomatic background execution combines Hilt scopes + coroutine dispatchers:

class UserRepository @Inject constructor(
    private val api: UserApi,
    private val db: UserDao,
    @IoDispatcher private val io: CoroutineDispatcher,
) {
    suspend fun getUser(id: String): User = withContext(io) {
        db.find(id) ?: api.fetch(id).also { db.insert(it) }
    }
}

withContext(io) shifts the coroutine to Dispatchers.IO for the block, returns to the caller’s dispatcher on completion. Avoid runBlocking (blocks a thread); avoid GlobalScope.launch (untracked). Tests inject Dispatchers.Unconfined or StandardTestDispatcher via TestDispatcherProvider patterns.

14. Common pitfalls

  • Configuration changes (rotation, theme). Activities recreate by default; state must be saved via ViewModel (process-survival) or rememberSaveable/SavedStateHandle (configuration-only).
  • Memory leaks via Activity context retention. Long-lived references (companion objects, singletons) holding Activity keep the entire view hierarchy alive. LeakCanary (Square) is the canonical diagnostic.
  • kapt vs KSP. Mixing both for the same processor causes duplicate generation; migrate fully to KSP when possible.
  • collectAsState vs collectAsStateWithLifecycle. Background collection drains battery; always use the lifecycle-aware variant in Compose.
  • Foreground service type missing on Android 14+. App crashes at start; declare the type in manifest and via ServiceCompat.startForeground(this, id, notif, type).
  • Play Console keystore loss. Without Play App Signing (legacy apps from before 2018), losing the upload key means losing the app — no recovery.
  • R8 over-shrinking. Reflection-using libraries (Gson, Moshi without codegen, kotlinx.serialization with reflection-fallback) need @Keep annotations or proguard rules; symptoms are NoSuchMethodError or null after-deserialisation.
  • WorkManager initialisation order. Custom WorkManager configuration must be set before any work is scheduled; the AndroidX library does default init via ContentProvider, which Hilt or custom initialisation can race.

15. KMP iOS interop reality check

KMP shared business logic + persistence + networking is production-proven (Cash App, Netflix Studio, McDonalds, Forecasta, 9GAG, Phillips). KMP shared UI via Compose Multiplatform on iOS is earlier-stage; native UIKit/SwiftUI for iOS-specific look-and-feel remains the conservative choice. ObjC-interop generated by the Kotlin/Native compiler exposes Kotlin types as Objective-C classes; consumed by Swift via the standard ObjC-Swift bridge. Swift Package Manager integration (KT-42247) reached stable in Kotlin 2.0 (2024); Cocoapods integration remains via the cocoapods plugin.

Memory model: Kotlin/Native used a tightly restrictive memory model until 1.7.20 (October 2022) when the new memory model (similar to JVM) became default. Modern KMP iOS code uses standard concurrent collections; legacy code may still encounter the “frozen state” exceptions from the old model.

16. Compose internals: snapshot system + slot table

Compose’s reactivity rests on two interlocked subsystems:

16.1 The snapshot system

A Snapshot is a transactional view of mutable state. MutableState<T> (returned by mutableStateOf(...)) writes go to a thread-local snapshot; reads come from the current snapshot. The composition observes a snapshot via snapshotFlow { state.value } or implicitly when a composable reads state.value. On commit, snapshots are merged with conflict detection; the global observer is notified of changed states; affected composables are scheduled for recomposition.

The snapshot system is independent of Compose UI — it underlies Compose’s reactivity but is reusable. Compose Material 3 uses it for theme state; libraries like Molecule (Cash App) use it for non-UI reactive computation.

16.2 The slot table

Each composable invocation occupies a slot in the SlotTable data structure, identified by call-site source position (encoded into the Composer parameter by the Compose compiler plugin). Re-invocations of the same composable at the same call-site reuse the slot, preserving remember { ... } values across recompositions. Conditional branches (if/when) use group keys to ensure slot identity is stable across structural changes.

This positional memoisation is what makes remember { mutableStateOf(0) } work the way developers expect: across recompositions, the same MutableState instance is returned because the call-site occupies the same slot.

16.3 Strong skipping mode

Compose Compiler 1.5.4+ enables strong skipping mode by default (controlled by kotlinx.compose.compiler.gradle.plugin configuration). With strong skipping, a composable skips recomposition if all parameters are equal (==), even if they are unstable types — provided the call-site is restartable. The compiler emits group keys and equality checks; the cost is a small amount of generated code per composable.

17. WindowManager and large-screen patterns

The androidx.window library exposes window-management metadata:

  • WindowMetrics. Current bounds in pixels and density-independent units.
  • WindowLayoutInfo. Folding features (hinge orientation, posture: flat/half-opened/half-closed/tabletop/book).
  • WindowSizeClass. Compact/Medium/Expanded buckets for width and height.

Production patterns:

  • Use WindowSizeClass for layout decisions (single pane vs split pane vs list-detail).
  • Use FoldingFeature for hinge-aware layouts (place chat list left of hinge, conversation right).
  • Use currentWindowAdaptiveInfo() Compose API to react to all three.
  • Configure AndroidManifest.xml <activity> android:resizeableActivity="true" and android:configChanges to handle multi-window mode + screen rotation without re-creation when possible.

Tablet + foldable + ChromeOS share the “large screen” surface; the Play Console gives a quality-tier ranking (“Limited” / “Optimised”) that affects search ranking on large-screen devices. Achieving “Optimised” requires adaptive layouts, keyboard navigation, mouse + touchpad support, and density tier coverage.

Further reading

  • Android Developer Documentation (developer.android.com) — canonical reference.
  • Now in Android (Google blog + sample app) — quarterly platform digest.
  • Jetpack Compose sample apps (github.com/android/compose-samples).
  • Donn Felker, Kotlin Standard Library Mastery (2024).
  • Marcin Moskala, Kotlin Coroutines Deep Dive (2nd ed., 2023).
  • Android Performance Patterns (Colt McAnlis, YouTube + book).
  • KMP Reference (kotlinlang.org/docs/multiplatform.html).
  • Compose Multiplatform docs (jetbrains.com/compose-multiplatform).
  • Pierre-Yves Ricau, LeakCanary 2 architecture (Square blog).

Adjacent