Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 

Repository files navigation

Android Developer Interview Guide

Table of Contents

Core Android Concepts

Activities & Fragments

Activity Lifecycle

  • onCreate(): Activity is first created

    • Initialize UI
    • Set content view
    • Initialize variables
  • onStart(): Activity becomes visible

    • Prepare UI elements
    • Register broadcast receivers
  • onResume(): Activity starts interacting with user

    • Start animations/video playback
    • Initialize foreground services
  • onPause(): Activity partially visible but not focused

    • Pause ongoing operations
    • Save draft data
  • onStop(): Activity no longer visible

    • Save persistent data
    • Release resources
  • onDestroy(): Activity being destroyed

    • Cleanup resources
    • Unregister receivers

Common Flows:

Normal Flow:
onCreate → onStart → onResume → onPause → onStop → onDestroy

Background Flow:
onStop → onRestart → onStart

Configuration Change:
onPause → onSaveInstanceState → onStop → onDestroy → 
onCreate → onStart → onRestoreInstanceState → onResume

Fragment Lifecycle

Complete Flow:
onAttach → onCreate → onCreateView → onViewCreated → 
onActivityCreated → onStart → onResume → onPause → 
onStop → onDestroyView → onDestroy → onDetach

Key Methods:

  • onAttach(): Fragment attached to activity
  • onCreateView(): Create and return view hierarchy
  • onViewCreated(): View setup after creation
  • onDestroyView(): View hierarchy being destroyed

Services

Types of Services

  1. Foreground Service
class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = createNotification()
        startForeground(NOTIFICATION_ID, notification)
        return START_STICKY
    }
}
  1. Background Service
class DataSyncService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Perform background operation
        return START_NOT_STICKY
    }
}
  1. Bound Service
class LocalService : Service() {
    private val binder = LocalBinder()
    
    inner class LocalBinder : Binder() {
        fun getService(): LocalService = this@LocalService
    }
    
    override fun onBind(intent: Intent): IBinder = binder
}
  1. IntentService (Deprecated, use WorkManager instead)
class DataProcessingService : IntentService("DataProcessingService") {
    override fun onHandleIntent(intent: Intent?) {
        // Process data in background
    }
}

Intents & Communication

Types of Intents

  1. Explicit Intents
// Start specific activity
val intent = Intent(context, TargetActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)
  1. Implicit Intents
// Open URL
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(intent)

// Share text
val shareIntent = Intent().apply {
    action = Intent.ACTION_SEND
    type = "text/plain"
    putExtra(Intent.EXTRA_TEXT, "Share this")
}
startActivity(Intent.createChooser(shareIntent, "Share via"))

Broadcast Receivers

  1. Static Receiver (Manifest-declared)
<receiver
    android:name=".MyReceiver"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>
  1. Dynamic Receiver (Runtime registration)
private val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // Handle broadcast
    }
}

override fun onResume() {
    super.onResume()
    registerReceiver(receiver, IntentFilter("ACTION_NAME"))
}

override fun onPause() {
    super.onPause()
    unregisterReceiver(receiver)
}

Kotlin Fundamentals

Key Features

Null Safety

// Nullable types
var nullableString: String? = null

// Safe call operator
nullableString?.length

// Elvis operator
val length = nullableString?.length ?: 0

// Not-null assertion
val definiteLength = nullableString!!.length // Throws if null

Properties

// Lazy initialization
val expensive: String by lazy {
    // Computed only on first access
    computeExpensiveString()
}

// Late initialization
lateinit var lateinitVar: String
// Initialize later
lateinitVar = "Now initialized"

// Custom getters/setters
var counter = 0
    get() = field
    set(value) {
        if (value >= 0) field = value
    }

Modern Android Development

Jetpack Compose

Basic Composables

@Composable
fun Greeting(name: String) {
    Column {
        Text(text = "Hello $name!")
        Button(onClick = { /* action */ }) {
            Text("Click me")
        }
    }
}

State Management

