# Interview Prep 2

### Dependency Injection & Build

**Q121.** How do you implement dependency injection in a large-scale app, and why prefer Hilt over manual DI or Koin? Hilt is the recommended DI framework for Android, generating components at compile time via annotation processing, ensuring type safety and performance. It integrates with Android lifecycle (Activity, Fragment, ViewModel) through predefined scopes and eliminates boilerplate. Manual DI becomes unmaintainable at scale due to growing factory classes and transitive dependency graphs. Koin uses service locator pattern with runtime resolution, risking crashes from missing bindings that Hilt catches at compile time. Hilt's generated code is optimized by the compiler and works seamlessly with Jetpack ViewModels via `by viewModels()`. For SDKs or Kotlin Multiplatform, Koin or manual DI may be preferable due to Hilt's Android-only nature. The trade-off is build time cost from kapt/KSP, but the safety and maintainability outweigh it for large teams.

```kotlin
@HiltAndroidApp
class MyApp : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val vm: MyViewModel by viewModels()
}
```

Read more: <https://developer.android.com/training/dependency-injection/hilt-android>

**Q122.** How do you write custom Gradle convention plugins to standardize build configuration across modules? Create a standalone Gradle plugin project (Kotlin DSL) applying common Android plugin configurations, dependencies, and lint settings. Define a class implementing `Plugin<<Project>` that configures `android` block defaults (compileSdk, minSdk, testInstrumentationRunner) and adds standard dependencies (JUnit, Kotlin stdlib). Apply the plugin in feature modules via `plugins { id("com.example.convention.android-library") }`. Use `extensions` to expose customizable properties (e.g., `enableCompose = true`). This eliminates copy-paste build scripts and ensures consistency. Version the plugin independently and publish to a private Maven repository or include in `buildSrc`. For large projects, split into `android-application`, `android-library`, `android-feature`, and `android-test` convention plugins. Document plugin usage in the project wiki. Update all modules atomically when build standards change.

```kotlin
class AndroidLibraryConventionPlugin : Plugin<<Project> {
    override fun apply(target: Project) = with(target) {
        pluginManager.apply("com.android.library")
        pluginManager.apply("org.jetbrains.kotlin.android")
        extensions.configure<<LibraryExtension> {
            compileSdk = 34
            defaultConfig { minSdk = 24; testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" }
        }
        dependencies { add("implementation", libs.findLibrary("androidx.core.ktx").get()) }
    }
}
```

Read more: <https://developer.android.com/build>

**Q123.** How do you handle annotation processing migration from kapt to KSP in a large project? KSP (Kotlin Symbol Processing) is faster than kapt because it directly analyzes Kotlin AST without generating Java stubs, reducing build times by \~25%. Migrate incrementally: enable KSP for one module at a time (Room, Moshi, Hilt) while keeping kapt for others. Update Gradle plugins to KSP-compatible versions (`com.google.devtools.ksp`). Replace `kapt` with `ksp` in dependencies. Note that some libraries (Dagger/Hilt) require specific KSP versions; verify compatibility before migration. For libraries without KSP support, continue using kapt or replace them. Configure `ksp { arg("room.schemaLocation", "$projectDir/schemas") }` for Room schema export. Clean build after migration to clear stale generated code. Monitor build scan times to validate improvement. Document the migration in team wiki. KSP doesn't support Java-only processors; keep kapt for those if necessary.

```kotlin
plugins {
    id("com.google.devtools.ksp") version "1.9.0-1.0.13"
}
dependencies {
    ksp("androidx.room:room-compiler:2.6.0")
    ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.0")
}
```

Read more: <https://developer.android.com/build/migrate-to-ksp>

***

### Data & Storage

**Q124.** How do you handle complex multi-table database queries in Room while maintaining reactive updates? Room's `@Relation` with `@Embedded` and `@Transaction` fetches related entities, but emits reactive updates only when the parent table changes. For true reactive multi-table joins, use `@Query` with explicit JOINs returning a flattened POJO or use database views (`@DatabaseView`) to precompute complex relationships. Add indices on foreign keys to avoid full table scans during joins. Use `Flow` return types for automatic emission on table changes, but note that Room invalidates queries when any referenced table changes, potentially causing excessive recompositions. For complex aggregations, use `GROUP BY` with `@Query` and map to domain models. Test query plans with `EXPLAIN QUERY PLAN`. Avoid `@Relation` for many-to-many without a join table entity. Use `Transaction` annotation to ensure consistency.

```kotlin
@DatabaseView("SELECT u.id, u.name, COUNT(m.id) as messageCount FROM users u LEFT JOIN messages m ON u.id = m.userId GROUP BY u.id")
data class UserStats(val id: String, val name: String, val messageCount: Int)

@Dao
interface UserDao {
    @Query("SELECT * FROM user_stats")
    fun observeUserStats(): Flow<List<<UserStats>>
}
```

Read more: <https://developer.android.com/training/data-storage/room>

