| name | android-native-dev |
| description | Android native application development and UI design guide. Covers Material Design 3, Kotlin/Compose development, project configuration, accessibility, and build troubleshooting. Read this before Android native application development. |
| license | MIT |
| metadata | {"version":"1.0.0","category":"mobile","sources":["Material Design 3 Guidelines (material.io)","Android Developer Documentation (developer.android.com)","Google Play Quality Guidelines","WCAG Accessibility Guidelines"]} |
1. Project Scenario Assessment
Before starting development, assess the current project state:
| Scenario | Characteristics | Approach |
|---|
| Empty Directory | No files present | Full initialization required, including Gradle Wrapper |
| Has Gradle Wrapper | gradlew and gradle/wrapper/ exist | Use ./gradlew directly for builds |
| Android Studio Project | Complete project structure, may lack wrapper | Check wrapper, run gradle wrapper if needed |
| Incomplete Project | Partial files present | Check missing files, complete configuration |
Key Principles:
- Before writing business logic, ensure
./gradlew assembleDebug succeeds
- If
gradle.properties is missing, create it first and configure AndroidX
1.1 Required Files Checklist
MyApp/
โโโ gradle.properties # Configure AndroidX and other settings
โโโ settings.gradle.kts
โโโ build.gradle.kts # Root level
โโโ gradle/wrapper/
โ โโโ gradle-wrapper.properties
โโโ app/
โ โโโ build.gradle.kts # Module level
โ โโโ src/main/
โ โโโ AndroidManifest.xml
โ โโโ java/com/example/myapp/
โ โ โโโ MainActivity.kt
โ โโโ res/
โ โโโ values/
โ โ โโโ strings.xml
โ โ โโโ colors.xml
โ โ โโโ themes.xml
โ โโโ mipmap-*/ # App icons
2. Project Configuration
2.1 gradle.properties
# Required configuration
android.useAndroidX=true
android.enableJetifier=true
# Build optimization
org.gradle.parallel=true
kotlin.code.style=official
# JVM memory settings (adjust based on project size)
# Small projects: 2048m, Medium: 4096m, Large: 8192m+
# org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
Note: If you encounter OutOfMemoryError during build, increase -Xmx value. Large projects with many dependencies may require 8GB or more.
2.2 Dependency Declaration Standards
dependencies {
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
}
2.3 Build Variants & Product Flavors
Product Flavors allow you to create different versions of your app (e.g., free/paid, dev/staging/prod).
Configuration in app/build.gradle.kts:
android {
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
resValue("string", "app_name", "MyApp Dev")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
resValue("string", "app_name", "MyApp Staging")
}
create("prod") {
dimension = "environment"
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "false")
resValue("string", "app_name", "MyApp")
}
}
buildTypes {
debug {
isDebuggable = true
isMinifyEnabled = false
}
release {
isDebuggable = false
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}
Build Variant Naming: {flavor}{BuildType} โ e.g., devDebug, prodRelease
Gradle Build Commands:
./gradlew tasks --group="build"
./gradlew assembleDevDebug
./gradlew assembleStagingDebug
./gradlew assembleProdRelease
./gradlew assembleDev
./gradlew assembleProd
./gradlew assembleDebug
./gradlew assembleRelease
./gradlew installDevDebug
./gradlew installProdRelease
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity
Access BuildConfig in Code:
Note: Starting from AGP 8.0, BuildConfig is no longer generated by default. You must explicitly enable it in your build.gradle.kts:
android {
buildFeatures {
buildConfig = true
}
}
val apiUrl = BuildConfig.API_BASE_URL
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING
if (BuildConfig.DEBUG) {
}
Flavor-Specific Source Sets:
app/src/
โโโ main/ # Shared code for all flavors
โโโ dev/ # Dev-only code and resources
โ โโโ java/
โ โโโ res/
โโโ staging/ # Staging-only code and resources
โโโ prod/ # Prod-only code and resources
โโโ debug/ # Debug build type code
โโโ release/ # Release build type code
Multiple Flavor Dimensions (e.g., environment + tier):
android {
flavorDimensions += listOf("environment", "tier")
productFlavors {
create("dev") { dimension = "environment" }
create("prod") { dimension = "environment" }
create("free") { dimension = "tier" }
create("paid") { dimension = "tier" }
}
}
3. Kotlin Development Standards
3.1 Naming Conventions
| Type | Convention | Example |
|---|
| Class/Interface | PascalCase | UserRepository, MainActivity |
| Function/Variable | camelCase | getUserName(), isLoading |
| Constant | SCREAMING_SNAKE | MAX_RETRY_COUNT |
| Package | lowercase | com.example.myapp |
| Composable | PascalCase | @Composable fun UserCard() |
3.2 Code Standards (Important)
Null Safety:
val name = user!!.name
val name = user?.name ?: "Unknown"
user?.let { processUser(it) }
Exception Handling:
fun loadData() {
try {
val data = api.fetch()
} catch (e: Exception) {
}
}
suspend fun loadData(): Result<Data> {
return try {
Result.success(api.fetch())
} catch (e: Exception) {
Result.failure(e)
}
}
viewModelScope.launch {
runCatching { repository.loadData() }
.onSuccess { _uiState.value = UiState.Success(it) }
.onFailure { _uiState.value = UiState.Error(it.message) }
}
3.3 Threading & Coroutines (Critical)
Thread Selection Principles:
| Operation Type | Thread | Description |
|---|
| UI Updates | Dispatchers.Main | Update View, State, LiveData |
| Network Requests | Dispatchers.IO | HTTP calls, API requests |
| File I/O | Dispatchers.IO | Local storage, database operations |
| Compute Intensive | Dispatchers.Default | JSON parsing, sorting, encryption |
Correct Usage:
viewModelScope.launch {
_uiState.value = UiState.Loading
val result = withContext(Dispatchers.IO) {
repository.fetchData()
}
_uiState.value = UiState.Success(result)
}
suspend fun fetchData(): Data = withContext(Dispatchers.IO) {
api.getData()
}
Common Mistakes:
viewModelScope.launch(Dispatchers.IO) {
val data = api.fetch()
_uiState.value = data
}
viewModelScope.launch {
val data = api.fetch()
}
viewModelScope.launch {
val data = withContext(Dispatchers.IO) { api.fetch() }
_uiState.value = data
}
3.4 Visibility Rules
class UserRepository {
private val cache = mutableMapOf<String, User>()
internal fun clearCache() {}
}
data class User(
val id: String,
val name: String
)
3.5 Common Syntax Pitfalls
class MyViewModel : ViewModel() {
lateinit var data: String
fun process() = data.length
}
class MyViewModel : ViewModel() {
var data: String? = null
fun process() = data?.length ?: 0
}
list.forEach { item ->
if (item.isEmpty()) return
}
list.forEach { item ->
if (item.isEmpty()) return@forEach
}
3.6 Server Response Data Class Fields Must Be Nullable
data class UserResponse(
val id: String = "",
val name: String = "",
val avatar: String = ""
)
data class UserResponse(
@SerializedName("id")
val id: String? = null,
@SerializedName("name")
val name: String? = null,
@SerializedName("avatar")
val avatar: String? = null
)
3.7 Lifecycle Resource Management
class MyView : View {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
activity?.lifecycle?.addObserver(this)
}
}
class MyView : View {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
activity?.lifecycle?.addObserver(this)
}
override fun onDetachedFromWindow() {
activity?.lifecycle?.removeObserver(this)
super.onDetachedFromWindow()
}
}
3.8 Logging Level Usage
import android.util.Log
Log.i(TAG, "loadData: started, userId = $userId")
Log.w(TAG, "loadData: cache miss, fallback to network")
Log.e(TAG, "loadData failed: ${error.message}")
| Level | Use Case |
|---|
i (Info) | Normal flow, method entry, key parameters |
w (Warning) | Recoverable exceptions, fallback handling, null returns |