// Local state
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

// Hoisted state
@Composable
fun StatefulCounter(
    count: Int,
    onCountChange: (Int) -> Unit
) {
    Button(onClick = { onCountChange(count + 1) }) {
        Text("Count: $count")
    }
}

Side Effects

@Composable
fun MyScreen() {
    // Run on first composition
    LaunchedEffect(Unit) {
        // Launch coroutine
    }

    // Run on every successful composition
    SideEffect {
        // Update non-compose code
    }

    // Cleanup when leaving composition
    DisposableEffect(Unit) {
        onDispose {
            // Cleanup
        }
    }
}

Navigation Component

Navigation Graph

<?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=".HomeFragment">
        <action
            android:id="@+id/to_detail"
            app:destination="@id/detailFragment">
            <argument
                android:name="itemId"
                app:argType="string" />
        </action>
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name=".DetailFragment" />
</navigation>

Navigation in Code

// Using Safe Args
class HomeFragment : Fragment() {
    private val navArgs: HomeFragmentArgs by navArgs()
    
    fun navigate() {
        val action = HomeFragmentDirections.toDetail(itemId = "123")
        findNavController().navigate(action)
    }
}

Architecture Components

MVVM Pattern

// Model
data class User(val id: String, val name: String)

// ViewModel
class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user

    fun loadUser(id: String) {
        viewModelScope.launch {
            _user.value = repository.getUser(id)
        }
    }
}

// View
class UserFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.user.observe(viewLifecycleOwner) { user ->
            // Update UI
        }
    }
}

State Management

StateFlow

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Initial)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val result = repository.getData()
                _uiState.value = UiState.Success(result)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }
}

// States
sealed class UiState {
    object Initial : UiState()
    object Loading : UiState()
    data class Success(val data: Data) : UiState()
    data class Error(val message: String?) : UiState()
}

Room Database

// Entity
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: String,
    val name: String,
    val email: String
)

// DAO
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAll(): Flow<List<UserEntity>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(user: UserEntity)

    @Delete
    suspend fun delete(user: UserEntity)
}

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

Dependency Injection with Hilt

// Application class
@HiltAndroidApp
class MyApplication : Application()

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

// ViewModel injection
@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: Repository
) : ViewModel()

// Activity injection
@AndroidEntryPoint
class MainActivity : AppCompatActivity()

Background Processing

Coroutines

class MainViewModel : ViewModel() {
    // Different Coroutine Scopes
    init {
        // ViewModel Scope
        viewModelScope.launch {
            // Runs on Main dispatcher by default
        }

        // Background work with IO dispatcher
        viewModelScope.launch(Dispatchers.IO) {
            // Network or database operations
        }

        // CPU-intensive work
        viewModelScope.launch(Dispatchers.Default) {
            // Complex calculations
        }
    }

    // Parallel Execution
    suspend fun loadDataInParallel() {
        coroutineScope {
            val result1 = async { api.getData1() }
            val result2 = async { api.getData2() }
            val combinedResult = result1.await() + result2.await()
        }
    }
}

WorkManager

// Define Work
class DataSyncWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {
    