**Q125.** How do you migrate from SharedPreferences to DataStore, and what are the trade-offs? DataStore offers type safety (Preferences or Proto), coroutine-based async I/O, and transactional updates with strong consistency guarantees, unlike SharedPreferences' synchronous apply/commit and potential ANR on main thread. Migrate by reading all existing SharedPreferences keys in a one-shot coroutine, writing them to DataStore, then deleting the old file. Use `androidx.datastore:datastore-preferences` for key-value pairs or Proto DataStore for typed schemas. DataStore handles migration automatically via `SharedPreferencesMigration` helper. Trade-offs: DataStore is slower for very frequent writes due to coroutine overhead and file rewriting. SharedPreferences is still acceptable for simple flags with low write frequency. For multi-process access, neither is ideal—use SQLite or MMKV. DataStore lacks `OnSharedPreferenceChangeListener`; use `Flow` observation instead. Always perform writes off the main thread.

```kotlin
val dataStore: DataStore<<Preferences> = PreferenceDataStoreFactory.create(
    migrations = listOf(SharedPreferencesMigration(context, "legacy_prefs")),
    produceFile = { context.preferencesDataStoreFile("settings") }
)
val THEME_KEY = stringPreferencesKey("theme")
suspend fun setTheme(theme: String) = dataStore.edit { it[THEME_KEY] = theme }
```

Read more: <https://developer.android.com/topic/libraries/architecture/datastore>

**Q126.** How do you implement a content provider for cross-app data sharing with fine-grained permissions? Extend `ContentProvider` and implement `query`, `insert`, `update`, `delete`, and `getType` with URI matching via `UriMatcher`. Define path-level permissions in the manifest using `android:readPermission` and `android:writePermission` with signature protection. Use `path-permission` elements for granular access control per URI pattern. Return `ParcelFileDescriptor` for file access rather than raw paths. Validate caller identity in each method using `getCallingPackage()` or `Binder.getCallingUid()`. Use `SQLiteQueryBuilder` for secure parameterized queries preventing injection. Expose only necessary columns; never return internal IDs or sensitive metadata. Implement `ContentObserver` notifications for data changes. Document the content URI contract and MIME types for consumers. Test with a separate consumer app to verify permission enforcement.

```kotlin
class MyProvider : ContentProvider() {
    private val matcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI(AUTHORITY, "items", ITEMS)
        addURI(AUTHORITY, "items/#", ITEM_ID)
    }
    override fun query(uri: Uri, projection: Array<<out String>?, selection: String?, selectionArgs: Array<<out String>?, sortOrder: String?): Cursor? {
        when (matcher.match(uri)) {
            ITEMS -> return db.query("items", projection, selection, selectionArgs, null, null, sortOrder)
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }
}
```

Read more: <https://developer.android.com/guide/topics/providers/content-providers>

***

### Security & Privacy

**Q127.** How do you implement biometric authentication with cryptographic binding for high-security operations? Use `BiometricPrompt` with `CryptoObject` to bind biometric authentication to cryptographic operations, ensuring the key is only usable after successful biometric verification. Generate a key in Android Keystore with `setUserAuthenticationRequired(true)` and `setInvalidatedByBiometricEnrollment(true)`. Wrap the `Cipher`, `Signature`, or `Mac` in `BiometricPrompt.CryptoObject`. On authentication success, use the unlocked crypto object to decrypt sensitive data or sign transactions. Handle all error codes (`ERROR_LOCKOUT`, `ERROR_NO_BIOMETRICS`) with user-friendly messaging. For fallback, allow device credentials (PIN/pattern) by setting `setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)`. Never store the biometric data itself—only use Keystore-backed keys. Test on devices with and without biometric hardware. Ensure the prompt is cancellable and lifecycle-aware.

```kotlin
val keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
val spec = KeyGenParameterSpec.Builder("bio_key", PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
    .setBlockModes(BLOCK_MODE_GCM)
    .setUserAuthenticationRequired(true)
    .setUserAuthenticationValidityDurationSeconds(-1) // require each use
    .build()
keyGen.init(spec)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val prompt = BiometricPrompt(activity, executor, callback)
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
```

Read more: <https://developer.android.com/training/sign-in/biometric-auth>

**Q128.** How do you handle file sharing securely using FileProvider and content URIs? Define a `FileProvider` in the manifest with `android:authorities` and an XML `paths` configuration limiting exposed directories (e.g., `<files-path name="shared" path="shared/" />`). Never expose the entire internal storage. Generate content URIs via `FileProvider.getUriForFile()` and grant temporary permissions with `Intent.FLAG_GRANT_READ_URI_PERMISSION` or `Intent.FLAG_GRANT_WRITE_URI_PERMISSION`. For multiple files, use `ClipData` with `Item` containing the URI and grant permissions via `Intent.setClipData()`. Revoke permissions via `Context.revokeUriPermission()` when no longer needed. Validate incoming URIs in receiving apps using `ContentResolver.query()` to check file size and type before processing. Prevent path traversal by rejecting URIs with `..` segments. Use `StrictMode` VmPolicy to detect unsafe file URI exposure. Test with `adb shell am start` sending file intents.

```xml
<<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
```

Read more: <https://developer.android.com/training/secure-file-sharing>

**Q129.** How do you architect user privacy controls for per-app language preferences and data deletion requests introduced in Android 13+? Implement `AppLocalePicker` using `LocaleManager` (API 33+) or `AppCompatDelegate.setApplicationLocales()` from AppCompat to allow users to select a language different from the system default. Store the selected locale in `AppCompatDelegate` which persists across sessions using `androidx.core:core-1.9.0+`. Provide an in-app language selector that updates the locale and recreates the activity. For older devices, use a custom `ContextWrapper` attaching a new `Configuration` with the selected locale to the base context. Ensure all string resources are properly externalized with translations. Test by changing the app locale and verifying layout direction (RTL) changes for Arabic/Hebrew. Document that `LocaleManager` requires app targeting API 33+. Handle locale changes in `attachBaseContext()` for `Activity` and `Application` context. Use `autoStoreLocales = true` in `Application` metadata to persist without manual storage.

```kotlin
// Android 13+
val localeManager = getSystemService(Context.LOCALE_SERVICE) as LocaleManager
localeManager.applicationLocales = LocaleList.forLanguageTags("fr")

// AppCompat fallback
AppCompatDelegate.setApplicationLocales(LocaleListCompat.create(Locale("fr")))
```

Read more: <https://developer.android.com/guide/topics/resources/app-languages>

***

### UI & Compose

**Q130.** How do you implement edge-to-edge UI while handling system bars and keyboard insets correctly? Use `WindowCompat.setDecorFitsSystemWindows(window, false)` to enable edge-to-edge display. Consume insets via `ViewCompat.setOnApplyWindowInsetsListener` or in Compose via `WindowInsets` and `Modifier.padding` with `safeDrawing` or `safeContent`. For Compose, use `Scaffold` with `contentWindowInsets` or manual `PaddingValues` derived from `WindowInsets.statusBars` and `navigationBars`. Handle IME (keyboard) animations using `WindowInsets.ime` with `Modifier.imePadding()` or `AnimatedInsets` on Android 11+. Respect display cutouts with `WindowInsets.displayCutout`. Ensure touchable elements don't overlap system gestures by adding `Modifier.systemGesturesPadding()`. Test on devices with notches, gesture navigation, and three-button navigation. Use `InsetsController` to control light/dark system bar icons. Material3 components like `TopAppBar` and `BottomNavigationBar` automatically handle insets when used within `Scaffold`.

```kotlin
setContent {
    val insets = WindowInsets.safeDrawing
    Scaffold(
        topBar = { TopAppBar(...) },
        content = { padding ->
            Box(Modifier.padding(padding).consumeWindowInsets(insets)) { ... }
        }
    )
}
```

Read more: <https://developer.android.com/develop/ui/views/layout/edge-to-edge>

**Q131.** How do you implement dynamic theming with Material3 and wallpaper-derived color schemes? Use `dynamicColorScheme()` from `androidx.compose.material3:material3` on Android 12+ to generate color schemes from the user's wallpaper via `android.graphics.color` APIs. Fallback to predefined light/dark schemes on older devices. Use `isSystemInDarkTheme()` to respect system preference, overridden by in-app toggle stored in DataStore. Apply `MaterialTheme` with the dynamic scheme at the app root. Use tonal palettes (`primaryContainer`, `onPrimaryContainer`) for accessible contrast. For Views, use `Theme.Material3.DynamicColors` and apply `DynamicColors.applyToActivitiesIfAvailable()`. Test with different wallpapers and under dark mode. Ensure custom brand colors harmonize with dynamic palettes using `ColorScheme.harmonized()`. Document that dynamic theming requires `targetSdk` 31+ and device support. Provide an opt-out in settings for users preferring consistent app branding.

```kotlin
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colors = when {
    dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
    dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
    darkTheme -> DarkColorScheme
    else -> LightColorScheme
}
MaterialTheme(colorScheme = colors, typography = Typography, content = content)
```

Read more: <https://developer.android.com/develop/ui/views/theming/dynamic-colors>

**Q132.** How do you implement predictive back gesture support in an app targeting Android 15+? Enable predictive back by setting `android:enableOnBackInvokedCallback="true"` in the manifest and using `OnBackInvokedDispatcher` instead of overriding `onBackPressed()`. Register callbacks with priority levels to handle hierarchical back navigation (e.g., close drawer before exiting app). In Compose, use `PredictiveBackHandler` from `androidx.activity:activity-compose` to receive back progress events and animate UI accordingly (e.g., scaling down the screen or revealing the home screen behind). Provide visual continuity by interpolating from `Float` progress values (0.0 to 1.0) during the gesture. Test with gesture navigation enabled on Android 14+ devices and emulators. For custom transitions, use `androidx.transition` with predictive back support. Ensure that disabled users can still navigate via accessibility services when animations are reduced. Document the back stack behavior for each screen in navigation graphs.

```kotlin
// Manifest
<<application android:enableOnBackInvokedCallback="true" ... />

// Compose
PredictiveBackHandler { progress ->
    progress.collect { backEvent ->
        val scale = 1f - (backEvent.progress * 0.1f)
        // animate scale
    }
}
```

Read more: <https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture>

**Q133.** How do you implement and test custom Views with complex drawing and touch handling? Extend `View` and override `onMeasure` to define exact dimensions based on `MeasureSpec` constraints. Override `onDraw` using `Canvas` and `Paint` for custom rendering; cache `Path` and `Bitmap` objects in `onSizeChanged` to avoid allocation during draw calls. Handle touch events in `onTouchEvent` using `MotionEvent` coordinates, supporting both single and multi-touch via `getPointerId`. Use `VelocityTracker` for gesture speed calculations. Enable hardware acceleration hints with `setLayerType(View.LAYER_TYPE_HARDWARE, null)` for complex animations. Test custom views using `androidx.test` with `PixelCopy` for screenshot verification and manual touch injection via `MotionEvent.obtain()`. Use `View.isInEditMode` to provide preview data in Android Studio layout editor. Profile custom drawing with GPU rendering profile bars.

```kotlin
class CircleView @JvmOverloads constructor(...) : View(...) {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.RED }
    override fun onDraw(canvas: Canvas) {
        canvas.drawCircle(width / 2f, height / 2f, min(width, height) / 2f, paint)
    }
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> { /* handle touch */ return true }
        }
        return super.onTouchEvent(event)
    }
}
```

Read more: <https://developer.android.com/training/custom-views>

***

### Media & Background

**Q134.** How do you architect media playback for a podcast app supporting background playback, downloads, and casting? Use Jetpack Media3 with `ExoPlayer` as the player engine, wrapped in a `MediaSessionService` for background playback and platform integration. Implement `MediaController` in the UI for transport controls and metadata display. Use `DownloadManager` for offline episodes with a `DownloadService` foreground notification. Support Google Cast via `CastPlayer` from Media3 extensions. Structure the app with a single `MediaBrowserService` that exposes the content hierarchy to Android Auto, Wear, and Assistant. Use `PlayerNotificationManager` for persistent media notifications with playback controls. Handle audio focus via `AudioFocusRequest` and noisy intent for headphone disconnects. Store playback progress in DataStore and resume seamlessly. Use `WorkManager` for periodic library sync and download cleanup.

```kotlin
class PodcastPlaybackService : MediaSessionService() {
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }
    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
}
```

Read more: <https://developer.android.com/media>

**Q135.** How do you handle audio ducking and focus management when multiple apps request audio playback? Request audio focus with `AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)` when your app plays short audio (notifications, navigation instructions) that should duck others rather than pause them. For long-form media, use `AUDIOFOCUS_GAIN` and pause on focus loss. Implement `OnAudioFocusChangeListener` to handle `AUDIOFOCUS_LOSS_TRANSIENT` (pause temporarily) and `AUDIOFOCUS_LOSS` (pause indefinitely). Use `AudioAttributes` with `CONTENT_TYPE_SPEECH` or `CONTENT_TYPE_MUSIC` to help the system prioritize. In Media3, `ExoPlayer` handles audio focus automatically when configured with `AudioAttributes`. Test ducking behavior by playing music in Spotify and launching your app. Ensure volume changes are smooth using `VolumeShaper`. Respect the `NotificationManager.INTERRUPTION_FILTER_PRIORITY` Do Not Disturb state.

```kotlin
val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
    .setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build())
    .setOnAudioFocusChangeListener { focusChange ->
        when (focusChange) {
            AudioManager.AUDIOFOCUS_LOSS -> player.pause()
            AudioManager.AUDIOFOCUS_GAIN -> player.play()
        }
    }.build()
audioManager.requestAudioFocus(focusRequest)
```

Read more: <https://developer.android.com/guide/topics/media>

**Q136.** How do you implement NFC Host Card Emulation (HCE) for contactless payments or access control? Declare an `HceService` extending `HostApduService` in the manifest with `android:exported="true"` and an intent filter for `android.nfc.cardemulation.action.HOST_APDU_SERVICE`. Provide an XML `apduservice` configuration defining AID (Application ID) filters. Override `processCommandApdu()` to handle SELECT and other APDU commands, returning response APDUs as `ByteArray`. Maintain a secure element or Keystore-backed keys for cryptographic operations within the service. Use `sendResponseApdu()` for synchronous responses. Handle `onDeactivated()` to clean up sessions. Ensure the service is bound to a foreground activity or notification for security. Test with real NFC readers and the NFC Forum test suite. Comply with EMVCo specifications for payment apps. Use `TapAndPay` APIs if integrating with Google Wallet rather than building custom HCE.

```kotlin
class MyHceService : HostApduService() {
    override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
        return when (commandApdu.sliceArray(0..3)) {
            SELECT_APDU -> { /* authenticate and respond */ SUCCESS_RESPONSE }
            else -> UNKNOWN_APDU
        }
    }
    override fun onDeactivated(reason: Int) { /* cleanup session */ }
}
```

Read more: <https://developer.android.com/guide/topics/connectivity/nfc/hce>

***

### Performance & Profiling

**Q137.** How do you use Macrobenchmark and JankStats to detect and fix UI jank in production? Macrobenchmark measures cold startup, warm startup, scrolling, and animation frame times using `FrameTimingMetric` and `StartupTimingMetric` on real devices without debug overhead. Write benchmark classes extending `MacrobenchmarkRule` to repeatedly execute user journeys and report frame durations. JankStats (androidx.metrics) is a production library that reports frame metrics from real user sessions via `JankStats.createAndTrack(window, executor) { frameData -> analytics.log(frameData) }`. It detects frames exceeding the target refresh rate deadline and reports stack traces when available. Combine both: use Macrobenchmark in CI for regression detection and JankStats in production for real-world anomaly detection. Fix jank by identifying slow layout passes via Systrace, reducing overdraw, and moving heavy work off the main thread. Target 90th percentile frame times rather than averages.

```kotlin
class ScrollBenchmark {
    @get:Rule val benchmarkRule = MacrobenchmarkRule()
    @Test fun scrollList() = benchmarkRule.measureRepeated(packageName, listOf(FrameTimingMetric())) {
        val list = device.findObject(By.res(packageName, "recycler"))
        list.setGestureMargin(device.displayWidth / 5)
        list.fling(Direction.DOWN)
    }
}
```

Read more: <https://developer.android.com/topic/performance/benchmarking>

**Q138.** How do you use the ProfileInstaller library to ship Baseline Profiles without waiting for Play Store cloud profiling? ProfileInstaller (androidx.profileinstaller) embeds a Baseline Profile in `assets/dexopt/baseline.prof` and installs it on first launch or app update, bypassing the delay for Play Store cloud profiles. It uses `ProfileInstallerInitializer` via App Startup library to trigger installation. Ensure your AAB includes the profile asset generated by the Baseline Profile Gradle plugin. ProfileInstaller works on API 24+ but is most effective on API 31+ with ART updates. Verify installation via logcat filtering for `ProfileInstaller`. Combine with `Macrobenchmark` to measure startup improvement immediately after install rather than waiting days. If the profile is invalid or ART rejects it, ProfileInstaller logs warnings without crashing. Use `androidx.benchmark:benchmark-macro-junit4` to generate profiles via the `BaselineProfileRule`. Document profile generation in CI so profiles are updated with each release.

```kotlin
// Automatically handled by androidx.profileinstaller:profileinstaller
// No code required, but verify in logcat:
// adb shell pm compile -f -m speed-profile com.example.app
```

Read more: <https://developer.android.com/topic/performance/baselineprofiles>

**Q139.** How do you handle strict mode policies in development to catch main thread violations early? Enable `StrictMode.ThreadPolicy` to detect disk reads, disk writes, and network calls on the main thread with `penaltyDeathOnNetwork()` or `penaltyLog()`. Enable `StrictMode.VmPolicy` to detect leaked `SQLiteCursor`, `Activity`, and `Closeable` objects, plus untagged sockets. Configure policies in `Application.onCreate()` wrapped in `if (BuildConfig.DEBUG)`. Use `penaltyListener` to send violations to Crashlytics or custom logging in internal builds. For coroutines, StrictMode helps identify accidental `Dispatchers.Main` usage for I/O. Fix violations by moving operations to `Dispatchers.IO`, using `AsyncLayoutInflater`, or deferring initialization. Don't enable `penaltyDeath()` in production; use logging only. Use `StrictMode.noteSlowCall()` to annotate expected slow operations on the main thread. Document expected exceptions (e.g., Firebase init) with `StrictMode.allowThreadDiskReads()` temporarily.

```kotlin
if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
        .detectDiskReads().detectDiskWrites().detectNetwork()
        .penaltyLog().penaltyDeathOnNetwork().build())
    StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder()
        .detectLeakedSqlLiteObjects().detectLeakedClosableObjects()
        .penaltyLog().build())
}
```

Read more: <https://developer.android.com/reference/android/os/StrictMode>

***

### System Integration & Distribution

**Q140.** How do you leverage Android App Bundles and Dynamic Delivery to reduce install size and enable on-demand features? Build as Android App Bundle (AAB) instead of APK; Google Play generates optimized APKs per device configuration (ABI, density, language) reducing install size by \~20%. Use Dynamic Feature Modules (`com.android.dynamic-feature`) for large or rarely used features (AR, heavy media, advanced editing). Request on-demand installation via `SplitInstallManager.startInstall()` with a loading UI and progress listener. Use `install-time` delivery for critical features and `on-demand` for optional ones. Implement deferred uninstall via `SplitInstallManager.deferredUninstall()` to reclaim space. Handle module uninstallation gracefully by checking `SplitInstallHelper.updateAppContext()`. Monitor install success rates and cancellation via Play Console. For instant experiences, use `dist:instant="true"`. Ensure the base module contains all core functionality; dynamic modules must not be required for app launch.

```kotlin
val splitInstallManager = SplitInstallManagerFactory.create(context)
val request = SplitInstallRequest.newBuilder().addModule("premium_feature").build()
splitInstallManager.startInstall(request)
    .addOnSuccessListener { /* navigate to feature */ }
    .addOnFailureListener { /* handle error */ }
```

Read more: <https://developer.android.com/guide/app-bundle>

**Q141.** How do you handle in-app updates using Play In-App Updates API? Use `AppUpdateManager` to check for available updates via `appUpdateManager.appUpdateInfo`. For critical updates, trigger an immediate update flow that blocks UI until completed using `startUpdateFlowForResult()` with `AppUpdateType.IMMEDIATE`. For flexible updates, show a snackbar prompting the user to download in the background using `AppUpdateType.FLEXIBLE`; monitor progress via `InstallStateUpdatedListener` and complete installation with `completeUpdate()` when the app is backgrounded. Always check `updateAvailability()` and `isUpdateTypeAllowed()` before launching flows. Handle user denial gracefully—don't force updates unless security-critical. Test with internal app sharing which supports in-app update testing. Use version code checks to ensure the update is actually newer. Monitor update adoption rates via Play Console. Wrap the API in a repository for testability.

```kotlin
val appUpdateManager = AppUpdateManagerFactory.create(context)
val appUpdateInfoTask = appUpdateManager.appUpdateInfo
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
        appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, activity, RC_APP_UPDATE)
    }
}
```

Read more: <https://developer.android.com/guide/playcore/in-app-updates>

**Q142.** How do you implement and manage a crash reporting and logging strategy across multiple environments? Use Firebase Crashlytics for production crash reporting with custom keys (`Crashlytics.setCustomKey("user_tier", tier)`) and non-fatal logging (`Crashlytics.recordException(e)`). Use Timber for debug logging with a custom tree that forwards to Crashlytics only in release builds. Maintain separate Firebase projects for dev, staging, and prod to isolate crash data. Use `BuildConfig.DEBUG` to switch log levels and tree planting. For sensitive data, implement a `RedactingTree` that scrubs PII before sending to remote logs. Use `BreadCrumb` logging to trace user actions leading to crashes. In CI, symbolicate native crashes with `ndk` upload paths. Monitor crash-free user rate and prioritize top crashes by affected user count. Integrate with alerting (PagerDuty/Slack) for crash rate spikes. Regularly audit and resolve ignored/non-fatal exceptions to prevent noise.

```kotlin
class ReleaseTree : Timber.Tree() {
    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
        if (priority == Log.ERROR || priority == Log.WARN) {
            FirebaseCrashlytics.getInstance().log("$priority/$tag: $message")
            t?.let { FirebaseCrashlytics.getInstance().recordException(it) }
        }
    }
}
```

Read more: <https://developer.android.com/studio/debug>

***

### Wearables, Native & Advanced

**Q143.** How do you architect a Wear OS app that complements a mobile app without duplicating logic? Use the same domain and data layers via Kotlin Multiplatform or shared library modules, exposing business logic through a Wear-specific presentation layer. Communicate with the phone via Wearable Data Layer API (`DataClient`, `MessageClient`, `CapabilityClient`) for syncing small data payloads, or use Bluetooth/WiFi direct for larger transfers. Implement complications (`ComplicationDataSourceService`) and tiles (`TileService`) for glanceable surfaces that fetch data from a local cache synced from the phone. Use `HealthServices` (PassiveMonitoringClient) for fitness data instead of polling sensors. Keep the Wear app functional offline with local Room database caching. Use `WatchFace` APIs for custom watch faces if applicable. Test on physical Wear devices and emulators with round/square configurations. Minimize network calls—batch syncs via WorkManager constrained to charging. Ensure the Wear UI follows Material design for Wear with large touch targets.

```kotlin
class HeartRateTileService : TileService() {
    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) = Futures.immediateFuture(
        TileBuilders.Tile.Builder()
            .setResourcesVersion("1")
            .setTimeline(TimelineBuilders.Timeline.Builder().addTimelineEntry(...).build())
            .build()
    )
}
```

Read more: <https://developer.android.com/training/wearables>

**Q144.** How do you manage native memory and JNI boundaries when integrating C++ libraries? Use `try/finally` or `Cleaner` (Java 9+) to ensure native resources are released. Allocate native memory via `ByteBuffer.allocateDirect()` for zero-copy transfer between Java and C++, or use `MemoryFile` for shared memory scenarios. Pass primitive arrays and `DirectByteBuffer` to JNI rather than object graphs to minimize marshalling overhead. Keep JNI calls coarse-grained—batch operations rather than calling native methods in tight loops. Use `System.loadLibrary()` in a static block, handling `UnsatisfiedLinkError` gracefully for missing ABIs. Profile native heap with Android Studio Native Memory Profiler. Ensure thread safety by attaching native threads to the JVM via `AttachCurrentThread`. Use `WeakGlobalRef` sparingly to avoid GC issues. Package only required ABIs (`arm64-v8a`, `armeabi-v7a`) to reduce APK size. Document JNI function signatures with `javah` or manual prototypes.

```cpp
extern "C" JNIEXPORT void JNICALL
Java_com_example_app_NativeLib_processBuffer(JNIEnv* env, jobject, jobject buffer) {
    auto* ptr = static_cast<char*>(env->GetDirectBufferAddress(buffer));
    jlong capacity = env->GetDirectBufferCapacity(buffer);
    // process without copy
}
```

Read more: <https://developer.android.com/training/articles/perf-jni>

**Q145.** How do you architect a photo editor with undo/redo, filters, and non-destructive editing using GPU acceleration? Use `GLSurfaceView` or `TextureView` with custom `GLRenderer` for real-time GPU filter previews via OpenGL ES shaders. Maintain an edit history stack as a list of `EditOperation` sealed classes (Crop, Filter, Rotate) applied sequentially. For non-destructive editing, store the original bitmap and the operation stack; render the final image by replaying operations on the GPU rather than mutating a single bitmap. Use `RenderScript` (deprecated) or `GPUImage` / custom shaders for filters. Implement undo by removing the last operation from the stack and re-rendering. Use `SurfaceTexture` for camera preview integration. Export the final image asynchronously using `CoroutineWorker` with `Dispatchers.Default`. Cache intermediate textures to avoid recomputing the full stack on every preview frame. Test memory usage with large images (12MP+) to avoid OOM.

```kotlin
sealed class EditOperation {
    data class Filter(val shader: String, val intensity: Float) : EditOperation()
    data class Crop(val rect: RectF) : EditOperation()
}
class PhotoEditor(private val glRenderer: GLRenderer) {
    private val history = mutableListOf<<EditOperation>()
    fun apply(op: EditOperation) { history.add(op); glRenderer.render(history) }
    fun undo() { history.removeLast(); glRenderer.render(history) }
}
```

Read more: <https://developer.android.com/guide/topics/graphics/opengl>

***

### Testing, Lint & Quality

**Q146.** How do you write custom Lint rules to enforce architectural boundaries in a multi-module project? Create an `IssueRegistry` extending `LintRegistry` and registering custom `Detector` implementations. Implement a `Detector` with `UastScanner` to inspect Kotlin/Java AST nodes, checking for illegal imports (e.g., `android.content.Context` in ViewModels) or cross-module dependencies. Define `Issue` metadata with severity (`Severity.ERROR`), category, and explanation. Package the rules in a dedicated Java/Kotlin module applied via `lintChecks` dependency. Run custom lint alongside Android Lint via `./gradlew lint`. For module boundary enforcement, detect `import` statements referencing forbidden packages or use `PsiJavaFile` analysis. Use `Context.getProject()` to identify module names and enforce that `feature:a` doesn't import `feature:b:impl`. Document rules with quickfix suggestions. Update the registry when adding new detectors. Test detectors with `LintDetectorTest` for unit-level validation.

```kotlin
class ViewModelContextDetector : Detector(), Detector.UastScanner {
    override fun getApplicableUastTypes() = listOf(UImportStatement::class.java)
    override fun visitNode(context: JavaContext, node: UElement) {
        val import = (node as UImportStatement).importReference?.asSourceString()
        if (import?.contains("android.content.Context") == true && isInViewModel(context)) {
            context.report(ISSUE, node, context.getLocation(node), "ViewModels must not depend on Context")
        }
    }
    companion object {
        val ISSUE = Issue.create("ViewModelContext", "Context in ViewModel", "...", Category.CORRECTNESS, 5, Severity.ERROR, Implementation(ViewModelContextDetector::class.java, Scope.JAVA_FILE))
    }
}
```

Read more: <https://developer.android.com/studio/write/lint>

**Q147.** How do you implement and test accessibility services integration beyond basic TalkBack support? Implement `AccessibilityService` for custom hardware or assistive technology integration, overriding `onAccessibilityEvent()` to react to window changes and `onInterrupt()` for cleanup. Use `AccessibilityNodeInfo` to programmatically interact with UI elements from the service. For app accessibility, ensure custom views implement `ExploreByTouchHelper` or override `onInitializeAccessibilityNodeInfo()` to expose semantics. Test with Accessibility Scanner, TalkBack, and Switch Access. Use `uiautomator` for automated accessibility testing querying nodes by content description. Verify focus traversal order with keyboard navigation (Tab/Shift+Tab). Support `AccessibilityAction`s like `ACTION_CLICK` and `ACTION_SCROLL_FORWARD`. Handle `AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED` efficiently to avoid infinite loops. Document minimum accessibility requirements in PR checklists. For Braille displays, ensure text content is fully exposed via `contentDescription` or view text.

```kotlin
class MyAccessibilityService : AccessibilityService() {
    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        if (event.eventType == AccessibilityEvent.TYPE_VIEW_CLICKED) {
            val node = event.source ?: return
            if (node.className == "android.widget.Button") {
                // perform assistive action
            }
        }
    }
    override fun onInterrupt() {}
}
```

Read more: <https://developer.android.com/guide/topics/ui/accessibility>

**Q148.** How do you handle complex notification interactions including direct reply, progress, and grouped summaries? Use `NotificationCompat.Builder` with `addAction` containing a `RemoteInput` for direct reply actions, processing the input via a `BroadcastReceiver` or `Service`. For progress notifications, use `setProgress(max, current, false)` and update via `NotificationManager.notify()` with the same ID. Group related notifications using `setGroup()` and provide a summary notification with `setGroupSummary(true)` and `InboxStyle` or `BigTextStyle`. Use notification channels with appropriate importance levels and sound/vibration settings. Handle notification dismissal via `DeleteIntent`. Use `BubbleMetadata` for conversations on Android 11+. For media, use `MediaStyle` with `MediaSession` token. Test notification behavior under Do Not Disturb and with notification permission (Android 13+). Use `NotificationManager.areNotificationsEnabled()` to check status before posting.

```kotlin
val replyLabel = "Reply"
val remoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build()
val replyPendingIntent = PendingIntent.getBroadcast(context, 0, Intent(context, ReplyReceiver::class.java), FLAG_MUTABLE)
val action = NotificationCompat.Action.Builder(R.drawable.reply, "Reply", replyPendingIntent)
    .addRemoteInput(remoteInput).build()
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
    .addAction(action).setSmallIcon(R.drawable.ic_message).build()
```

Read more: <https://developer.android.com/develop/ui/views/notifications>

***

### Startup & Optimization

**Q149.** How do you use the App Startup library to optimize initialization order and reduce cold start time? Use `InitializationProvider` as a content provider that automatically discovers `Initializer<T>` implementations via manifest metadata, eliminating manual `Application.onCreate` bloat. Implement `Initializer<T>` for each library (WorkManager, Timber, Firebase) with `dependencies()` declaring prerequisites. The library initializes components in topological order based on the dependency graph. For lazy initialization, don't list the initializer in manifest; call `AppInitializer.getInstance(context).initializeComponent(MyInitializer::class.java)` manually when needed. Move non-critical initialization to background threads within `Initializer.create()`. Combine with `ProfileInstaller` to ship Baseline Profiles. Measure startup improvement via `Macrobenchmark`. Avoid using `ContentProvider` directly for libraries that support Startup library integration. The overhead is minimal compared to manual initialization, and dependency ordering is explicit rather than implicit.

```kotlin
class WorkManagerInitializer : Initializer<<WorkManager> {
    override fun create(context: Context): WorkManager {
        val config = Configuration.Builder().setMinimumLoggingLevel(android.util.Log.DEBUG).build()
        WorkManager.initialize(context, config)
        return WorkManager.getInstance(context)
    }
    override fun dependencies() = listOf(TimberInitializer::class.java)
}
```

Read more: <https://developer.android.com/topic/libraries/app-startup>

**Q150.** How do you architect large-scale A/B testing of UI and behavior in an Android app? Use a server-driven feature flag system (Firebase Remote Config, LaunchDarkly, or custom) to assign users to experiment buckets deterministically based on stable user IDs or device hashes. Define experiments in a data class `Experiment(val name: String, val variant: String)`. Evaluate variants in ViewModels or UseCases, never in UI directly, to keep logic testable. Use `Analytics` to log exposure events (`experiment_exposed`) for analysis. Ensure assignment is consistent across sessions by caching the variant locally. For UI experiments, use Compose's conditional rendering or View-based visibility toggles. For backend-dependent experiments, include the variant in API headers so the backend can coordinate behavior. Run experiments for statistically significant durations (2 weeks minimum). Use holdout groups (0% vs 100%) to measure long-term impact. Document experiment hypotheses and success metrics before launch.

```kotlin
class ExperimentManager @Inject constructor(private val remoteConfig: FirebaseRemoteConfig) {
    fun getVariant(experiment: String): String {
        val userHash = userId.hashCode().absoluteValue % 100
        return if (userHash < remoteConfig.getLong("${experiment}_threshold")) "treatment" else "control"
    }
}
```

Read more: <https://developer.android.com/topic/architecture>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://notes.tejpratapsingh.com/android-tips/interview/interview-prep-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
