· Dhiraj Gupta · Recipes · 34 min read
Android Developer Interview Questions
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.
- Android Fundamentals
- Android Architecture Components
- Kotlin and Android
- Advanced Android Concepts
- Security in Android
- Latest Android Features
- Bluetooth
- Camera
- Location Services and Maps
- Firebase
- Kotlin Multiplatform
- Kotlin Flows and Coroutines
- Modern Android App Architecture
- 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:
- Not running: The app hasn’t been launched or was completely terminated.
- Stopped: The app was running but is no longer visible to the user.
- Paused: The app is visible but not in the foreground (partially obscured).
- 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:
- Standard: Default mode. Creates a new instance of the activity in the task every time.
- SingleTop: If an instance of the activity already exists at the top of the stack, it reuses that instance.
- SingleTask: Creates a new task and instantiates the activity at the root of the new task.
- 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:
- Adding intent filters in the AndroidManifest.xml
- 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:
- Use Room database for local storage
- Implement a Repository pattern to manage data sources
- 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:
- Write a failing test
- Write the minimum amount of code to make the test pass
- 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:
- Use Android Studio’s CPU Profiler to identify bottlenecks
- Implement lazy initialization of resources
- Use AsyncLayoutInflater for complex layouts
- Optimize database operations
- Minimize network calls during startup
- 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:
- Use Android Keystore for cryptographic keys
- Encrypt sensitive data before storing
- Use EncryptedSharedPreferences for small amounts of sensitive data
- 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:
- Obtain the certificate or public key of your server
- Add the certificate or key hash to your app’s network security configuration
- 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:
- Using the Material Design 3 library
- Defining a custom color scheme
- Using dynamic color where appropriate
- 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:
- Use responsive layouts (ConstraintLayout, responsive grids)
- Implement multi-pane layouts for larger screens
- Handle configuration changes and screen state changes
- Use WindowManager to detect fold state
- 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:
- Extracting all strings into resources
- Using string formatting for variables
- Providing translations in different
values-<language>
folders - Handling RTL layouts
- Considering cultural differences in imagery and icons
- 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:
- Extend View or an existing widget class
- Override onTouchEvent() to handle touch events
- Use VelocityTracker for gesture detection
- Implement GestureDetector.OnGestureListener for common gestures
- 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:
- Use WebSockets for bidirectional communication
- Implement Firebase Realtime Database or Firestore for real-time data syncing
- Use push notifications for important updates
- 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:
- Improved build times
- Better separation of concerns
- Reusability of code
- Easier maintenance and testing
- Support for dynamic feature modules
Steps to implement modularization:
- Identify logical components of your app
- Create separate Gradle modules for each component
- Define clear interfaces between modules
- 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:
- Declarative UI paradigm instead of imperative
- No XML layouts; UI is defined in Kotlin
- Automatic UI updates when state changes
- 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:
- Define flavors in your app’s build.gradle file
- Create flavor-specific resources and code
- 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:
- Setting up your Google Play Console
- Configuring your app’s build.gradle file
- Implementing the Google Play Billing Library
- 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:
- Define static shortcuts in a resource file
- Create dynamic shortcuts programmatically
- 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:
- Smaller app size for users (only necessary code/resources are downloaded)
- Dynamic feature delivery
- 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:
- Create a new module for your plugin
- Implement the Plugin interface
- 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:
- Create a new Java/Kotlin module
- Implement a custom Issue and Detector
- 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:
- Define a navigation graph
- Add NavHost to your layout
- 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:
- Add CameraX dependencies
- Request camera permissions
- Set up the camera provider
- 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:
- Define entities (database tables)
- Create Data Access Objects (DAOs)
- Create the database class
- 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:
- Built-in support for coroutines
- Simpler API
- Cold by default (RxJava has both hot and cold observables)
- 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:
- Implement a PagingSource
- Create a Pager
- Collect the PagingData in your ViewModel
- 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:
- Create a class that extends View or an existing widget
- Implement constructors
- Override onMeasure() for custom sizing
- Override onDraw() for custom drawing
- 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:
- Add DataStore dependencies
- Create a preferences DataStore or proto DataStore
- 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:
- Declare Bluetooth permissions in the manifest
- Check if Bluetooth is supported and enabled
- Scan for nearby Bluetooth devices
- Connect to a device
- 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:
- Add CameraX dependencies
- Request camera permissions
- Set up a Preview use case
- 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:
- Declaring permissions in the manifest
- Checking if permissions are granted
- Requesting permissions if not granted
- 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:
- Adding location permissions to the manifest
- Checking and requesting permissions at runtime
- 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:
- Getting a Google Maps API key
- Adding the Maps SDK for Android to your project
- Adding a map to your app’s UI
- 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:
- Setting up Firebase in your project
- Adding FCM dependency
- Implementing a service to handle FCM messages
- Registering the device with FCM
- 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:
- Setting up Firebase in your project
- Adding Firebase Realtime Database dependency
- Writing data to the database
- 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:
- Creating a shared module for common code
- Creating platform-specific modules (androidApp, iosApp)
- Defining expected and actual declarations for platform-specific implementations
- 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
56. What are the main components of the recommended modern Android app architecture?
The main components of modern Android app architecture are:
- UI Layer
- Domain Layer
- 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:
- Repositories: Act as single sources of truth for data, managing data operations and coordinating between multiple data sources
- Data Sources: Handle data operations for a specific source (e.g., network, local database)
- 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:
- Add Hilt to your project
- Annotate your Application class with @HiltAndroidApp
- Annotate your activities, fragments, and ViewModels with @AndroidEntryPoint
- 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:
- Repository in the data layer exposes data as a Flow
- Use case in the domain layer may transform this data
- ViewModel in the UI layer collects the Flow and exposes it to the UI
- 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:
- Defining error states or models
- Propagating errors through the layers
- 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:
- Define a Worker class
- Create and configure a WorkRequest
- 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