    override suspend fun doWork(): Result {
        return try {
            // Do background work
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

// Schedule Work
class Repository {
    fun scheduleSync() {
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()

        val syncWork = OneTimeWorkRequestBuilder<DataSyncWorker>()
            .setConstraints(constraints)
            .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
            .build()

        WorkManager.getInstance(context)
            .enqueueUniqueWork(
                "sync_work",
                ExistingWorkPolicy.REPLACE,
                syncWork
            )
    }
}

RxJava

// Basic Observable
Observable.just(1, 2, 3, 4, 5)
    .map { it * 2 }
    .filter { it > 5 }
    .subscribe { println(it) }

// Network Call with RxJava
interface ApiService {
    @GET("users")
    fun getUsers(): Single<List<User>>
}

class Repository {
    fun getUsers(): Single<List<User>> {
        return apiService.getUsers()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
    }
}

Data Management

Secure Data Storage

// Encrypted Shared Preferences
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

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

// Encrypted Files
val encryptedFile = EncryptedFile.Builder(
    context,
    File(context.filesDir, "secret.txt"),
    masterKey,
    EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()

Network Security

// Certificate Pinning with OkHttp
val certificatePinner = CertificatePinner.Builder()
    .add("example.com", "sha256/XXXX=")
    .build()

val okHttpClient = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

// Retrofit with Security
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

Testing

Unit Testing

@Test
fun `test user validation`() {
    val validator = UserValidator()
    
    assertTrue(validator.isValidEmail("test@example.com"))
    assertFalse(validator.isValidEmail("invalid-email"))
}

// ViewModel Testing
@Test
fun `test loading state`() = runTest {
    val repository = mockk<Repository>()
    coEvery { repository.getData() } returns Result.success(data)
    
    val viewModel = MainViewModel(repository)
    viewModel.loadData()
    
    assertEquals(UiState.Success(data), viewModel.uiState.value)
}

UI Testing with Espresso

@Test
fun testLoginFlow() {
    // Launch activity
    ActivityScenario.launch(LoginActivity::class.java)

    // Type text
    onView(withId(R.id.emailInput))
        .perform(typeText("test@example.com"))

    // Click button
    onView(withId(R.id.loginButton))
        .perform(click())

    // Verify text
    onView(withId(R.id.statusText))
        .check(matches(withText("Success")))
}

Compose UI Testing

@Test
fun testCounter() {
    composeTestRule.setContent {
        Counter()
    }

    composeTestRule
        .onNodeWithText("Count: 0")
        .assertExists()
        .performClick()

    composeTestRule
        .onNodeWithText("Count: 1")
        .assertExists()
}

Performance Optimization

Memory Management

class MainActivity : AppCompatActivity() {
    // Avoid memory leaks with weak references
    private val weakReference = WeakReference(this)
    
    // Proper bitmap handling
    private fun loadBitmap() {
        val options = BitmapFactory.Options().apply {
            inJustDecodeBounds = true
        }
        BitmapFactory.decodeResource(resources, R.drawable.image, options)
        
        options.apply {
            inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
            inJustDecodeBounds = false
        }
    }
}

// Memory Cache
class ImageCache {
    private val memoryCache = object : LruCache<String, Bitmap>(
        (Runtime.getRuntime().maxMemory() / 1024).toInt() / 8
    ) {
        override fun sizeOf(key: String, bitmap: Bitmap): Int {
            return bitmap.byteCount / 1024
        }
    }
}

Build & Deployment

Gradle Configuration

// App level build.gradle.kts
plugins {
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-kapt")
    id("dagger.hilt.android.plugin")
}

android {
    defaultConfig {
        applicationId = "com.example.app"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0.0"
    }

    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
        }
        
        debug {
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }
    }

    buildFeatures {
        compose = true
        viewBinding = true
    }
}

APK vs App Bundle

// App Bundle benefits:
// 1. Smaller download size
// 2. Dynamic feature delivery
// 3. Optimized for different devices

// Configure dynamic feature
android {
    dynamicFeatures = mutableSetOf(":feature_module")
}

// Install dynamic feature
class MainActivity : AppCompatActivity() {
    private fun installFeature() {
        val request = SplitInstallRequest.newBuilder()
            .addModule("feature_module")
            .build()

        splitInstallManager.startInstall(request)
            .addOnSuccessListener { /* Handle success */ }
            .addOnFailureListener { /* Handle failure */ }
    }
}

CI/CD Pipeline

# Example GitHub Actions workflow
name: Android CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up JDK
      uses: actions/setup-java@v2
      with:
        java-version: '11'
        
    - name: Run Tests
      run: ./gradlew test
      
    - name: Build Debug APK
      run: ./gradlew assembleDebug
      
    - name: Upload APK
      uses: actions/upload-artifact@v2
      with:
        name: app-debug
        path: app/build/outputs/apk/debug/app-debug.apk

Data Structures & Algorithms

Common Interview Problems

1. String Manipulation

// Reverse String
fun reverseString(str: String): String {
    return str.reversed()
    // Or manual implementation:
    return str.toCharArray()
        .apply { reverse() }
        .joinToString("")
}

// Check Palindrome
fun isPalindrome(str: String): Boolean {
    val cleanStr = str.lowercase().filter { it.isLetterOrDigit() }
    return cleanStr == cleanStr.reversed()
}

2. Array Operations

// Find Missing Number
fun findMissingNumber(arr: IntArray): Int {
    val n = arr.size + 1
    val expectedSum = (n * (n + 1)) / 2
    val actualSum = arr.sum()
    return expectedSum - actualSum
}

// Maximum Subarray Sum
fun maxSubArraySum(arr: IntArray): Int {
    var maxSoFar = arr[0]
    var maxEndingHere = arr[0]
    
    for (i in 1 until arr.size) {
        maxEndingHere = maxOf(arr[i], maxEndingHere + arr[i])
        maxSoFar = maxOf(maxSoFar, maxEndingHere)
    }
    return maxSoFar
}

3. Linked List Operations

data class ListNode(
    var value: Int,
    var next: ListNode? = null
)

// Reverse Linked List
fun reverseList(head: ListNode?): ListNode? {
    var prev: ListNode? = null
    var current = head
    
    while (current != null) {
        val next = current.next
        current.next = prev
        prev = current
        current = next
    }
    return prev
}

// Detect Cycle
fun hasCycle(head: ListNode?): Boolean {
    var slow = head
    var fast = head
    
    while (fast?.next != null) {
        slow = slow?.next
        fast = fast.next?.next
        if (slow == fast) return true
    }
    return false
}

Android-Specific Optimizations

1. View Hierarchy Optimization

// Flatten view hierarchy
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Child views -->
</merge>

// Use ConstraintLayout for complex layouts
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- Constraints reduce nesting -->
</androidx.constraintlayout.widget.ConstraintLayout>

2. RecyclerView Optimization

class OptimizedAdapter : RecyclerView.Adapter<ViewHolder>() {
    // Implement DiffUtil
    private val diffCallback = object : DiffUtil.ItemCallback<Item>() {
        override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem.id == newItem.id
        }
        
        override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
            return oldItem == newItem
        }
    }
    
