· Dhiraj Gupta · Android  · 34 min read

Android Developer Interview Questions

70 questions (with answers) to help you prepare for your interview

70 questions (with answers) to help you prepare for your interview

These questions should help you prepare for standard questions that might get asked in a developer interview. The qeustions range from easy to advanced; answers include links to relevant resources where possible, should you want to learn more on the topic.

  1. Android Fundamentals
  2. Android Architecture Components
  3. Kotlin and Android
  4. Advanced Android Concepts
  5. Security in Android
  6. Latest Android Features
  7. Bluetooth
  8. Camera
  9. Location Services and Maps
  10. Firebase
  11. Kotlin Multiplatform
  12. Kotlin Flows and Coroutines
  13. Modern Android App Architecture
  14. Android Jetpack and Architecture Components

Android Fundamentals

1. Explain the difference between an Activity and a Fragment. When would you choose one over the other?

Activities and Fragments are both key components in Android development, but they serve different purposes:

Activities:

  • Represent a single screen with a user interface
  • Manage user interactions on that screen
  • Have their own lifecycle

Fragments:

  • Represent a portion of a user interface within an Activity
  • Can be reused across multiple Activities
  • Have their own lifecycle, but it’s influenced by the host Activity’s lifecycle

You would choose an Activity when you need a full-screen interface that operates independently. Fragments are preferable when you want to create modular, reusable UI components or when you need to support different layouts for various screen sizes.

For a visual representation of the relationship between Activities and Fragments, see this diagram: Activity and Fragment Relationship

Further reading: Android Developers: Fragments

2. What is the purpose of the AndroidManifest.xml file?

The AndroidManifest.xml file serves several crucial purposes in an Android application:

  • Declares the application’s package name
  • Lists all components of the application (activities, services, broadcast receivers, content providers)
  • Specifies required permissions
  • Declares the minimum and target API levels
  • Defines hardware and software features used or required by the app
  • Lists any libraries the application needs to be linked against

For more details, refer to the Android Developers: App Manifest Overview

3. Describe the Android application lifecycle.

The Android application lifecycle consists of several states that an app can be in:

  1. Not running: The app hasn’t been launched or was completely terminated.
  2. Stopped: The app was running but is no longer visible to the user.
  3. Paused: The app is visible but not in the foreground (partially obscured).
  4. Resumed/Running: The app is in the foreground and has user focus.

The system can terminate stopped or paused apps if it needs to free up memory. Understanding this lifecycle is crucial for managing app resources and preserving user data.

For a visual representation of the lifecycle, see: Activity Lifecycle Diagram

Further reading: Android Developers: Activity Lifecycle

4. Explain the difference between Service and IntentService. When would you use each?

Service:

  • Runs in the background indefinitely
  • Runs on the main thread by default
  • Must be stopped explicitly or by the system

IntentService:

  • Handles asynchronous requests in the background
  • Creates a worker thread to handle requests
  • Stops itself when it finishes its work

Use a Service when you need long-running operations without a predefined stopping point. Use IntentService for shorter, sequential background tasks that don’t need to run simultaneously.

Note: IntentService is deprecated as of Android 11. For modern alternatives, consider using WorkManager or Kotlin coroutines.

Further reading: Android Developers: Services

5. What is the purpose of the Application class in Android?

The Application class in Android:

  • Is instantiated before any other class when the process for your application is created
  • Maintains global application state
  • Can be used to initialize app-wide resources
  • Allows you to override application-level functionality

Example of a custom Application class:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        // Initialize app-wide resources here
    }
}

For more information, see: Android Developers: Application

6. Describe the different launch modes in Android and their use cases.

Android provides four launch modes:

  1. Standard: Default mode. Creates a new instance of the activity in the task every time.
  2. SingleTop: If an instance of the activity already exists at the top of the stack, it reuses that instance.
  3. SingleTask: Creates a new task and instantiates the activity at the root of the new task.
  4. SingleInstance: Similar to SingleTask, but the task can only contain that single activity.

Use cases:

  • Standard: For most activities
  • SingleTop: For activities that receive frequent notifications (e.g., messaging app)
  • SingleTask: For “home” activities or activities that should only have one instance (e.g., shopping cart)
  • SingleInstance: For activities that should never have other activities in their task (e.g., launcher)

For a visual representation of launch modes, see: Launch Mode Diagrams

Further reading: Android Developers: Tasks and Back Stack

Android Architecture Components

7. What are the key components of Android Jetpack, and how do they improve app development?

Android Jetpack is a set of libraries, tools, and architectural guidance designed to help developers build high-quality apps more easily. Key components include:

  • ViewModel: Manages UI-related data, surviving configuration changes
  • LiveData: Provides observable data holders that respect the app lifecycle
  • Room: Provides an abstraction layer over SQLite for easier database operations
  • Navigation: Handles in-app navigation
  • WorkManager: Manages background tasks
  • DataBinding: Binds UI components to data sources declaratively

These components improve app development by providing standardized, tested solutions to common development challenges, reducing boilerplate code, and encouraging best practices in app architecture.

For an overview of Jetpack components, see: Android Jetpack Components

Further reading: Android Developers: Jetpack

8. Explain the MVVM (Model-View-ViewModel) architecture pattern and its benefits in Android development.

MVVM is an architectural pattern that separates the development of the graphical user interface from the business logic or back-end logic of the application:

  • Model: Represents the data and business logic
  • View: Represents the UI components
  • ViewModel: Acts as a bridge between the Model and View, handling UI logic and state

Benefits of MVVM in Android development:

  • Improved separation of concerns
  • Easier unit testing
  • Better maintainability and scalability
  • Survives configuration changes
  • Reduces coupling between UI and business logic

For a visual representation of MVVM, see: MVVM Architecture Diagram

Further reading: Android Developers: Guide to App Architecture

9. How does the Paging library work, and what problems does it solve?

