- Core Android Concepts
- Kotlin Fundamentals
- Modern Android Development
- Architecture Components
- Background Processing
- Data Management
- Performance & Optimization
- Testing
- Build & Deployment
- Data Structures & Algorithms
-
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
Complete Flow:
onAttach → onCreate → onCreateView → onViewCreated →
onActivityCreated → onStart → onResume → onPause →
onStop → onDestroyView → onDestroy → onDetach
Key Methods:
onAttach()
: Fragment attached to activityonCreateView()
: Create and return view hierarchyonViewCreated()
: View setup after creationonDestroyView()
: View hierarchy being destroyed
- 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
}
}
- Background Service
class DataSyncService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Perform background operation
return START_NOT_STICKY
}
}
- 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
}
- IntentService (Deprecated, use WorkManager instead)
class DataProcessingService : IntentService("DataProcessingService") {
override fun onHandleIntent(intent: Intent?) {
// Process data in background
}
}
- Explicit Intents
// Start specific activity
val intent = Intent(context, TargetActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)
- 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"))
- Static Receiver (Manifest-declared)
<receiver
android:name=".MyReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
- 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)
}
// 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
// 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
}
@Composable
fun Greeting(name: String) {
Column {
Text(text = "Hello $name!")
Button(onClick = { /* action */ }) {
Text("Click me")
}
}
}
// 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")
}
}
@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
}
}
}
<?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>
// Using Safe Args
class HomeFragment : Fragment() {
private val navArgs: HomeFragmentArgs by navArgs()
fun navigate() {
val action = HomeFragmentDirections.toDetail(itemId = "123")
findNavController().navigate(action)
}
}
// 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
}
}
}
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()
}
// 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
}
// 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()
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()
}
}
}
// 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
)
}
}
// 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())
}
}
// 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()
// 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()
@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)
}
@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")))
}
@Test
fun testCounter() {
composeTestRule.setContent {
Counter()
}
composeTestRule
.onNodeWithText("Count: 0")
.assertExists()
.performClick()
composeTestRule
.onNodeWithText("Count: 1")
.assertExists()
}
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
}
}
}
// 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
}
}
// 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 */ }
}
}
# 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
// 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()
}
// 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
}
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
}
// 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>
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)
}
}
// 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>()
}
// 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()
}
// 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)
}
- Keep up with Android Developers Blog
- Follow Android Dev Summit
- Practice with Android Code Labs
- Read official Material Design Guidelines
- Join Android development communities on Reddit and Stack Overflow