    // Use ListAdapter for automatic diff
    class EfficientAdapter : ListAdapter<Item, ViewHolder>(diffCallback)
    
    // View pool for shared ViewHolders
    init {
        setHasStableIds(true)
        recyclerView.setRecycledViewPool(viewPool)
    }
}

Best Practices & Tips

Code Quality

// Use meaningful names
fun processUserData() instead of fun process()

// Single Responsibility Principle
class UserRepository {
    fun getUser() // Only user-related operations
}

// Dependency Injection over direct instantiation
class MyViewModel @Inject constructor(
    private val repository: Repository
)

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

Security Best Practices

// Secure data storage
private fun storeSecurely(data: String) {
    val encryptedData = encrypt(data)
    securePreferences.edit().putString("key", encryptedData).apply()
}

// Network security
private fun configureNetworkSecurity() {
    // Force HTTPS
    android:usesCleartextTraffic="false"
    
    // Certificate pinning
    val certificatePinner = CertificatePinner.Builder()
        .add("example.com", "sha256/XXXX=")
        .build()
}

Performance Tips

// Lazy loading
private val expensiveObject by lazy {
    // Created only when first accessed
    ExpensiveObject()
}

// Efficient image loading
private fun loadImageEfficiently() {
    Glide.with(context)
        .load(imageUrl)
        .transition(DrawableTransitionOptions.withCrossFade())
        .diskCacheStrategy(DiskCacheStrategy.ALL)
        .into(imageView)
}

Additional Resources

About

Study Notes on Android + Kotlin + Java + OOPs

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors