Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

anugotta/AndroidNotes

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

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

AltStyle によって変換されたページ (->オリジナル) /