Skip to main content

Android Development Overview

Android Development Standards

Modern Android apps require secure local storage, biometric authentication, offline-first architecture with Room, and strict compliance with security standards. Android development with Jetpack Compose and Kotlin Coroutines enables robust, maintainable apps. Poor mobile practices lead to memory leaks, ANRs (Application Not Responding), security vulnerabilities, and poor Google Play ratings. Target Play Store rating: 4.5+, crash-free rate >99.5%.

Overview

This guide provides a comprehensive overview of Android development best practices. We cover project setup, Gradle configuration, recommended versions, project structure following Clean Architecture principles (see Architecture Overview for foundational concepts), development workflow, dependency injection with Hilt, and core architectural principles.

What This Guide Covers

  • Project Setup: Android Studio, Gradle, Kotlin configuration
  • Project Structure: Clean Architecture folder organization for Android
  • Development Workflow: From development to Google Play submission
  • Dependency Injection: Hilt for testability and maintainability
  • Code Review Checklist: Common mistakes and best practices

This overview sets the foundation. For specialized topics, see:


Core Principles

1. Kotlin First

Modern Kotlin idioms, null safety, coroutines. Avoid Java legacy patterns.

2. Jetpack Compose

Declarative UI with Material Design 3. Compose is the standard for new Android development.

3. MVVM Architecture

Clear separation between UI and business logic. ViewModels survive configuration changes.

4. Dependency Injection with Hilt

Dagger-based DI for compile-time safety and Android-optimized dependency graphs.

5. Offline-First

Room database with local caching and sync strategies. Banking apps must work offline.

6. Security

EncryptedSharedPreferences, Keystore, biometric authentication, certificate pinning.

7. Performance

Lazy loading, efficient LazyColumn, memory management, and avoiding ANRs.

8. Testability

Design for testability from the start. Use dependency injection, repository pattern, and ViewModels.


Android Development Lifecycle


Project Setup

// build.gradle (Project level)
buildscript {
ext {
kotlin_version = '1.9.20'
compose_version = '1.5.4'
hilt_version = '2.48'
}
}

plugins {
id 'com.android.application' version '8.1.4' apply false
id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
id 'com.google.dagger.hilt.android' version '2.48' apply false
}

Module build.gradle

// build.gradle (Module :app)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}

android {
namespace 'com.bank.paymentapp'
compileSdk 34

defaultConfig {
applicationId "com.bank.paymentapp"
minSdk 26 // Android 8.0 (95%+ of users)
targetSdk 34
versionCode 1
versionName "1.0.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

vectorDrawables {
useSupportLibrary true
}
}

buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

buildConfigField "String", "API_BASE_URL", '"https://api.bank.com"'
}
debug {
buildConfigField "String", "API_BASE_URL", '"https://api-dev.bank.com"'
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = '17'
freeCompilerArgs += [
'-opt-in=kotlin.RequiresOptIn',
'-Xjvm-default=all'
]
}

buildFeatures {
compose true
buildConfig true
}

composeOptions {
kotlinCompilerExtensionVersion = '1.5.4'
}

packaging {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}

dependencies {
// Core Android
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
implementation 'androidx.activity:activity-compose:1.8.1'

// Compose
implementation platform('androidx.compose:compose-bom:2023.10.01')
implementation 'androidx.compose.ui:ui'
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.navigation:navigation-compose:2.7.5'

// Hilt Dependency Injection
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
implementation 'androidx.hilt:hilt-navigation-compose:1.1.0'

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'

// Room Database
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.room:room-ktx:2.6.1'
kapt 'androidx.room:room-compiler:2.6.1'

// Retrofit for API calls
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'

// Security
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
implementation 'androidx.biometric:biometric:1.1.0'

// Testing
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'app.cash.turbine:turbine:1.0.0'

androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2023.10.01')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'

debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}
Minimum SDK Version

Apps should support minSdk 26 (Android 8.0) to reach 95%+ of users. Android 8.0 introduced background execution limits, notification channels, and improved permission handling. Lower API levels lack modern security capabilities and are increasingly unsupported by third-party libraries. Monitor Android version distribution at Android Studio Distribution Dashboard to make informed decisions about minimum supported versions.


Project Structure

Clean Architecture Directory Organization

app/
├── src/
│ ├── main/
│ │ ├── java/com/bank/paymentapp/
│ │ │ ├── data/ # Data layer
│ │ │ │ ├── local/ # Room database
│ │ │ │ │ ├── dao/
│ │ │ │ │ │ ├── PaymentDao.kt
│ │ │ │ │ │ └── AccountDao.kt
│ │ │ │ │ ├── entities/
│ │ │ │ │ │ ├── PaymentEntity.kt
│ │ │ │ │ │ └── AccountEntity.kt
│ │ │ │ │ └── PaymentDatabase.kt
│ │ │ │ ├── remote/ # Retrofit API
│ │ │ │ │ ├── api/
│ │ │ │ │ │ ├── PaymentApi.kt
│ │ │ │ │ │ └── AuthApi.kt
│ │ │ │ │ └── dto/
│ │ │ │ │ ├── PaymentDto.kt
│ │ │ │ │ └── AccountDto.kt
│ │ │ │ └── repository/ # Repository implementations
│ │ │ │ ├── PaymentRepositoryImpl.kt
│ │ │ │ └── AccountRepositoryImpl.kt
│ │ │ ├── domain/ # Domain layer (framework-independent)
│ │ │ │ ├── model/ # Domain models
│ │ │ │ │ ├── Payment.kt
│ │ │ │ │ └── Account.kt
│ │ │ │ ├── repository/ # Repository interfaces
│ │ │ │ │ ├── PaymentRepository.kt
│ │ │ │ │ └── AccountRepository.kt
│ │ │ │ └── usecase/ # Use cases
│ │ │ │ ├── GetPaymentsUseCase.kt
│ │ │ │ └── CreatePaymentUseCase.kt
│ │ │ ├── presentation/ # UI layer
│ │ │ │ ├── navigation/
│ │ │ │ │ ├── NavGraph.kt
│ │ │ │ │ └── Screen.kt
│ │ │ │ ├── screens/
│ │ │ │ │ ├── payments/
│ │ │ │ │ │ ├── PaymentListScreen.kt
│ │ │ │ │ │ ├── PaymentListViewModel.kt
│ │ │ │ │ │ ├── PaymentDetailScreen.kt
│ │ │ │ │ │ └── PaymentDetailViewModel.kt
│ │ │ │ │ ├── profile/
│ │ │ │ │ │ ├── ProfileScreen.kt
│ │ │ │ │ │ └── ProfileViewModel.kt
│ │ │ │ │ └── auth/
│ │ │ │ │ ├── LoginScreen.kt
│ │ │ │ │ └── LoginViewModel.kt
│ │ │ │ ├── components/ # Reusable UI components
│ │ │ │ │ ├── PaymentCard.kt
│ │ │ │ │ ├── LoadingIndicator.kt
│ │ │ │ │ └── ErrorView.kt
│ │ │ │ └── theme/ # Material Design theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Type.kt
│ │ │ │ └── Theme.kt
│ │ │ ├── di/ # Hilt modules
│ │ │ │ ├── AppModule.kt
│ │ │ │ ├── NetworkModule.kt
│ │ │ │ ├── DatabaseModule.kt
│ │ │ │ └── RepositoryModule.kt
│ │ │ └── PaymentApplication.kt # Application class
│ │ ├── res/ # Resources
│ │ │ ├── values/
│ │ │ ├── drawable/
│ │ │ └── mipmap/
│ │ └── AndroidManifest.xml
│ ├── test/ # Unit tests
│ │ └── java/com/bank/paymentapp/
│ └── androidTest/ # Instrumented tests
│ └── java/com/bank/paymentapp/
├── build.gradle
└── proguard-rules.pro

Architecture Layers Diagram

Dependency Flow Diagram

Dependency Rule

Domain layer has ZERO dependencies on Android framework or other layers. This is critical for testability and business logic isolation. Data and Presentation layers depend on Domain through interfaces (Dependency Inversion Principle).


Dependency Injection with Hilt

Hilt Setup

