SKILL.md
$27
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 {
// 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, BuildConfig is no longer generated by default. You must explicitly enable it in your build.gradle.kts:
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) }
}
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:
// In ViewModel
viewModelScope.launch {
// Default Main thread, can update UI State
_uiState.value = UiState.Loading
// Switch to IO thread for network request
val result = withContext(Dispatchers.IO) {
repository.fetchData()
}
// Automatically returns to Main thread, update UI
_uiState.value = UiState.Success(result)
}
// In Repository (suspend functions should be main-safe)
suspend fun fetchData(): Data = withContext(Dispatchers.IO) {
api.getData()
}
Common Mistakes:
// ❌ Wrong: Updating UI on IO thread
viewModelScope.launch(Dispatchers.IO) {
val data = api.fetch()
_uiState.value = data // Crash or warning!
}
// ❌ Wrong: Executing time-consuming operation on Main thread
viewModelScope.launch {
val data = api.fetch() // Blocking main thread! ANR
}
// ✅ Correct: Fetch on IO, update on Main
viewModelScope.launch {
val data = withContext(Dispatchers.IO) { api.fetch() }
_uiState.value = data
}
3.4 Visibility Rules
// Default is public, declare explicitly when needed
class UserRepository { // public
private val cache = mutableMapOf<String, User>() // Visible only within class
internal fun clearCache() {} // Visible only within module
}
// data class properties are public by default, be careful when used across modules
data class User(
val id: String, // public
val name: String
)
3.5 Common Syntax Pitfalls
// ❌ Wrong: Accessing uninitialized lateinit
class MyViewModel : ViewModel() {
lateinit var data: String
fun process() = data.length // May crash
}
// ✅ Correct: Use nullable or default value
class MyViewModel : ViewModel() {
var data: String? = null
fun process() = data?.length ?: 0
}
// ❌ Wrong: Using return in lambda
list.forEach { item ->
if (item.isEmpty()) return // Returns from outer function!
}
// ✅ Correct: Use return@forEach
list.forEach { item ->
if (item.isEmpty()) return@forEach
}
3.6 Server Response Data Class Fields Must Be Nullable
// ❌ Wrong: Fields declared as non-null (server may not return them)
data class UserResponse(
val id: String = "",
val name: String = "",
val avatar: String = ""
)
// ✅ Correct: All fields declared as nullable
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
// ❌ Wrong: Only adding Observer, not removing
class MyView : View {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
activity?.lifecycle?.addObserver(this)
}
// Memory leak!
}
// ✅ Correct: Paired add and remove
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
// Info: Key checkpoints in normal flow
Log.i(TAG, "loadData: started, userId = $userId")
// Warning: Abnormal but recoverable situations
Log.w(TAG, "loadData: cache miss, fallback to network")
// Error: Failure/error situations
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
e (Error)
Request failures, caught exceptions, unrecoverable errors
4. Jetpack Compose Standards
4.1 @Composable Context Rules
// ❌ Wrong: Calling Composable from non-Composable function
fun showError(message: String) {
Text(message) // Compile error!
}
// ✅ Correct: Mark as @Composable
@Composable
fun ErrorMessage(message: String) {
Text(message)
}
// ❌ Wrong: Using suspend outside LaunchedEffect
@Composable
fun MyScreen() {
val data = fetchData() // Error!
}
// ✅ Correct: Use LaunchedEffect
@Composable
fun MyScreen() {
var data by remember { mutableStateOf<Data?>(null) }
LaunchedEffect(Unit) {
data = fetchData()
}
}
4.2 State Management
// Basic State
var count by remember { mutableStateOf(0) }
// Derived State (avoid redundant computation)
val isEven by remember { derivedStateOf { count % 2 == 0 } }
// Persist across recomposition (e.g., scroll position)
val scrollState = rememberScrollState()
// State in ViewModel
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}
4.3 Common Compose Mistakes
// ❌ Wrong: Creating objects in Composable (created on every recomposition)
@Composable
fun MyScreen() {
val viewModel = MyViewModel() // Wrong!
}
// ✅ Correct: Use viewModel() or remember
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
// ...
}
5. Resources & Icons
5.1 App Icon Requirements
Must provide multi-resolution icons:
Directory
Size
Purpose
mipmap-mdpi
48x48
Baseline
mipmap-hdpi
72x72
1.5x
mipmap-xhdpi
96x96
2x
mipmap-xxhdpi
144x144
3x
mipmap-xxxhdpi
192x192
4x
Recommended: Use Adaptive Icon (Android 8+):
<!-- res/mipmap-anydpi-v26/ic_launcher.xml -->
<adaptive-icon>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
5.2 Resource Naming Conventions
Type
Prefix
Example
Layout
layout_
layout_main.xml
Image
ic_, img_, bg_
ic_user.png
Color
color_
color_primary
String
-
app_name, btn_submit
5.3 Avoid Android Reserved Names (Important)
Variable names, resource IDs, colors, icons, and XML elements must not use Android reserved words or system resource names. Using reserved names causes build errors or resource conflicts.
Common Reserved Names to Avoid:
Category
Reserved Names (Do NOT Use)
Colors
background, foreground, transparent, white, black
Icons/Drawables
icon, logo, image, drawable
Views
view, text, button, layout, container
Attributes
id, name, type, style, theme, color
System
app, android, content, data, action
Examples:
<!-- ❌ Wrong: Using reserved names -->
<color name="background">#FFFFFF</color>
<color name="icon">#000000</color>
<!-- ✅ Correct: Add prefix or specific naming -->
<color name="app_background">#FFFFFF</color>
<color name="icon_primary">#000000</color>
// ❌ Wrong: Variable names conflict with system
val icon = R.drawable.my_icon
val background = Color.White
// ✅ Correct: Use descriptive names
val appIcon = R.drawable.my_icon
val screenBackground = Color.White
<!-- ❌ Wrong: Drawable name conflicts -->
<ImageView android:src="@drawable/icon" />
<!-- ✅ Correct: Add prefix -->
<ImageView android:src="@drawable/ic_home" />
6. Build Error Diagnosis & Fixes
6.1 Common Error Quick Reference
Error Keyword
Cause
Fix
Unresolved reference
Missing import or undefined
Check imports, verify dependencies
Type mismatch
Type incompatibility
Check parameter types, add conversion
Cannot access
Visibility issue
Check public/private/internal
@Composable invocations
Composable context error
Ensure caller is also @Composable
Duplicate class
Dependency conflict
Use ./gradlew dependencies to investigate
AAPT: error
Resource file error
Check XML syntax and resource references
6.2 Fix Best Practices
- Read the complete error message first: Locate file and line number
- Check recent changes: Problems usually in latest modifications
- Clean Build:
./gradlew clean assembleDebug
- Check dependency versions: Version conflicts are common causes
- Refresh dependencies if needed: Clear cache and rebuild
6.3 Debugging Commands
# Clean and build
./gradlew clean assembleDebug
# View dependency tree (investigate conflicts)
./gradlew :app:dependencies
# View detailed errors
./gradlew assembleDebug --stacktrace
# Refresh dependencies
./gradlew --refresh-dependencies
7. Material Design 3 Guidelines
Review Android UI files for compliance with Material Design 3 Guidelines and Android best practices.
Design Philosophy
#### M3 Core Principles
Principle
Description
Personal
Dynamic color based on user preferences and wallpaper
Adaptive
Responsive across all screen sizes and form factors
Expressive
Bold colors and typography with personality
Accessible
Inclusive design for all users
#### M3 Expressive (Latest)
The latest evolution adds emotion-driven UX through:
- Vibrant, dynamic colors
- Intuitive motion physics
- Adaptive components
- Flexible typography
- Contrasting shapes (35 new shape options)
App Style Selection
Critical Decision: Match visual style to app category and target audience.
App Category
Visual Style
Key Characteristics
Utility/Tool
Minimalist
Clean, efficient, neutral colors
Finance/Banking
Professional Trust
Conservative colors, security-focused
Health/Wellness
Calm & Natural
Soft colors, organic shapes
Kids (3-5)
Playful Simple
Bright colors, large targets (56dp+)
Kids (6-12)
Fun & Engaging
Vibrant, gamified feedback
Social/Entertainment
Expressive
Brand-driven, gesture-rich
Productivity
Clean & Focused
Minimal, high contrast
E-commerce
Conversion-focused
Clear CTAs, scannable
See Design Style Guide for detailed style profiles.
Quick Reference: Key Specifications
#### Color Contrast Requirements
Element
Minimum Ratio
Body text
4.5:1
Large text (18sp+)
3:1
UI components
3:1
#### Touch Targets
Type
Size
Minimum
48 × 48dp
Recommended (primary actions)
56 × 56dp
Kids apps
56dp+
Spacing between targets
8dp minimum
#### 8dp Grid System
Token
Value
Usage
xs
4dp
Icon padding
sm
8dp
Tight spacing
md
16dp
Default padding
lg
24dp
Section spacing
xl
32dp
Large gaps
xxl
48dp
Screen margins
#### Typography Scale (Summary)
Category
Sizes
Display
57sp, 45sp, 36sp
Headline
32sp, 28sp, 24sp
Title
22sp, 16sp, 14sp
Body
16sp, 14sp, 12sp
Label
14sp, 12sp, 11sp
#### Animation Duration
Type
Duration
Micro (ripples)
50-100ms
Short (simple)
100-200ms
Medium (expand/collapse)
200-300ms
Long (complex)
300-500ms
#### Component Dimensions
Component
Height
Min Width
Button
40dp
64dp
FAB
56dp
56dp
Text Field
56dp
280dp
App Bar
64dp
-
Bottom Nav
80dp
-
Anti-Patterns (Must Avoid)
#### UI Anti-Patterns
- More than 5 bottom navigation items
- Multiple FABs on same screen
- Touch targets smaller than 48dp
- Inconsistent spacing (non-8dp multiples)
- Missing dark theme support
- Text on colored backgrounds without contrast check
#### Performance Anti-Patterns
- Startup time > 2 seconds without progress indicator
- Frame rate < 60 FPS (> 16ms per frame)
- Crash rate > 1.09% (Google Play threshold)
- ANR rate > 0.47% (Google Play threshold)
#### Accessibility Anti-Patterns
- Missing contentDescription on interactive elements
- Element type in labels (e.g., "Save button" instead of "Save")
- Complex gestures in kids apps
- Text-only buttons for non-readers
Review Checklist
- 8dp spacing grid compliance
- 48dp minimum touch targets
- Proper typography scale usage
- Color contrast compliance (4.5:1+ for text)
- Dark theme support
- contentDescription on all interactive elements
- Startup < 2 seconds or shows progress
- Visual style matches app category
Design References
Topic
Reference
Colors, Typography, Spacing, Shapes
Animation & Transitions
Accessibility Guidelines
Large Screens & Foldables
Android Vitals & Performance
Performance &#x26; Stability
Privacy & Security
Audio, Video, Notifications
App Style by Category
8. Testing
Note: Only add test dependencies when the user explicitly asks for testing.
A well-tested Android app uses layered testing: fast local unit tests for logic, instrumentation tests for UI and integration, and Gradle Managed Devices to run emulators reproducibly on any machine — including CI.
8.1 Test Dependencies
Before adding test dependencies, inspect the project's existing versions to avoid conflicts:
- Check
gradle/libs.versions.toml— if present, add test deps using the project's version catalog style
- Check existing
build.gradle.ktsfor already-pinned dependency versions
- Match version families using the table below
Version Alignment Rules:
Test Dependency
Must Align With
How to Check
kotlinx-coroutines-test
Project's kotlinx-coroutines-core version
Search for kotlinx-coroutines in build files or version catalog
compose-ui-test-junit4
Project's Compose BOM or compose-compiler
Search for compose-bom or compose.compiler in build files
espresso-*
All Espresso artifacts must use the same version
Search for espresso in build files
androidx.test:runner, rules, ext:junit
Should use compatible AndroidX Test versions
Search for androidx.test in build files
mockk
Must support the project's Kotlin version
Check kotlin version in root build.gradle.kts or version catalog
Dependencies Reference — add only the groups you need:
dependencies {
// --- Local unit tests (src/test/) ---
testImplementation("junit:junit:<version>") // 4.13.2+
testImplementation("org.robolectric:robolectric:<version>") // 4.16.1+
testImplementation("io.mockk:mockk:<version>") // match Kotlin version
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:<version>") // match coroutines-core
testImplementation("androidx.arch.core:core-testing:<version>") // InstantTaskExecutorRule for LiveData
testImplementation("app.cash.turbine:turbine:<version>") // Flow/StateFlow testing
// --- Instrumentation tests (src/androidTest/) ---
androidTestImplementation("androidx.test.ext:junit:<version>")
androidTestImplementation("androidx.test:runner:<version>")
androidTestImplementation("androidx.test:rules:<version>")
androidTestImplementation("androidx.test.espresso:espresso-core:<version>")
androidTestImplementation("androidx.test.espresso:espresso-contrib:<version>") // RecyclerView, Drawer
androidTestImplementation("androidx.test.espresso:espresso-intents:<version>") // Intent verification
androidTestImplementation("androidx.test.espresso:espresso-idling-resource:<version>")
androidTestImplementation("androidx.test.uiautomator:uiautomator:<version>")
// --- Compose UI tests (only if project uses Compose) ---
androidTestImplementation("androidx.compose.ui:ui-test-junit4") // version from Compose BOM
debugImplementation("androidx.compose.ui:ui-test-manifest") // required for createComposeRule
}
Note: If the project uses a Compose BOM, ui-test-junit4 and ui-test-manifest don't need explicit versions — the BOM manages them.
Enable Robolectric resource support in the android block:
android {
testOptions {
unitTests.isIncludeAndroidResources = true // required for Robolectric
}
}
8.2 Testing by Layer
Layer
Location
Runs On
Speed
Use For
Unit (JUnit)
src/test/
JVM
~ms
ViewModels, repos, mappers, validators
Unit + Robolectric
src/test/
JVM + simulated Android
~100ms
Code needing Context, resources, SharedPrefs
Compose UI (local)
src/test/
JVM + Robolectric
~100ms
Composable rendering & interaction
Espresso
src/androidTest/
Device/Emulator
~seconds
View-based UI flows, Intents, DB integration
Compose UI (device)
src/androidTest/
Device/Emulator
~seconds
Full Compose UI flows with real rendering
UI Automator
src/androidTest/
Device/Emulator
~seconds
System dialogs, notifications, multi-app
Managed Device
src/androidTest/
Gradle-managed AVD
~minutes (first run)
CI, matrix testing across API levels
See Testing for detailed examples, code patterns, and Gradle Managed Device configuration.
8.3 Testing Commands
# Local unit tests (fast, no emulator)
./gradlew test # all modules
./gradlew :app:testDebugUnitTest # app module, debug variant
# Single test class
./gradlew :app:testDebugUnitTest --tests "com.example.myapp.CounterViewModelTest"
# Instrumentation tests (requires device or managed device)
./gradlew connectedDebugAndroidTest # on connected device
./gradlew pixel6Api34DebugAndroidTest # on managed device
# Both together
./gradlew test connectedDebugAndroidTest
# Test with coverage report (JaCoCo)
./gradlew testDebugUnitTest jacocoTestReport