The Paging library:

  • Loads and displays small chunks of data at a time
  • Integrates with RecyclerView to load data as the user scrolls
  • Supports loading from various data sources (network, database)
  • Handles configuration changes and process death gracefully

It solves problems like:

  • Efficient memory usage
  • Smooth scrolling performance
  • Reducing network bandwidth and battery usage

Example of using Paging with Room and Retrofit:

@Dao
interface UserDao {
    @Query("SELECT * FROM users ORDER BY name ASC")
    fun pagingSource(): PagingSource<Int, User>
}

@OptIn(ExperimentalPagingApi::class)
val pagingFlow = Pager(
    config = PagingConfig(pageSize = 20),
    remoteMediator = UserRemoteMediator(database, api)
) {
    database.userDao().pagingSource()
}.flow.cachedIn(viewModelScope)

For a visual representation of the Paging library architecture, see: Paging Library Architecture

Further reading: Android Developers: Paging library overview

10. Explain the concept of Data Binding and how it differs from View Binding.

Data Binding:

  • Allows you to bind UI components in layouts to data sources in your app using a declarative format
  • Supports two-way data binding
  • Enables the use of expressions in XML layouts

View Binding:

  • Generates a binding class for each XML layout file
  • Provides null-safe access to view references
  • Lighter weight and has faster compilation than Data Binding

Use Data Binding for complex layouts with dynamic content, and View Binding for simpler layouts where you just need safe view access.

For more information on Data Binding, see: Android Developers: Data Binding Library

For View Binding, refer to: Android Developers: View Binding

Kotlin and Android

11. What are the advantages of using Kotlin for Android development compared to Java?

Kotlin offers several advantages over Java for Android development:

  • Conciseness: Kotlin requires less boilerplate code
  • Null safety: Kotlin’s type system distinguishes between nullable and non-nullable types
  • Extension functions: Allow adding methods to existing classes without modifying their source code
  • Coroutines: Simplify asynchronous programming
  • Data classes: Automatically generate common methods for classes that just hold data
  • Smart casts: Automatically cast types in many cases
  • Interoperability: Kotlin is fully interoperable with Java

For a comparison between Kotlin and Java, see: Kotlin vs Java

Further reading: Android Developers: Kotlin on Android

12. Explain Kotlin coroutines and how they improve asynchronous programming in Android.

Kotlin coroutines are a way to write asynchronous, non-blocking code in a sequential manner. They improve asynchronous programming in Android by:

  • Simplifying code that executes asynchronously
  • Reducing the need for callbacks
  • Providing built-in cancellation support
  • Offering structured concurrency
  • Integrating well with Android Jetpack libraries

Example of using coroutines for a network request:

viewModelScope.launch {
    try {
        val result = withContext(Dispatchers.IO) {
            api.fetchData()
        }
        // Update UI with result
    } catch (e: Exception) {
        // Handle error
    }
}

For a visual representation of coroutine concepts, see: Coroutines Basics

Further reading: Android Developers: Coroutines on Android

13. Explain Kotlin’s extension functions and provide an example of how they can be useful in Android development.

Kotlin extension functions allow you to add new functions to existing classes without modifying their source code. They’re particularly useful in Android development for adding utility functions to framework classes.

Example:

fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

// Usage
context.showToast("Hello, World!")

For more examples and details, see: Kotlin Docs: Extensions

14. How do Kotlin’s null safety features help prevent null pointer exceptions, and what are some best practices for handling nullability in Android?

Kotlin’s null safety features include:

  • Nullable and non-nullable types
  • Safe call operator (?.)
  • Elvis operator (?:)
  • Not-null assertion operator (!!)

Best practices:

  • Use non-nullable types whenever possible
  • Use the safe call operator for nullable types
  • Provide default values using the Elvis operator
  • Avoid using the not-null assertion operator unless absolutely necessary

Example:

val name: String? = null
val length = name?.length ?: 0

For more information on Kotlin’s null safety, see: Kotlin Docs: Null Safety

15. Describe Kotlin’s sealed classes and how they can be used in Android development.

Sealed classes in Kotlin:

  • Represent restricted class hierarchies
  • All subclasses must be defined in the same file as the sealed class
  • Are particularly useful for representing a finite set of states or types

Example use in Android:

sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}

// Usage
when (uiState) {
    is UiState.Loading -> showLoadingIndicator()
    is UiState.Success -> displayData(uiState.data)
    is UiState.Error -> showErrorMessage(uiState.message)
}

For more details on sealed classes, see: Kotlin Docs: Sealed Classes

Advanced Android Concepts

16. How would you implement deep linking in an Android app, and what are some common use cases?

Implementing deep linking involves:

  1. Adding intent filters in the AndroidManifest.xml
  2. Handling the incoming intent in your activity

Common use cases:

  • Opening specific content directly from notifications
  • Sharing content to specific screens within your app
  • Integrating with other apps or web services

Example manifest entry:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="www.example.com" android:pathPrefix="/product" />
</intent-filter>

For more information on deep linking, see: Android Developers: Deep linking

17. Explain the concept of Content Providers and when you would use them in an Android application.

Content Providers:

  • Manage access to a structured set of data
  • Provide a standard interface for other applications to query or modify the data
  • Enable data sharing between applications

Use Content Providers when:

  • You want to share data with other applications
  • You need a standard interface for accessing your app’s data
  • You’re working with complex data sets that benefit from the query capabilities

Example of querying a Content Provider:

val cursor = contentResolver.query(
    ContactsContract.Contacts.CONTENT_URI,
    null,
    null,
    null,
    null
)

For more details on Content Providers, see: Android Developers: Content providers

18. How would you implement offline caching in an Android app, and what are some strategies for keeping offline data in sync with a server?

Implementing offline caching:

  1. Use Room database for local storage
  2. Implement a Repository pattern to manage data sources
  3. Use WorkManager for background sync

Strategies for keeping data in sync:

  • Implement a sync adapter
  • Use timestamps to track changes
  • Implement conflict resolution logic
  • Use push notifications for real-time updates

Example of a simple caching strategy:

class Repository(private val api: Api, private val dao: Dao) {
    suspend fun getData(): List<Item> {
        return try {
            val networkData = api.fetchData()
            dao.insertAll(networkData)
            networkData
        } catch (e: Exception) {
            dao.getAll()
        }
    }
}

For more details on offline caching, see: Android Developers: Save data in a local database using Room

19. Explain the concept of Test-Driven Development (TDD) and how you would apply it in Android development.

Test-Driven Development (TDD) is a software development process where you write tests before writing the actual code. The process typically follows these steps:

  1. Write a failing test
  2. Write the minimum amount of code to make the test pass
  3. Refactor the code

Applying TDD in Android development:

  • Write unit tests for business logic and ViewModels
  • Use Espresso for UI tests
  • Mock dependencies to isolate the code being tested

Example of a TDD approach for a ViewModel:

@Test
fun `fetchData should update LiveData on success`() {
    // Given
    val repository = mock<Repository>()
    whenever(repository.fetchData()).thenReturn(flowOf(listOf(Item("Test"))))
    val viewModel = MyViewModel(repository)

    // When
    viewModel.fetchData()

    // Then
    assertEquals(listOf(Item("Test")), viewModel.data.value)
}

For more information on TDD in Android, see: Android Developers: Test-driven development on Android

20. How would you profile and optimize the startup time of an Android application?

To profile and optimize startup time:

  1. Use Android Studio’s CPU Profiler to identify bottlenecks
  2. Implement lazy initialization of resources
  3. Use AsyncLayoutInflater for complex layouts
  4. Optimize database operations
  5. Minimize network calls during startup
  6. Use App Startup library to initialize components efficiently

Example of using App Startup:

class MyInitializer : Initializer<Unit> {
    override fun create(context: Context) {
        // Initialize components here
    }

    override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}

For more details on optimizing app startup, see: Android Developers: Optimize your app startup time

Security in Android

21. How would you securely store sensitive data in an Android application?

To securely store sensitive data:

  1. Use Android Keystore for cryptographic keys
  2. Encrypt sensitive data before storing
  3. Use EncryptedSharedPreferences for small amounts of sensitive data
  4. For larger datasets, use encrypted databases like SQLCipher

Example of using EncryptedSharedPreferences:

val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val sharedPreferences = EncryptedSharedPreferences.create(
    context,
    "secret_shared_prefs",
    masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

For more information on secure data storage, see: Android Developers: Store data securely

22. Explain the concept of certificate pinning and how you would implement it in an Android app.

Certificate pinning is a security technique that associates a host with its expected X509 certificate or public key. It helps prevent man-in-the-middle attacks.

To implement certificate pinning:

  1. Obtain the certificate or public key of your server
  2. Add the certificate or key hash to your app’s network security configuration
  3. Use OkHttp’s CertificatePinner for more dynamic pinning

Example using network security configuration:

<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <pin-set>
            <pin digest="SHA-256">base64 encoded pin</pin>
        </pin-set>
    </domain-config>
</network-security-config>

For more details on certificate pinning, see: Android Developers: Network security configuration

Latest Android Features

23. How would you implement a custom Material You theme in an Android app?

Implementing a custom Material You theme involves:

  1. Using the Material Design 3 library
  2. Defining a custom color scheme
  3. Using dynamic color where appropriate
  4. Updating your app’s theme to use Material You components

Example of defining a custom color scheme:

<style name="Theme.MyApp" parent="Theme.Material3.DayNight">
    <item name="colorPrimary">@color/md_theme_primary</item>
    <item name="colorOnPrimary">@color/md_theme_onPrimary</item>
    <!-- Define other color attributes -->
</style>

For more information on implementing Material You, see: Android Developers: Material Design 3

24. Explain how you would adapt your app for foldable devices and large screens.

Adapting for foldables and large screens:

  1. Use responsive layouts (ConstraintLayout, responsive grids)
  2. Implement multi-pane layouts for larger screens
  3. Handle configuration changes and screen state changes
  4. Use WindowManager to detect fold state
  5. Test on various screen sizes and foldable emulators

Example of detecting fold state:

val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
windowManager.currentWindowMetrics.windowInsets.displayCutout?.let { cutout ->
    // Handle fold
}

For more details on adapting for foldables, see: Android Developers: Build for foldables

25. How would you approach internationalizing and localizing an Android application?

Internationalizing and localizing an Android app involves:

  1. Extracting all strings into resources
  2. Using string formatting for variables
  3. Providing translations in different values-<language> folders
  4. Handling RTL layouts
  5. Considering cultural differences in imagery and icons
  6. Testing thoroughly with different locales

Example of a localized string resource:

<!-- values/strings.xml -->
<string name="greeting">Hello, %1$s!</string>

<!-- values-es/strings.xml -->
<string name="greeting">¡Hola, %1$s!</string>

For more information on localization, see: Android Developers: Localize your app

26. Describe how you would implement a custom view with complex touch interactions.

Implementing a custom view with complex touch interactions:

  1. Extend View or an existing widget class
  2. Override onTouchEvent() to handle touch events
  3. Use VelocityTracker for gesture detection
  4. Implement GestureDetector.OnGestureListener for common gestures
  5. Use ViewConfiguration to get system constants

Example of handling touch in a custom view:

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
        override fun onDown(e: MotionEvent): Boolean = true
        override fun onSingleTapUp(e: MotionEvent): Boolean {
            // Handle tap
            return true
        }
    })

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return gestureDetector.onTouchEvent(event) || super.onTouchEvent(event)
    }
}

For more details on custom views, see: Android Developers: Custom View Components

27. How would you implement real-time features in an Android app, such as live updates or chat functionality?

Implementing real-time features:

  1. Use WebSockets for bidirectional communication
  2. Implement Firebase Realtime Database or Firestore for real-time data syncing
  3. Use push notifications for important updates
  4. Consider using MQTT for IoT applications

Example of using OkHttp for WebSocket:

val client = OkHttpClient()
val request = Request.Builder().url("ws://your-websocket-url").build()
val webSocket = client.newWebSocket(request, object : WebSocketListener() {
    override fun onMessage(webSocket: WebSocket, text: String) {
        // Handle incoming message
    }
})

// Sending a message
webSocket.send("Hello, WebSocket!")

For more information on real-time features, see: Firebase Realtime Database

28. Explain the concept of Dependency Injection and how it’s implemented in Android using libraries like Dagger or Hilt.

Dependency Injection (DI) is a design pattern that allows for loosely coupled code by providing dependencies to a class instead of having the class create them.

Hilt, built on top of Dagger, is the recommended DI library for Android. It simplifies Dagger-related infrastructure for Android apps.

Example of using Hilt:

@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var someClass: SomeClass
}

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideSomeClass(): SomeClass = SomeClass()
}

For more details on Dependency Injection with Hilt, see: Android Developers: Dependency injection with Hilt

29. How would you implement app modularization in Android, and what are its benefits?

App modularization involves breaking down an app into smaller, independent modules. Benefits include:

  1. Improved build times
  2. Better separation of concerns
  3. Reusability of code
  4. Easier maintenance and testing
  5. Support for dynamic feature modules

Steps to implement modularization:

  1. Identify logical components of your app
  2. Create separate Gradle modules for each component
  3. Define clear interfaces between modules
  4. Use dynamic feature modules for on-demand delivery

For more information on app modularization, see: Android Developers: Guide to Android app modularization

30. Explain the concept of Jetpack Compose and how it differs from traditional View-based UI development.

Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Key differences from View-based development:

  1. Declarative UI paradigm instead of imperative
  2. No XML layouts; UI is defined in Kotlin
  3. Automatic UI updates when state changes
  4. Easier to create complex, custom layouts

Example of a simple Compose UI:

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

For more details on Jetpack Compose, see: Android Developers: Jetpack Compose

31. How would you implement and manage different product flavors in an Android app?

Product flavors allow you to create different versions of your app (e.g., free vs. paid) from a single codebase. To implement:

  1. Define flavors in your app’s build.gradle file
  2. Create flavor-specific resources and code
  3. Use BuildConfig to access flavor-specific values

Example of defining flavors:

android {
    ...
    flavorDimensions "version"
    productFlavors {
        free {
            dimension "version"
            applicationIdSuffix ".free"
            versionNameSuffix "-free"
        }
        paid {
            dimension "version"
            applicationIdSuffix ".paid"
            versionNameSuffix "-paid"
        }
    }
}

For more information on product flavors, see: Android Developers: Configure build variants

32. Describe the process of implementing in-app purchases in an Android application.

Implementing in-app purchases involves:

  1. Setting up your Google Play Console
  2. Configuring your app’s build.gradle file
  3. Implementing the Google Play Billing Library
  4. Handling purchase flow and verification

Example of initiating a purchase:

private val billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases()
    .build()

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingClient.BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

For more details on implementing in-app purchases, see: Android Developers: Google Play Billing Library

33. How would you implement and manage app shortcuts in Android?

App shortcuts provide quick access to specific actions within your app. To implement:

  1. Define static shortcuts in a resource file
  2. Create dynamic shortcuts programmatically
  3. Handle shortcut intents in your app

Example of defining a static shortcut in XML:

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <shortcut
        android:shortcutId="compose"
        android:enabled="true"
        android:icon="@drawable/ic_compose"
        android:shortcutShortLabel="@string/compose_shortcut_short_label"
        android:shortcutLongLabel="@string/compose_shortcut_long_label">
        <intent
            android:action="android.intent.action.VIEW"
            android:targetPackage="com.example.myapp"
            android:targetClass="com.example.myapp.ComposeActivity" />
    </shortcut>
</shortcuts>

For more information on app shortcuts, see: Android Developers: App Shortcuts

34. Explain the concept of Android App Bundles and how they differ from APKs.

Android App Bundles are a publishing format that includes all your app’s compiled code and resources, but defers APK generation and signing to Google Play. Key differences:

  1. Smaller app size for users (only necessary code/resources are downloaded)
  2. Dynamic feature delivery
  3. Easy configuration for different device architectures and languages

To create an App Bundle:

android {
    bundle {
        language {
            enableSplit = true
        }
        density {
            enableSplit = true
        }
        abi {
            enableSplit = true
        }
    }
}

For more details on App Bundles, see: Android Developers: App Bundles

35. Describe how you would implement a custom Gradle plugin for your Android project.

Custom Gradle plugins can automate build processes or add new tasks. To create one:

  1. Create a new module for your plugin
  2. Implement the Plugin interface
  3. Register your plugin

Example:

class MyCustomPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.task("myCustomTask") {
            doLast {
                println("My custom task executed!")
            }
        }
    }
}

For more details on custom Gradle plugins, see: Gradle User Manual: Custom Plugins

36. How would you implement a custom lint check for your Android project?

Custom lint checks can help enforce project-specific code style or catch common errors. To implement:

  1. Create a new Java/Kotlin module
  2. Implement a custom Issue and Detector
  3. Register your lint check

Example:

class MyCustomDetector : Detector(), Detector.UastScanner {
    companion object {
        val ISSUE = Issue.create(
            id = "MyCustomIssue",
            briefDescription = "This is a custom lint check",
            explanation = "Longer explanation here",
            category = Category.CORRECTNESS,
            priority = 6,
            severity = Severity.WARNING,
            implementation = Implementation(MyCustomDetector::class.java, Scope.JAVA_FILE_SCOPE)
        )
    }

    override fun getApplicableUastTypes() = listOf(UClass::class.java)

    override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
        override fun visitClass(node: UClass) {
            // Your lint check logic here
        }
    }
}

For more information on custom lint checks, see: Android Developers: Create a custom lint check

37. Explain how you would implement and use the Navigation component in Android.

The Navigation component helps you implement navigation, from simple button clicks to more complex patterns like app bars and the navigation drawer. To use:

  1. Define a navigation graph
  2. Add NavHost to your layout
  3. Use NavController to manage app navigation

Example of a navigation graph (nav_graph.xml):

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.myapp.HomeFragment"
        android:label="fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_detailFragment"
            app:destination="@id/detailFragment" />
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.myapp.DetailFragment"
        android:label="fragment_detail" />
</navigation>

For more details on the Navigation component, see: Android Developers: Navigation

38. How would you implement and use the CameraX library in an Android application?

CameraX is a Jetpack library that makes camera app development easier. To use:

  1. Add CameraX dependencies
  2. Request camera permissions
  3. Set up the camera provider
  4. Implement use cases (Preview, ImageCapture, ImageAnalysis)

Example:

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener({
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

        val preview = Preview.Builder()
            .build()
            .also {
                it.setSurfaceProvider(viewFinder.surfaceProvider)
            }

        val imageCapture = ImageCapture.Builder()
            .build()

        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture)
        } catch(exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }

    }, ContextCompat.getMainExecutor(this))
}

For more information on CameraX, see: Android Developers: CameraX overview

39. How would you implement and use the Room persistence library in an Android application?

Room is a part of Android Jetpack and provides an abstraction layer over SQLite. To use Room:

  1. Define entities (database tables)
  2. Create Data Access Objects (DAOs)
  3. Create the database class
  4. Use the database in your app

Example:

@Entity
data class User(
    @PrimaryKey val uid: Int,
    @ColumnInfo(name = "first_name") val firstName: String?,
    @ColumnInfo(name = "last_name") val lastName: String?
)

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Insert
    fun insertAll(vararg users: User)
}

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

// Usage
val db = Room.databaseBuilder(
    applicationContext,
    AppDatabase::class.java, "database-name"
).build()

val userDao = db.userDao()
val users: List<User> = userDao.getAll()

For more details on Room, see: Android Developers: Save data in a local database using Room

40. Explain the concept of Kotlin Flow and how it differs from RxJava.

Kotlin Flow is a cold asynchronous data stream that sequentially emits values and completes normally or with an exception. Key differences from RxJava:

  1. Built-in support for coroutines
  2. Simpler API
  3. Cold by default (RxJava has both hot and cold observables)
  4. Built-in support for structured concurrency

Example of using Flow:

fun fetchData(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

lifecycleScope.launch {
    fetchData().collect { value ->
        println(value)
    }
}

For more information on Kotlin Flow, see: Kotlin Docs: Asynchronous Flow

41. How would you implement and use the Paging 3 library in an Android application?

Paging 3 is part of Android Jetpack and helps you load and display pages of data from a larger dataset. To use:

  1. Implement a PagingSource
  2. Create a Pager
  3. Collect the PagingData in your ViewModel
  4. Use a PagingDataAdapter in your RecyclerView

Example:

class MyPagingSource(private val backend: MyBackendService) : PagingSource<Int, MyItem>() {
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, MyItem> {
        try {
            val nextPageNumber = params.key ?: 1
            val response = backend.getItems(nextPageNumber)
            return LoadResult.Page(
                data = response.items,
                prevKey = if (nextPageNumber > 1) nextPageNumber - 1 else null,
                nextKey = if (nextPageNumber < response.totalPages) nextPageNumber + 1 else null
            )
        } catch (e: Exception) {
            return LoadResult.Error(e)
        }
    }
}

@OptIn(ExperimentalPagingApi::class)
val flow = Pager(
    config = PagingConfig(pageSize = 20),
    pagingSourceFactory = { MyPagingSource(backend) }
).flow.cachedIn(viewModelScope)

For more details on Paging 3, see: Android Developers: Paging library overview

42. Describe how you would implement a custom View in Android and what lifecycle methods you need to override.

To implement a custom View:

  1. Create a class that extends View or an existing widget
  2. Implement constructors
  3. Override onMeasure() for custom sizing
  4. Override onDraw() for custom drawing
  5. Optionally override onTouchEvent() for touch handling

Example:

class CustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val desiredWidth = 100
        val desiredHeight = 100

        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)

        val width = when (widthMode) {
            MeasureSpec.EXACTLY -> widthSize
            MeasureSpec.AT_MOST -> min(desiredWidth, widthSize)
            else -> desiredWidth
        }

        val height = when (heightMode) {
            MeasureSpec.EXACTLY -> heightSize
            MeasureSpec.AT_MOST -> min(desiredHeight, heightSize)
            else -> desiredHeight
        }

        setMeasuredDimension(width, height)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // Custom drawing code here
    }
}

For more information on custom Views, see: Android Developers: Custom View Components

43. How would you implement and use the DataStore library in Android?

DataStore is a data storage solution that allows you to store key-value pairs or typed objects with protocol buffers. To use:

  1. Add DataStore dependencies
  2. Create a preferences DataStore or proto DataStore
  3. Read and write data using Flow

Example of using Preferences DataStore:

val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")

suspend fun incrementCounter() {
    dataStore.edit { settings ->
        val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
        settings[EXAMPLE_COUNTER] = currentCounterValue + 1
    }
}

val exampleCounterFlow: Flow<Int> = dataStore.data
    .map { preferences ->
        preferences[EXAMPLE_COUNTER] ?: 0
    }

For more details on DataStore, see: Android Developers: DataStore

Bluetooth:

44. How would you implement Bluetooth communication in an Android app?

Implementing Bluetooth communication in Android involves several steps:

  1. Declare Bluetooth permissions in the manifest
  2. Check if Bluetooth is supported and enabled
  3. Scan for nearby Bluetooth devices
  4. Connect to a device
  5. Manage the Bluetooth connection

Example of scanning for Bluetooth devices:

private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()

private val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
private val scanCallback = object : ScanCallback() {
    override fun onScanResult(callbackType: Int, result: ScanResult) {
        // Handle scan result
    }
}

private fun scanLeDevice() {
    bluetoothLeScanner?.startScan(scanCallback)
}

For more information on Bluetooth in Android, see: Android Developers: Bluetooth overview

45. What are the differences between Bluetooth Classic and Bluetooth Low Energy (BLE)?

Key differences:

  • Power consumption: BLE uses significantly less power
  • Data transfer rate: Classic is faster for large data transfers
  • Range: Classic has a longer range
  • Use cases: BLE is better for periodic, small data transfers; Classic for continuous, large data transfers
  • Pairing: BLE doesn’t always require pairing
  • Android API support: BLE has more modern, easier-to-use APIs

Camera:

46. How would you implement camera functionality in an Android app using the CameraX library?

CameraX is a Jetpack library that simplifies camera implementation. Steps include:

  1. Add CameraX dependencies
  2. Request camera permissions
  3. Set up a Preview use case
  4. Implement image capture or analysis

Example:

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener({
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

        val preview = Preview.Builder()
            .build()
            .also {
                it.setSurfaceProvider(viewFinder.surfaceProvider)
            }

        val imageCapture = ImageCapture.Builder()
            .build()

        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture)
        } catch(exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }

    }, ContextCompat.getMainExecutor(this))
}

For more details on CameraX, see: Android Developers: CameraX overview

47. How would you handle camera permissions in Android?

Handling camera permissions involves:

  1. Declaring permissions in the manifest
  2. Checking if permissions are granted
  3. Requesting permissions if not granted
  4. Handling the permission request result

Example:

private fun requestCameraPermission() {
    when {
        ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED -> {
            // Permission is granted, start camera
        }
        shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
            // Show an explanation to the user
        }
        else -> {
            requestPermissions(
                arrayOf(Manifest.permission.CAMERA),
                CAMERA_PERMISSION_REQUEST_CODE
            )
        }
    }
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    when (requestCode) {
        CAMERA_PERMISSION_REQUEST_CODE -> {
            if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                // Permission was granted, start camera
            } else {
                // Permission denied, handle accordingly
            }
            return
        }
    }
}

Location Services and Maps:

48. How would you implement location tracking in an Android app?

Implementing location tracking involves:

  1. Adding location permissions to the manifest
  2. Checking and requesting permissions at runtime
  3. Using FusedLocationProviderClient to get location updates

Example:

private lateinit var fusedLocationClient: FusedLocationProviderClient

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
}

@SuppressLint("MissingPermission")
private fun startLocationUpdates() {
    fusedLocationClient.requestLocationUpdates(locationRequest,
        locationCallback,
        Looper.getMainLooper())
}

private val locationCallback = object : LocationCallback() {
    override fun onLocationResult(locationResult: LocationResult) {
        for (location in locationResult.locations){
            // Update UI with location data
        }
    }
}

For more information on location services, see: Android Developers: Location and context overview

49. How would you integrate Google Maps into an Android app?

Integrating Google Maps involves:

  1. Getting a Google Maps API key
  2. Adding the Maps SDK for Android to your project
  3. Adding a map to your app’s UI
  4. Customizing the map as needed

Example of adding a map to your layout:

<fragment
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

And in your activity:

private lateinit var map: GoogleMap

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val mapFragment = supportFragmentManager
        .findFragmentById(R.id.map) as SupportMapFragment
    mapFragment.getMapAsync(this)
}

override fun onMapReady(googleMap: GoogleMap) {
    map = googleMap
    // Customize map here
}

For more details on Google Maps integration, see: Google Maps Platform: Maps SDK for Android

Firebase:

50. How would you implement push notifications using Firebase Cloud Messaging (FCM)?

Implementing push notifications with FCM involves:

  1. Setting up Firebase in your project
  2. Adding FCM dependency
  3. Implementing a service to handle FCM messages
  4. Registering the device with FCM
  5. Sending notifications from your server or Firebase Console

Example of a service to handle FCM messages:

class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // Handle FCM messages here
    }

    override fun onNewToken(token: String) {
        // Handle new token
    }
}

For more information on FCM, see: Firebase: Set up a Firebase Cloud Messaging client app on Android

51. How would you implement real-time database synchronization using Firebase Realtime Database?

Implementing real-time database sync with Firebase involves:

  1. Setting up Firebase in your project
  2. Adding Firebase Realtime Database dependency
  3. Writing data to the database
  4. Reading data and listening for changes

Example:

private lateinit var database: DatabaseReference

// Write data
fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)
    database.child("users").child(userId).setValue(user)
}

// Read data and listen for changes
val userListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        val user = dataSnapshot.getValue<User>()
        // Update UI with user data
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Handle error
    }
}
database.child("users").child(userId).addValueEventListener(userListener)

For more details on Firebase Realtime Database, see: Firebase: Read and Write Data on Android

Kotlin Multiplatform:

52. What is Kotlin Multiplatform, and how does it benefit mobile app development?

Kotlin Multiplatform is a feature that allows developers to share code between different platforms (e.g., Android, iOS, web). Benefits include:

  • Code reuse across platforms
  • Reduced development time and costs
  • Consistency across platforms
  • Platform-specific optimizations

Example of a multiplatform module:

expect class Platform() {
    val name: String
}

actual class Platform actual constructor() {
    actual val name: String = "Android"
}

class Greeting {
    fun greet(): String = "Hello, ${Platform().name}!"
}

For more information on Kotlin Multiplatform, see: Kotlin Docs: Multiplatform Programming

53. How would you structure a Kotlin Multiplatform project for Android and iOS?

Structuring a Kotlin Multiplatform project typically involves:

  1. Creating a shared module for common code
  2. Creating platform-specific modules (androidApp, iosApp)
  3. Defining expected and actual declarations for platform-specific implementations
  4. Using the shared module in platform-specific code

Example project structure:

my-project/
├── shared/
│   ├── src/
│   │   ├── commonMain/
│   │   ├── androidMain/
│   │   └── iosMain/
├── androidApp/
└── iosApp/

For more details on structuring Kotlin Multiplatform projects, see: Kotlin Docs: Create a multiplatform project

Kotlin Flows and Coroutines:

54. How do Kotlin Flows differ from RxJava Observables?

Key differences include:

  • Flows are built on top of coroutines
  • Flows are cold by default (RxJava has both hot and cold observables)
  • Flows have built-in support for structured concurrency
  • Flows have a simpler API
  • Flows integrate well with other Kotlin coroutines features

Example of using Flow:

fun fetchData(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}

lifecycleScope.launch {
    fetchData().collect { value ->
        println(value)
    }
}

For more information on Kotlin Flow, see: Kotlin Docs: Asynchronous Flow

55. Explain the concept of structured concurrency in Kotlin Coroutines.

Structured concurrency ensures that:

  • Coroutines are scoped to a specific lifecycle
  • All child coroutines complete before the parent coroutine completes
  • Exceptions in child coroutines are propagated to the parent

Example:

fun main() = runBlocking {
    launch {
        delay(200L)
        println("Task from runBlocking")
    }
    
    coroutineScope {
        launch {
            delay(500L)
            println("Task from nested launch")
        }
        delay(100L)
        println("Task from coroutine scope")
    }
    
    println("Coroutine scope is over")
}

For more details on structured concurrency, see: Kotlin Docs: Structured concurrency

Modern Android App Architecture

The main components of modern Android app architecture are:

  1. UI Layer
  2. Domain Layer
  3. Data Layer

Each layer has specific responsibilities:

  • UI Layer: Displays application data on the screen and serves as the primary point of user interaction
  • Domain Layer: Contains business logic and use cases
  • Data Layer: Manages application data and exposes it to the rest of the app

For more details, see: Android Developers: Guide to app architecture

57. Explain the purpose and components of the data layer in Android architecture.

The data layer is responsible for managing and exposing application data. Its main components are:

  1. Repositories: Act as single sources of truth for data, managing data operations and coordinating between multiple data sources
  2. Data Sources: Handle data operations for a specific source (e.g., network, local database)
  3. Models: Represent the data structure

Example of a repository:

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) {
    suspend fun getUser(id: String): Flow<User> = flow {
        emit(localDataSource.getUser(id))
        try {
            val remoteUser = remoteDataSource.getUser(id)
            localDataSource.saveUser(remoteUser)
            emit(remoteUser)
        } catch (e: Exception) {
            // Handle error
        }
    }
}

For more information, see: Android Developers: Data layer

58. What is the role of the domain layer in Android architecture, and how does it relate to use cases?

The domain layer is responsible for encapsulating complex business logic, or simple business logic that is reused by multiple ViewModels. It sits between the UI and data layers.

Use cases (also known as interactors) are the main components of the domain layer. They represent single, specific tasks or flows that can be carried out by the app. Use cases are used to promote reusability and separation of concerns.

Example of a use case:

class GetUserUseCase(private val userRepository: UserRepository) {
    suspend operator fun invoke(userId: String): Flow<User> {
        return userRepository.getUser(userId)
    }
}

For more details, see: Android Developers: Domain layer

59. How do you implement dependency injection in modern Android architecture?

Dependency injection in modern Android architecture is typically implemented using Hilt, which is built on top of Dagger. Here’s how you might set it up:

  1. Add Hilt to your project
  2. Annotate your Application class with @HiltAndroidApp
  3. Annotate your activities, fragments, and ViewModels with @AndroidEntryPoint
  4. Create modules to provide dependencies

Example:

@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity()

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideUserRepository(
        localDataSource: UserLocalDataSource,
        remoteDataSource: UserRemoteDataSource
    ): UserRepository {
        return UserRepository(localDataSource, remoteDataSource)
    }
}

For more information, see: Android Developers: Dependency injection with Hilt

60. How do you handle data flow between layers in modern Android architecture?

Data typically flows from the data layer, through the domain layer (if present), to the UI layer. This is often implemented using Kotlin Flows or LiveData. Here’s a typical flow:

  1. Repository in the data layer exposes data as a Flow
  2. Use case in the domain layer may transform this data
  3. ViewModel in the UI layer collects the Flow and exposes it to the UI
  4. UI observes and reacts to the data

Example:

// Data Layer
class UserRepository {
    fun getUser(id: String): Flow<User> = flow {
        // Fetch user data
    }
}

// Domain Layer
class GetUserUseCase(private val repository: UserRepository) {
    operator fun invoke(id: String): Flow<User> = repository.getUser(id)
}

// UI Layer
class UserViewModel(private val getUserUseCase: GetUserUseCase) : ViewModel() {
    val user: StateFlow<User> = getUserUseCase("123")
        .stateIn(viewModelScope, SharingStarted.Lazily, User.empty())
}

For more details on data flow, see: Android Developers: Architecture: Recommended app architecture

Android Jetpack and Architecture Components

61. How do you handle error cases in modern Android architecture?

Error handling in modern Android architecture typically involves:

  1. Defining error states or models
  2. Propagating errors through the layers
  3. Handling errors in the UI layer

Example:

// Define a Result wrapper
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

// Use in Repository
class UserRepository {
    suspend fun getUser(id: String): Result<User> = try {
        Result.Success(api.getUser(id))
    } catch (e: Exception) {
        Result.Error(e)
    }
}

// Handle in ViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

    fun getUser(id: String) {
        viewModelScope.launch {
            _uiState.value = when (val result = repository.getUser(id)) {
                is Result.Success -> UiState.Success(result.data)
                is Result.Error -> UiState.Error(result.exception.message ?: "Unknown error")
            }
        }
    }
}

For more on error handling, see: Android Developers: App architecture: Expose errors

62. How does WorkManager handle background tasks, and when would you use it over other background processing options?

WorkManager:

  • Provides a unified API for deferrable background work
  • Handles compatibility across different Android versions
  • Supports one-time and periodic tasks
  • Respects battery optimization and system constraints

Use WorkManager for:

  • Tasks that should run even if the app exits or the device restarts
  • Scheduled tasks that need to run periodically
  • Tasks that require certain constraints (e.g., network availability, battery not low)

Example of using WorkManager:

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .build()

WorkManager.getInstance(context).enqueue(uploadWorkRequest)

For a visual representation of WorkManager’s task handling, see: WorkManager Task Handling

Further reading: Android Developers: WorkManager

63. How would you implement and use WorkManager for background tasks in Android?

WorkManager is part of Android Jetpack and provides a unified API for deferrable background work. To use:

  1. Define a Worker class
  2. Create and configure a WorkRequest
  3. Enqueue the WorkRequest with WorkManager

Example:

class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // Perform the work here
        return Result.success()
    }
}

val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build())
    .build()

WorkManager.getInstance(context).enqueue(uploadWorkRequest)

For more information on WorkManager, see: Android Developers: WorkManager

64. What is the purpose of the Lifecycle component in Android Jetpack?

The Lifecycle component helps manage the lifecycle of activities and fragments. It allows other components to observe lifecycle changes and adjust their behavior accordingly.

Key features:

  • Provides lifecycle-aware components
  • Helps prevent memory leaks and crashes due to lifecycle issues
  • Simplifies the process of handling lifecycle events

Example usage:

class MyObserver : LifecycleObserver {
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
        // Do something when the lifecycle owner resumes
    }
}

// In your activity or fragment
lifecycle.addObserver(MyObserver())

For more information, see: Android Developers: Lifecycle

65. How does the Paging library in Android Jetpack help with large datasets?

The Paging library helps load and display large datasets efficiently in RecyclerViews. It provides:

  • Gradual and smooth loading of data
  • Built-in support for different data sources (network, database)
  • Automatic updates when the dataset changes

Example usage:

@Dao
interface UserDao {
    @Query("SELECT * FROM users ORDER BY name ASC")
    fun pagingSource(): PagingSource<Int, User>
}

val flow = Pager(
    config = PagingConfig(pageSize = 20),
    pagingSourceFactory = { userDao.pagingSource() }
).flow

// In your ViewModel
val users = flow.cachedIn(viewModelScope)

For more details, see: Android Developers: Paging library overview

66. What is the purpose of the DataStore component in Android Jetpack?

DataStore is a data storage solution that allows you to store key-value pairs or typed objects with protocol buffers. It’s designed to replace SharedPreferences. Key features include:

  • Asynchronous API using Kotlin coroutines and Flow
  • Handles data corruption
  • Provides type safety

Example usage:

val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")

suspend fun incrementCounter() {
    dataStore.edit { settings ->
        val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
        settings[EXAMPLE_COUNTER] = currentCounterValue + 1
    }
}

For more information, see: Android Developers: DataStore

67. How does the CameraX library in Android Jetpack simplify camera development?

CameraX is a Jetpack library that simplifies camera app development. Key features include:

  • Consistent behavior across most Android devices
  • Easier implementation of common use cases (preview, image capture, image analysis)
  • Lifecycle-aware
  • Supports camera features like zoom, flash, and touch-to-focus

Example usage:

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener({
        val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

        val preview = Preview.Builder()
            .build()
            .also {
                it.setSurfaceProvider(viewFinder.surfaceProvider)
            }

        val imageCapture = ImageCapture.Builder().build()

        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture)
        } catch(exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }

    }, ContextCompat.getMainExecutor(this))
}

For more details, see: Android Developers: CameraX overview

68. What is the purpose of the Hilt library in Android Jetpack?

Hilt is a dependency injection library for Android that reduces the boilerplate of doing manual dependency injection. Key features include:

  • Built on top of the popular Dagger dependency injection library
  • Provides a standard way to do DI in your application
  • Scopes dependencies to various Android components automatically
  • Provides testing utilities for swapping out dependencies

Example usage:

@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity()

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton
    fun provideDatabase(@ApplicationContext context: Context): AppDatabase {
        return Room.databaseBuilder(context, AppDatabase::class.java, "app_database").build()
    }
}

For more information, see: Android Developers: Dependency injection with Hilt

69. How does the Compose library change UI development in Android?

Jetpack Compose is a modern toolkit for building native Android UI. It simplifies and accelerates UI development on Android. Key features include:

  • Declarative UI paradigm
  • Fully built in Kotlin
  • Powerful and intuitive Kotlin APIs
  • Compatible with existing UI toolkit
  • Less code, powerful tools, and intuitive APIs

Example usage:

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyTheme {
        Greeting("Android")
    }
}

For more details, see: Android Developers: Jetpack Compose

70. What is the purpose of the App Startup library in Android Jetpack?

The App Startup library provides a straightforward, performant way to initialize components at application startup. Key features include:

  • Automatic initialization of components
  • Manages dependencies between components
  • Allows lazy initialization of components

Example usage:

class ExampleInitializer : Initializer<ExampleLibrary> {
    override fun create(context: Context): ExampleLibrary {
        // Initialize and return the library
        return ExampleLibrary()
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // Define dependencies on other initializers
        return emptyList()
    }
}

For more information, see: Android Developers: App Startup

Back to Blog

Related Posts

View All Posts »
Mango Cheesecake Smoothie

Mango Cheesecake Smoothie

Summer means mangoes! This delightfully thick, creamy mango cheesecake milkshake / smoothie will take you back to childhood. 😊