// PaymentApplication.kt
package com.bank.paymentapp

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class PaymentApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize Firebase, Crashlytics, etc.
}
}
<!-- AndroidManifest.xml -->
<application
android:name=".PaymentApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.BankingApp">
<!-- Activities -->
</application>

Hilt Modules

// di/NetworkModule.kt
package com.bank.paymentapp.di

import com.bank.paymentapp.data.remote.api.PaymentApi
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
}

@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.bank.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

@Provides
@Singleton
fun providePaymentApi(retrofit: Retrofit): PaymentApi {
return retrofit.create(PaymentApi::class.java)
}
}

Hilt Dependency Graph


Development Workflow

Feature Development Flow


Build Configuration

Build Variants


Common Mistakes

Don't: Use GlobalScope for Coroutines

// BAD: GlobalScope survives application lifecycle
GlobalScope.launch {
val payments = repository.getPayments()
}

// GOOD: Use ViewModelScope tied to ViewModel lifecycle
viewModelScope.launch {
val payments = repository.getPayments()
}

Don't: Store Secrets in SharedPreferences

// BAD: SharedPreferences is NOT encrypted
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", token).apply()

// GOOD: Use EncryptedSharedPreferences
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()

val encryptedPrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
encryptedPrefs.edit().putString("auth_token", token).apply()

Don't: Block Main Thread

// BAD: Blocking main thread causes ANR
fun loadPayments() {
val payments = runBlocking { // BLOCKS UI!
repository.getPayments()
}
}

// GOOD: Use coroutines in ViewModel
fun loadPayments() {
viewModelScope.launch {
_state.value = PaymentState.Loading
try {
val payments = repository.getPayments()
_state.value = PaymentState.Success(payments)
} catch (e: Exception) {
_state.value = PaymentState.Error(e.message)
}
}
}

Code Review Checklist

Security Red Flags (Block PR)

  • No secrets in SharedPreferences - Use EncryptedSharedPreferences
  • No hardcoded API keys - Use BuildConfig or secure storage
  • Certificate pinning implemented - For production API calls
  • No sensitive data logged - Account numbers, tokens, PINs
  • Biometric auth has fallback - Support PIN entry
  • ProGuard/R8 enabled - For release builds

Watch For (Request Changes)

  • No GlobalScope coroutines - Use viewModelScope or lifecycleScope
  • Main thread not blocked - Network/DB operations in coroutines
  • Memory leaks prevented - No activity/context leaks in ViewModels
  • Null safety - Proper null checks or safe calls
  • Large composables (300+ lines) - Break into smaller functions
  • Missing error handling - All coroutines have try/catch
  • Deprecated APIs - Check for Android deprecation warnings

Best Practices (Approve if Present)

  • Dependency injection used - Hilt for all dependencies
  • Repository pattern - Abstracts data sources
  • Unit tests included - ViewModels and use cases tested
  • String resources - No hardcoded user-facing text
  • Compose previews - All composables have @Preview
  • Proper coroutine usage - Structured concurrency
  • State hoisting - Stateless composables where possible

Additional Checks

  • BigDecimal for currency - Never Float/Double for monetary values (precision loss)
  • Audit logging - Critical user actions logged with user ID, timestamp, action type
  • Offline support - Core features work without network via Room caching
  • Input validation - All user inputs validated (format, range, type)
  • Accessibility - Content descriptions for all interactive elements

Performance Considerations

App Launch Time

Target: Cold start < 2 seconds, warm start < 0.5 seconds


Further Reading

Android Framework Guidelines

Language and Cross-Platform Guidelines

External Resources


Summary

Key Takeaways

  1. Kotlin-first development - Modern Kotlin patterns, coroutines
  2. Jetpack Compose - Declarative UI with Material Design 3
  3. Clean Architecture - Separation of concerns with clear dependency flow
  4. Hilt dependency injection - Compile-time safe DI with Android optimization
  5. MVVM pattern - ViewModels survive configuration changes
  6. Offline-first - Room database for local storage
  7. Security - EncryptedSharedPreferences, Keystore, biometrics
  8. Performance - Avoid ANRs, efficient coroutines, lazy loading
  9. Testing - Unit tests, instrumented tests, Compose UI tests
  10. Code review standards - Security, memory management, thread safety

Next Steps: