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 assembleDebugsucceeds - If
gradle.propertiesis 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 encounterOutOfMemoryErrorduring build, increase-Xmxvalue. Large projects with many dependencies may require 8GB or more.
2.2 Dependency Declaration Standards
dependencies {
// Use BOM to manage Compose versions
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
// Activity & ViewModel
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 {
// Define flavor dimensions
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
// Different config values per flavor
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
// Different resources
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"
// No suffix for production
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:
# List all available build variants
./gradlew tasks --group="build"
# Build specific variant (flavor + buildType)
./gradlew assembleDevDebug # Dev flavor, Debug build
./gradlew assembleStagingDebug # Staging flavor, Debug build
./gradlew assembleProdRelease # Prod flavor, Release build
# Build all variants of a specific flavor
./gradlew assembleDev # All Dev variants (debug + release)
./gradlew assembleProd # All Prod variants
# Build all variants of a specific build type
./gradlew assembleDebug # All flavors, Debug build
./gradlew assembleRelease # All flavors, Release build
# Install specific variant to device
./gradlew installDevDebug
./gradlew installProdRelease
# Build and install in one command
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity
Access BuildConfig in Code:
Note: Starting from AGP 8.0,BuildConfigis no longer generated by default. You must explicitly enable it in yourbuild.gradle.kts:
```kotlin
android {
buildFeatures {
buildConfig = true
}
}
```
// Use build config values in your code
val apiUrl = BuildConfig.API_BASE_URL
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING
if (BuildConfig.DEBUG) {
// Debug-only code
}
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" }
}
}
// Results in: devFreeDebug, devPaidDebug, prodFreeRelease, etc.
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:
// ❌ Avoid: Non-null assertion !! (may crash)
val name = user!!.name
// ✅ Recommended: Safe call + default value
val name = user?.name ?: "Unknown"
// ✅ Recommended: let handling
user?.let { processUser(it) }
Exception Handling:
// ❌ Avoid: Random try-catch in business layer swallowing exceptions
fun loadData() {
try {
val data = api.fetch()
} catch (e: Exception) {
// Swallowing exception, hard to debug
}
}
// ✅ Recommended: Let exceptions propagate, handle at appropriate layer
suspend fun loadData(): Result<Data> {
return try {
Result.success(api.fetch())
} catch (e: Exception) {
Result.failure(e) // Wrap and return, let caller decide handling
}
}
// ✅ Recommended: Unified handling in ViewModel
viewModelScope.launch {
runCatching { repository.loadData() }
.onSuccess { _uiState.value = UiState.Success(it) }
.onFailure { _uiState.value = UiState.Error(it.message) }
}