Android Native Dev
/install android-native-dev
\r \r
1. Project Scenario Assessment\r
\r
Before starting development, assess the current project state:\r
\r
| Scenario | Characteristics | Approach |\r
|----------|-----------------|----------|\r
| Empty Directory | No files present | Full initialization required, including Gradle Wrapper |\r
| Has Gradle Wrapper | gradlew and gradle/wrapper/ exist | Use ./gradlew directly for builds |\r
| Android Studio Project | Complete project structure, may lack wrapper | Check wrapper, run gradle wrapper if needed |\r
| Incomplete Project | Partial files present | Check missing files, complete configuration |\r
\r
Key Principles:\r
- Before writing business logic, ensure
./gradlew assembleDebugsucceeds\r - If
gradle.propertiesis missing, create it first and configure AndroidX\r \r
1.1 Required Files Checklist\r
\r
MyApp/\r
├── gradle.properties # Configure AndroidX and other settings\r
├── settings.gradle.kts\r
├── build.gradle.kts # Root level\r
├── gradle/wrapper/\r
│ └── gradle-wrapper.properties\r
├── app/\r
│ ├── build.gradle.kts # Module level\r
│ └── src/main/\r
│ ├── AndroidManifest.xml\r
│ ├── java/com/example/myapp/\r
│ │ └── MainActivity.kt\r
│ └── res/\r
│ ├── values/\r
│ │ ├── strings.xml\r
│ │ ├── colors.xml\r
│ │ └── themes.xml\r
│ └── mipmap-*/ # App icons\r
```\r
\r
---\r
\r
## 2. Project Configuration\r
\r
### 2.1 gradle.properties\r
\r
```properties\r
# Required configuration\r
android.useAndroidX=true\r
android.enableJetifier=true\r
\r
# Build optimization\r
org.gradle.parallel=true\r
kotlin.code.style=official\r
\r
# JVM memory settings (adjust based on project size)\r
# Small projects: 2048m, Medium: 4096m, Large: 8192m+\r
# org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8\r
```\r
\r
> **Note**: If you encounter `OutOfMemoryError` during build, increase `-Xmx` value. Large projects with many dependencies may require 8GB or more.\r
\r
### 2.2 Dependency Declaration Standards\r
\r
```kotlin\r
dependencies {\r
// Use BOM to manage Compose versions\r
implementation(platform("androidx.compose:compose-bom:2024.02.00"))\r
implementation("androidx.compose.ui:ui")\r
implementation("androidx.compose.material3:material3")\r
\r
// Activity & ViewModel\r
implementation("androidx.activity:activity-compose:1.8.2")\r
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")\r
}\r
```\r
\r
### 2.3 Build Variants & Product Flavors\r
\r
Product Flavors allow you to create different versions of your app (e.g., free/paid, dev/staging/prod).\r
\r
**Configuration in app/build.gradle.kts**:\r
\r
```kotlin\r
android {\r
// Define flavor dimensions\r
flavorDimensions += "environment"\r
\r
productFlavors {\r
create("dev") {\r
dimension = "environment"\r
applicationIdSuffix = ".dev"\r
versionNameSuffix = "-dev"\r
\r
// Different config values per flavor\r
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")\r
buildConfigField("Boolean", "ENABLE_LOGGING", "true")\r
\r
// Different resources\r
resValue("string", "app_name", "MyApp Dev")\r
}\r
\r
create("staging") {\r
dimension = "environment"\r
applicationIdSuffix = ".staging"\r
versionNameSuffix = "-staging"\r
\r
buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"")\r
buildConfigField("Boolean", "ENABLE_LOGGING", "true")\r
resValue("string", "app_name", "MyApp Staging")\r
}\r
\r
create("prod") {\r
dimension = "environment"\r
// No suffix for production\r
\r
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")\r
buildConfigField("Boolean", "ENABLE_LOGGING", "false")\r
resValue("string", "app_name", "MyApp")\r
}\r
}\r
\r
buildTypes {\r
debug {\r
isDebuggable = true\r
isMinifyEnabled = false\r
}\r
release {\r
isDebuggable = false\r
isMinifyEnabled = true\r
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")\r
}\r
}\r
}\r
```\r
\r
**Build Variant Naming**: `{flavor}{BuildType}` → e.g., `devDebug`, `prodRelease`\r
\r
**Gradle Build Commands**:\r
\r
```bash\r
# List all available build variants\r
./gradlew tasks --group="build"\r
\r
# Build specific variant (flavor + buildType)\r
./gradlew assembleDevDebug # Dev flavor, Debug build\r
./gradlew assembleStagingDebug # Staging flavor, Debug build\r
./gradlew assembleProdRelease # Prod flavor, Release build\r
\r
# Build all variants of a specific flavor\r
./gradlew assembleDev # All Dev variants (debug + release)\r
./gradlew assembleProd # All Prod variants\r
\r
# Build all variants of a specific build type\r
./gradlew assembleDebug # All flavors, Debug build\r
./gradlew assembleRelease # All flavors, Release build\r
\r
# Install specific variant to device\r
./gradlew installDevDebug\r
./gradlew installProdRelease\r
\r
# Build and install in one command\r
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity\r
```\r
\r
**Access BuildConfig in Code**:\r
\r
> **Note**: Starting from AGP 8.0, `BuildConfig` is no longer generated by default. You must explicitly enable it in your `build.gradle.kts`:\r
> ```kotlin\r
> android {\r
> buildFeatures {\r
> buildConfig = true\r
> }\r
> }\r
> ```\r
\r
```kotlin\r
// Use build config values in your code\r
val apiUrl = BuildConfig.API_BASE_URL\r
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING\r
\r
if (BuildConfig.DEBUG) {\r
// Debug-only code\r
}\r
```\r
\r
**Flavor-Specific Source Sets**:\r
\r
```\r
app/src/\r
├── main/ # Shared code for all flavors\r
├── dev/ # Dev-only code and resources\r
│ ├── java/\r
│ └── res/\r
├── staging/ # Staging-only code and resources\r
├── prod/ # Prod-only code and resources\r
├── debug/ # Debug build type code\r
└── release/ # Release build type code\r
```\r
\r
**Multiple Flavor Dimensions** (e.g., environment + tier):\r
\r
```kotlin\r
android {\r
flavorDimensions += listOf("environment", "tier")\r
\r
productFlavors {\r
create("dev") { dimension = "environment" }\r
create("prod") { dimension = "environment" }\r
\r
create("free") { dimension = "tier" }\r
create("paid") { dimension = "tier" }\r
}\r
}\r
// Results in: devFreeDebug, devPaidDebug, prodFreeRelease, etc.\r
```\r
\r
---\r
\r
## 3. Kotlin Development Standards\r
\r
### 3.1 Naming Conventions\r
\r
| Type | Convention | Example |\r
|------|------------|---------|\r
| Class/Interface | PascalCase | `UserRepository`, `MainActivity` |\r
| Function/Variable | camelCase | `getUserName()`, `isLoading` |\r
| Constant | SCREAMING_SNAKE | `MAX_RETRY_COUNT` |\r
| Package | lowercase | `com.example.myapp` |\r
| Composable | PascalCase | `@Composable fun UserCard()` |\r
\r
### 3.2 Code Standards (Important)\r
\r
**Null Safety**:\r
```kotlin\r
// ❌ Avoid: Non-null assertion !! (may crash)\r
val name = user!!.name\r
\r
// ✅ Recommended: Safe call + default value\r
val name = user?.name ?: "Unknown"\r
\r
// ✅ Recommended: let handling\r
user?.let { processUser(it) }\r
```\r
\r
**Exception Handling**:\r
```kotlin\r
// ❌ Avoid: Random try-catch in business layer swallowing exceptions\r
fun loadData() {\r
try {\r
val data = api.fetch()\r
} catch (e: Exception) {\r
// Swallowing exception, hard to debug\r
}\r
}\r
\r
// ✅ Recommended: Let exceptions propagate, handle at appropriate layer\r
suspend fun loadData(): Result\x3CData> {\r
return try {\r
Result.success(api.fetch())\r
} catch (e: Exception) {\r
Result.failure(e) // Wrap and return, let caller decide handling\r
}\r
}\r
\r
// ✅ Recommended: Unified handling in ViewModel\r
viewModelScope.launch {\r
runCatching { repository.loadData() }\r
.onSuccess { _uiState.value = UiState.Success(it) }\r
.onFailure { _uiState.value = UiState.Error(it.message) }\r
}\r
```\r
\r
### 3.3 Threading & Coroutines (Critical)\r
\r
**Thread Selection Principles**:\r
\r
| Operation Type | Thread | Description |\r
|----------------|--------|-------------|\r
| UI Updates | `Dispatchers.Main` | Update View, State, LiveData |\r
| Network Requests | `Dispatchers.IO` | HTTP calls, API requests |\r
| File I/O | `Dispatchers.IO` | Local storage, database operations |\r
| Compute Intensive | `Dispatchers.Default` | JSON parsing, sorting, encryption |\r
\r
**Correct Usage**:\r
```kotlin\r
// In ViewModel\r
viewModelScope.launch {\r
// Default Main thread, can update UI State\r
_uiState.value = UiState.Loading\r
\r
// Switch to IO thread for network request\r
val result = withContext(Dispatchers.IO) {\r
repository.fetchData()\r
}\r
\r
// Automatically returns to Main thread, update UI\r
_uiState.value = UiState.Success(result)\r
}\r
\r
// In Repository (suspend functions should be main-safe)\r
suspend fun fetchData(): Data = withContext(Dispatchers.IO) {\r
api.getData()\r
}\r
```\r
\r
**Common Mistakes**:\r
```kotlin\r
// ❌ Wrong: Updating UI on IO thread\r
viewModelScope.launch(Dispatchers.IO) {\r
val data = api.fetch()\r
_uiState.value = data // Crash or warning!\r
}\r
\r
// ❌ Wrong: Executing time-consuming operation on Main thread\r
viewModelScope.launch {\r
val data = api.fetch() // Blocking main thread! ANR\r
}\r
\r
// ✅ Correct: Fetch on IO, update on Main\r
viewModelScope.launch {\r
val data = withContext(Dispatchers.IO) { api.fetch() }\r
_uiState.value = data\r
}\r
```\r
\r
### 3.4 Visibility Rules\r
\r
```kotlin\r
// Default is public, declare explicitly when needed\r
class UserRepository { // public\r
private val cache = mutableMapOf\x3CString, User>() // Visible only within class\r
internal fun clearCache() {} // Visible only within module\r
}\r
\r
// data class properties are public by default, be careful when used across modules\r
data class User(\r
val id: String, // public\r
val name: String\r
)\r
```\r
\r
### 3.5 Common Syntax Pitfalls\r
\r
```kotlin\r
// ❌ Wrong: Accessing uninitialized lateinit\r
class MyViewModel : ViewModel() {\r
lateinit var data: String\r
fun process() = data.length // May crash\r
}\r
\r
// ✅ Correct: Use nullable or default value\r
class MyViewModel : ViewModel() {\r
var data: String? = null\r
fun process() = data?.length ?: 0\r
}\r
\r
// ❌ Wrong: Using return in lambda\r
list.forEach { item ->\r
if (item.isEmpty()) return // Returns from outer function!\r
}\r
\r
// ✅ Correct: Use return@forEach\r
list.forEach { item ->\r
if (item.isEmpty()) return@forEach\r
}\r
```\r
\r
### 3.6 Server Response Data Class Fields Must Be Nullable\r
\r
```kotlin\r
// ❌ Wrong: Fields declared as non-null (server may not return them)\r
data class UserResponse(\r
val id: String = "",\r
val name: String = "",\r
val avatar: String = ""\r
)\r
\r
// ✅ Correct: All fields declared as nullable\r
data class UserResponse(\r
@SerializedName("id")\r
val id: String? = null,\r
@SerializedName("name")\r
val name: String? = null,\r
@SerializedName("avatar")\r
val avatar: String? = null\r
)\r
```\r
\r
### 3.7 Lifecycle Resource Management\r
\r
```kotlin\r
// ❌ Wrong: Only adding Observer, not removing\r
class MyView : View {\r
override fun onAttachedToWindow() {\r
super.onAttachedToWindow()\r
activity?.lifecycle?.addObserver(this)\r
}\r
// Memory leak!\r
}\r
\r
// ✅ Correct: Paired add and remove\r
class MyView : View {\r
override fun onAttachedToWindow() {\r
super.onAttachedToWindow()\r
activity?.lifecycle?.addObserver(this)\r
}\r
\r
override fun onDetachedFromWindow() {\r
activity?.lifecycle?.removeObserver(this)\r
super.onDetachedFromWindow()\r
}\r
}\r
```\r
\r
### 3.8 Logging Level Usage\r
\r
```kotlin\r
import android.util.Log\r
\r
// Info: Key checkpoints in normal flow\r
Log.i(TAG, "loadData: started, userId = $userId")\r
\r
// Warning: Abnormal but recoverable situations\r
Log.w(TAG, "loadData: cache miss, fallback to network")\r
\r
// Error: Failure/error situations\r
Log.e(TAG, "loadData failed: ${error.message}")\r
```\r
\r
| Level | Use Case |\r
|-------|----------|\r
| `i` (Info) | Normal flow, method entry, key parameters |\r
| `w` (Warning) | Recoverable exceptions, fallback handling, null returns |\r
| `e` (Error) | Request failures, caught exceptions, unrecoverable errors |\r
\r
---\r
\r
## 4. Jetpack Compose Standards\r
\r
### 4.1 @Composable Context Rules\r
\r
```kotlin\r
// ❌ Wrong: Calling Composable from non-Composable function\r
fun showError(message: String) {\r
Text(message) // Compile error!\r
}\r
\r
// ✅ Correct: Mark as @Composable\r
@Composable\r
fun ErrorMessage(message: String) {\r
Text(message)\r
}\r
\r
// ❌ Wrong: Using suspend outside LaunchedEffect\r
@Composable\r
fun MyScreen() {\r
val data = fetchData() // Error!\r
}\r
\r
// ✅ Correct: Use LaunchedEffect\r
@Composable\r
fun MyScreen() {\r
var data by remember { mutableStateOf\x3CData?>(null) }\r
LaunchedEffect(Unit) {\r
data = fetchData()\r
}\r
}\r
```\r
\r
### 4.2 State Management\r
\r
```kotlin\r
// Basic State\r
var count by remember { mutableStateOf(0) }\r
\r
// Derived State (avoid redundant computation)\r
val isEven by remember { derivedStateOf { count % 2 == 0 } }\r
\r
// Persist across recomposition (e.g., scroll position)\r
val scrollState = rememberScrollState()\r
\r
// State in ViewModel\r
class MyViewModel : ViewModel() {\r
private val _uiState = MutableStateFlow(UiState())\r
val uiState: StateFlow\x3CUiState> = _uiState.asStateFlow()\r
}\r
```\r
\r
### 4.3 Common Compose Mistakes\r
\r
```kotlin\r
// ❌ Wrong: Creating objects in Composable (created on every recomposition)\r
@Composable\r
fun MyScreen() {\r
val viewModel = MyViewModel() // Wrong!\r
}\r
\r
// ✅ Correct: Use viewModel() or remember\r
@Composable\r
fun MyScreen(viewModel: MyViewModel = viewModel()) {\r
// ...\r
}\r
```\r
\r
---\r
\r
## 5. Resources & Icons\r
\r
### 5.1 App Icon Requirements\r
\r
Must provide multi-resolution icons:\r
\r
| Directory | Size | Purpose |\r
|-----------|------|---------|\r
| mipmap-mdpi | 48x48 | Baseline |\r
| mipmap-hdpi | 72x72 | 1.5x |\r
| mipmap-xhdpi | 96x96 | 2x |\r
| mipmap-xxhdpi | 144x144 | 3x |\r
| mipmap-xxxhdpi | 192x192 | 4x |\r
\r
Recommended: Use Adaptive Icon (Android 8+):\r
\r
```xml\r
\x3C!-- res/mipmap-anydpi-v26/ic_launcher.xml -->\r
\x3Cadaptive-icon>\r
\x3Cbackground android:drawable="@color/ic_launcher_background"/>\r
\x3Cforeground android:drawable="@mipmap/ic_launcher_foreground"/>\r
\x3C/adaptive-icon>\r
```\r
\r
### 5.2 Resource Naming Conventions\r
\r
| Type | Prefix | Example |\r
|------|--------|---------|\r
| Layout | layout_ | `layout_main.xml` |\r
| Image | ic_, img_, bg_ | `ic_user.png` |\r
| Color | color_ | `color_primary` |\r
| String | - | `app_name`, `btn_submit` |\r
\r
### 5.3 Avoid Android Reserved Names (Important)\r
\r
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.\r
\r
**Common Reserved Names to Avoid**:\r
\r
| Category | Reserved Names (Do NOT Use) |\r
|----------|----------------------------|\r
| Colors | `background`, `foreground`, `transparent`, `white`, `black` |\r
| Icons/Drawables | `icon`, `logo`, `image`, `drawable` |\r
| Views | `view`, `text`, `button`, `layout`, `container` |\r
| Attributes | `id`, `name`, `type`, `style`, `theme`, `color` |\r
| System | `app`, `android`, `content`, `data`, `action` |\r
\r
**Examples**:\r
\r
```xml\r
\x3C!-- ❌ Wrong: Using reserved names -->\r
\x3Ccolor name="background">#FFFFFF\x3C/color>\r
\x3Ccolor name="icon">#000000\x3C/color>\r
\r
\x3C!-- ✅ Correct: Add prefix or specific naming -->\r
\x3Ccolor name="app_background">#FFFFFF\x3C/color>\r
\x3Ccolor name="icon_primary">#000000\x3C/color>\r
```\r
\r
```kotlin\r
// ❌ Wrong: Variable names conflict with system\r
val icon = R.drawable.my_icon\r
val background = Color.White\r
\r
// ✅ Correct: Use descriptive names\r
val appIcon = R.drawable.my_icon\r
val screenBackground = Color.White\r
```\r
\r
```xml\r
\x3C!-- ❌ Wrong: Drawable name conflicts -->\r
\x3CImageView android:src="@drawable/icon" />\r
\r
\x3C!-- ✅ Correct: Add prefix -->\r
\x3CImageView android:src="@drawable/ic_home" />\r
```\r
\r
---\r
\r
## 6. Build Error Diagnosis & Fixes\r
\r
### 6.1 Common Error Quick Reference\r
\r
| Error Keyword | Cause | Fix |\r
|---------------|-------|-----|\r
| `Unresolved reference` | Missing import or undefined | Check imports, verify dependencies |\r
| `Type mismatch` | Type incompatibility | Check parameter types, add conversion |\r
| `Cannot access` | Visibility issue | Check public/private/internal |\r
| `@Composable invocations` | Composable context error | Ensure caller is also @Composable |\r
| `Duplicate class` | Dependency conflict | Use `./gradlew dependencies` to investigate |\r
| `AAPT: error` | Resource file error | Check XML syntax and resource references |\r
\r
### 6.2 Fix Best Practices\r
\r
1. **Read the complete error message first**: Locate file and line number\r
2. **Check recent changes**: Problems usually in latest modifications\r
3. **Clean Build**: `./gradlew clean assembleDebug`\r
4. **Check dependency versions**: Version conflicts are common causes\r
5. **Refresh dependencies if needed**: Clear cache and rebuild\r
\r
### 6.3 Debugging Commands\r
\r
```bash\r
# Clean and build\r
./gradlew clean assembleDebug\r
\r
# View dependency tree (investigate conflicts)\r
./gradlew :app:dependencies\r
\r
# View detailed errors\r
./gradlew assembleDebug --stacktrace\r
\r
# Refresh dependencies\r
./gradlew --refresh-dependencies\r
```\r
\r
---\r
\r
## 7. Material Design 3 Guidelines\r
\r
Review Android UI files for compliance with Material Design 3 Guidelines and Android best practices.\r
\r
### Design Philosophy\r
\r
#### M3 Core Principles\r
\r
| Principle | Description |\r
|-----------|-------------|\r
| **Personal** | Dynamic color based on user preferences and wallpaper |\r
| **Adaptive** | Responsive across all screen sizes and form factors |\r
| **Expressive** | Bold colors and typography with personality |\r
| **Accessible** | Inclusive design for all users |\r
\r
#### M3 Expressive (Latest)\r
\r
The latest evolution adds emotion-driven UX through:\r
- Vibrant, dynamic colors\r
- Intuitive motion physics\r
- Adaptive components\r
- Flexible typography\r
- Contrasting shapes (35 new shape options)\r
\r
### App Style Selection\r
\r
**Critical Decision**: Match visual style to app category and target audience.\r
\r
| App Category | Visual Style | Key Characteristics |\r
|--------------|--------------|---------------------|\r
| Utility/Tool | Minimalist | Clean, efficient, neutral colors |\r
| Finance/Banking | Professional Trust | Conservative colors, security-focused |\r
| Health/Wellness | Calm & Natural | Soft colors, organic shapes |\r
| Kids (3-5) | Playful Simple | Bright colors, large targets (56dp+) |\r
| Kids (6-12) | Fun & Engaging | Vibrant, gamified feedback |\r
| Social/Entertainment | Expressive | Brand-driven, gesture-rich |\r
| Productivity | Clean & Focused | Minimal, high contrast |\r
| E-commerce | Conversion-focused | Clear CTAs, scannable |\r
\r
See [Design Style Guide](references/design-style-guide.md) for detailed style profiles.\r
\r
### Quick Reference: Key Specifications\r
\r
#### Color Contrast Requirements\r
\r
| Element | Minimum Ratio |\r
|---------|---------------|\r
| Body text | **4.5:1** |\r
| Large text (18sp+) | **3:1** |\r
| UI components | **3:1** |\r
\r
#### Touch Targets\r
\r
| Type | Size |\r
|------|------|\r
| Minimum | 48 × 48dp |\r
| Recommended (primary actions) | 56 × 56dp |\r
| Kids apps | 56dp+ |\r
| Spacing between targets | 8dp minimum |\r
\r
#### 8dp Grid System\r
\r
| Token | Value | Usage |\r
|-------|-------|-------|\r
| xs | 4dp | Icon padding |\r
| sm | 8dp | Tight spacing |\r
| md | 16dp | Default padding |\r
| lg | 24dp | Section spacing |\r
| xl | 32dp | Large gaps |\r
| xxl | 48dp | Screen margins |\r
\r
#### Typography Scale (Summary)\r
\r
| Category | Sizes |\r
|----------|-------|\r
| Display | 57sp, 45sp, 36sp |\r
| Headline | 32sp, 28sp, 24sp |\r
| Title | 22sp, 16sp, 14sp |\r
| Body | 16sp, 14sp, 12sp |\r
| Label | 14sp, 12sp, 11sp |\r
\r
#### Animation Duration\r
\r
| Type | Duration |\r
|------|----------|\r
| Micro (ripples) | 50-100ms |\r
| Short (simple) | 100-200ms |\r
| Medium (expand/collapse) | 200-300ms |\r
| Long (complex) | 300-500ms |\r
\r
#### Component Dimensions\r
\r
| Component | Height | Min Width |\r
|-----------|--------|-----------|\r
| Button | 40dp | 64dp |\r
| FAB | 56dp | 56dp |\r
| Text Field | 56dp | 280dp |\r
| App Bar | 64dp | - |\r
| Bottom Nav | 80dp | - |\r
\r
### Anti-Patterns (Must Avoid)\r
\r
#### UI Anti-Patterns\r
- More than 5 bottom navigation items\r
- Multiple FABs on same screen\r
- Touch targets smaller than 48dp\r
- Inconsistent spacing (non-8dp multiples)\r
- Missing dark theme support\r
- Text on colored backgrounds without contrast check\r
\r
#### Performance Anti-Patterns\r
- Startup time > 2 seconds without progress indicator\r
- Frame rate \x3C 60 FPS (> 16ms per frame)\r
- Crash rate > 1.09% (Google Play threshold)\r
- ANR rate > 0.47% (Google Play threshold)\r
\r
#### Accessibility Anti-Patterns\r
- Missing contentDescription on interactive elements\r
- Element type in labels (e.g., "Save button" instead of "Save")\r
- Complex gestures in kids apps\r
- Text-only buttons for non-readers\r
\r
### Review Checklist\r
\r
- [ ] 8dp spacing grid compliance\r
- [ ] 48dp minimum touch targets\r
- [ ] Proper typography scale usage\r
- [ ] Color contrast compliance (4.5:1+ for text)\r
- [ ] Dark theme support\r
- [ ] contentDescription on all interactive elements\r
- [ ] Startup \x3C 2 seconds or shows progress\r
- [ ] Visual style matches app category\r
\r
### Design References\r
\r
| Topic | Reference |\r
|-------|-----------|\r
| Colors, Typography, Spacing, Shapes | [Visual Design](references/visual-design.md) |\r
| Animation & Transitions | [Motion System](references/motion-system.md) |\r
| Accessibility Guidelines | [Accessibility](references/accessibility.md) |\r
| Large Screens & Foldables | [Adaptive Screens](references/adaptive-screens.md) |\r
| Android Vitals & Performance | [Performance & Stability](references/performance-stability.md) |\r
| Privacy & Security | [Privacy & Security](references/privacy-security.md) |\r
| Audio, Video, Notifications | [Functional Requirements](references/functional-requirements.md) |\r
| App Style by Category | [Design Style Guide](references/design-style-guide.md) |\r
\r
---\r
\r
## 8. Testing\r
\r
> **Note**: Only add test dependencies when the user explicitly asks for testing.\r
\r
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.\r
\r
### 8.1 Test Dependencies\r
\r
Before adding test dependencies, inspect the project's existing versions to avoid conflicts:\r
\r
1. Check `gradle/libs.versions.toml` — if present, add test deps using the project's version catalog style\r
2. Check existing `build.gradle.kts` for already-pinned dependency versions\r
3. Match version families using the table below\r
\r
**Version Alignment Rules**:\r
\r
| Test Dependency | Must Align With | How to Check |\r
|----------------------------------------------|--------------------------------------------------|-----------------------------------------------------------------------|\r
| `kotlinx-coroutines-test` | Project's `kotlinx-coroutines-core` version | Search for `kotlinx-coroutines` in build files or version catalog |\r
| `compose-ui-test-junit4` | Project's Compose BOM or `compose-compiler` | Search for `compose-bom` or `compose.compiler` in build files |\r
| `espresso-*` | All Espresso artifacts must use the same version | Search for `espresso` in build files |\r
| `androidx.test:runner`, `rules`, `ext:junit` | Should use compatible AndroidX Test versions | Search for `androidx.test` in build files |\r
| `mockk` | Must support the project's Kotlin version | Check `kotlin` version in root `build.gradle.kts` or version catalog |\r
\r
**Dependencies Reference** — add only the groups you need:\r
\r
```kotlin\r
dependencies {\r
// --- Local unit tests (src/test/) ---\r
testImplementation("junit:junit:\x3Cversion>") // 4.13.2+\r
testImplementation("org.robolectric:robolectric:\x3Cversion>") // 4.16.1+\r
testImplementation("io.mockk:mockk:\x3Cversion>") // match Kotlin version\r
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:\x3Cversion>") // match coroutines-core\r
testImplementation("androidx.arch.core:core-testing:\x3Cversion>") // InstantTaskExecutorRule for LiveData\r
testImplementation("app.cash.turbine:turbine:\x3Cversion>") // Flow/StateFlow testing\r
\r
// --- Instrumentation tests (src/androidTest/) ---\r
androidTestImplementation("androidx.test.ext:junit:\x3Cversion>")\r
androidTestImplementation("androidx.test:runner:\x3Cversion>")\r
androidTestImplementation("androidx.test:rules:\x3Cversion>")\r
androidTestImplementation("androidx.test.espresso:espresso-core:\x3Cversion>")\r
androidTestImplementation("androidx.test.espresso:espresso-contrib:\x3Cversion>") // RecyclerView, Drawer\r
androidTestImplementation("androidx.test.espresso:espresso-intents:\x3Cversion>") // Intent verification\r
androidTestImplementation("androidx.test.espresso:espresso-idling-resource:\x3Cversion>")\r
androidTestImplementation("androidx.test.uiautomator:uiautomator:\x3Cversion>")\r
\r
// --- Compose UI tests (only if project uses Compose) ---\r
androidTestImplementation("androidx.compose.ui:ui-test-junit4") // version from Compose BOM\r
debugImplementation("androidx.compose.ui:ui-test-manifest") // required for createComposeRule\r
}\r
```\r
\r
> **Note**: If the project uses a Compose BOM, `ui-test-junit4` and `ui-test-manifest` don't need explicit versions — the BOM manages them.\r
\r
Enable Robolectric resource support in the `android` block:\r
\r
```kotlin\r
android {\r
testOptions {\r
unitTests.isIncludeAndroidResources = true // required for Robolectric\r
}\r
}\r
```\r
\r
### 8.2 Testing by Layer\r
\r
| Layer | Location | Runs On | Speed | Use For |\r
|--------------------|--------------------|-------------------------|----------------------|--------------------------------------------------|\r
| Unit (JUnit) | `src/test/` | JVM | ~ms | ViewModels, repos, mappers, validators |\r
| Unit + Robolectric | `src/test/` | JVM + simulated Android | ~100ms | Code needing Context, resources, SharedPrefs |\r
| Compose UI (local) | `src/test/` | JVM + Robolectric | ~100ms | Composable rendering & interaction |\r
| Espresso | `src/androidTest/` | Device/Emulator | ~seconds | View-based UI flows, Intents, DB integration |\r
| Compose UI (device)| `src/androidTest/` | Device/Emulator | ~seconds | Full Compose UI flows with real rendering |\r
| UI Automator | `src/androidTest/` | Device/Emulator | ~seconds | System dialogs, notifications, multi-app |\r
| Managed Device | `src/androidTest/` | Gradle-managed AVD | ~minutes (first run) | CI, matrix testing across API levels |\r
\r
See [Testing](references/testing.md) for detailed examples, code patterns, and Gradle Managed Device configuration.\r
\r
### 8.3 Testing Commands\r
\r
```bash\r
# Local unit tests (fast, no emulator)\r
./gradlew test # all modules\r
./gradlew :app:testDebugUnitTest # app module, debug variant\r
\r
# Single test class\r
./gradlew :app:testDebugUnitTest --tests "com.example.myapp.CounterViewModelTest"\r
\r
# Instrumentation tests (requires device or managed device)\r
./gradlew connectedDebugAndroidTest # on connected device\r
./gradlew pixel6Api34DebugAndroidTest # on managed device\r
\r
# Both together\r
./gradlew test connectedDebugAndroidTest\r
\r
# Test with coverage report (JaCoCo)\r
./gradlew testDebugUnitTest jacocoTestReport\r
```\r
- 确保已安装 OpenClaw(本地或 Docker 部署)
- 在对话框中输入安装命令:
/install android-native-dev - 安装完成后,直接呼叫该 Skill 的名称或使用
/android-native-dev触发 - 根据 Skill 的参数说明提供必要输入,即可获得结构化输出
Android Native Dev 是什么?
Android native application development and UI design guide. Covers Material Design 3, Kotlin/Compose development, project configuration, accessibility, and b... 它是一个面向 Claude Code / OpenClaw 的 AI Agent Skill 插件,目前累计下载 454 次。
如何安装 Android Native Dev?
在 OpenClaw 或 Claude Code 对话框中运行命令「/install android-native-dev」即可一键安装,无需额外配置。
Android Native Dev 是免费的吗?
是的,Android Native Dev 完全免费,采用 MIT-0 许可证,可自由下载、安装和使用。
Android Native Dev 支持哪些平台?
Android Native Dev 跨平台运行,可在任意部署了 OpenClaw / Claude Code 的环境中使用(cross-platform)。
谁开发了 Android Native Dev?
由 smasherlxd(@smasherlxd)开发并维护,当前版本 v1.0.0。