بنقرة واحدة
android-native-dev
Android native Kotlin/Compose app development, Material 3 UI, accessibility, and Gradle builds.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Android native Kotlin/Compose app development, Material 3 UI, accessibility, and Gradle builds.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Create, update, or validate a project-local AGENTS.md from the shared AILI template when initializing or checking repository agent rules.
Run the AILI delivery lifecycle for /ideate, /define, /build, and /ship; use for idea shaping, spec/test definition, autonomous BUILD package queues, review-repair closeout, or backend routing without exposing internal stage commands.
Analyze explicit user-provided or approved evidence such as sanitized OpenCode session exports, selected transcripts, git history, implementation notes, or task records to produce a safe report-first workflow retrospective; do not claim global history access, commit raw sessions/logs, or directly edit protected harness surfaces.
Diagnose user-reported AILI/ROSE harness or workflow behavior problems without editing files; use when the user says a command, skill trigger, subagent packet, memory flow, installer, docs, or agent prompt behavior is wrong and wants to know where the issue lives and how to fix it. Do not use for normal product-code bugs; hand off approved harness modifications to harness-evolution.
Use for `/local-review` or lifecycle-triggered local review gates over worktree diffs, refs, PRs, or OpenSpec changes; owns target resolution, evidence scope, categorized review reports, verdicts, explicit repair/re-review, and no-remote-mutation boundaries without overriding OpenCode `/review`.
Use when ROSE needs context-saving subagent dispatch: single read-only scouting for noisy repository evidence, or splitting two or more independent investigation, implementation, review, testing, documentation, or security work packages across subagents without shared mutable state, overlapping edits, or sequential dependencies.
| name | android-native-dev |
| description | Android native Kotlin/Compose app development, Material 3 UI, accessibility, and Gradle builds. |
Use this skill for native Android apps, Kotlin, Jetpack Compose, Android Views, Gradle/Android Studio, Material 3, Android permissions, and Google Play quality. Use ios-application-dev for native iOS, flutter-dev for Flutter/Dart, react-native-dev for React Native/Expo, and frontend-ui-engineering or frontend-dev for browser React/web UI.
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:
./gradlew assembleDebug succeedsgradle.properties is missing, create it first and configure AndroidX🔴 CHECKPOINT — Visual and platform decisions before UI code: confirm app category, target devices/API, Compose vs Views, Material 3 style, dark theme, accessibility target, and required permissions. If the user has not specified these and the choice affects navigation, data model, permissions, or visual identity, stop and ask instead of guessing.
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
# 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
OutOfMemoryErrorduring build, increase-Xmxvalue. Large projects with many dependencies may require 8GB or more.
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")
}
Use product flavors for distinct app variants such as dev/staging/prod or free/paid. Keep variant-specific application IDs, app names, API URLs, logging flags, and resources in app/build.gradle.kts / source sets instead of runtime conditionals.
{flavor}{BuildType} such as devDebug or prodRelease../gradlew tasks --group="build", ./gradlew assembleDevDebug, ./gradlew assembleRelease, ./gradlew installDevDebug.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: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
| 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() |
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) }
}
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
}
// 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
)
lateinit for data that may not be initialized before use; prefer nullable state or explicit defaults.return@forEach inside lambdas when you do not intend to return from the outer function.private or internal.// ❌ 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
)
// ❌ 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()
}
}
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 |
// ❌ 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()
}
}
// 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()
}
// ❌ 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()) {
// ...
}
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>
| Type | Prefix | Example |
|---|---|---|
| Layout | layout_ | layout_main.xml |
| Image | ic_, img_, bg_ | ic_user.png |
| Color | color_ | color_primary |
| String | - | app_name, btn_submit |
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 |
Use descriptive prefixes, e.g. app_background, icon_primary, screenBackground, and ic_home, rather than generic system-like names such as background, icon, or view.
| 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 |
./gradlew clean assembleDebug| Trigger | First action | If still failing |
|---|---|---|
| Gradle/AGP/Kotlin version conflict | Inspect wrapper, root Gradle, module Gradle, and version catalog before edits | Ask before changing plugin or dependency versions; do not broad-upgrade blindly |
| Compose compiler/BOM mismatch | Align with existing project version source | If no source exists, ask before pinning versions |
| AAPT/resource error | Fix the named XML/resource only | Do not rename resources broadly; verify references first |
| Cache/dependency corruption suspected | Run ./gradlew assembleDebug --stacktrace before cache refresh | Use --refresh-dependencies only after evidence points to cache/dependency state |
🛑 STOP: never delete Gradle caches, change SDK/AGP/Kotlin major versions, or rewrite build files as a generic fix without user approval and a specific error trace.
# 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
Review Android UI files for compliance with Material Design 3 Guidelines and Android best practices.
| 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 |
The latest evolution adds emotion-driven UX through:
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.
| Element | Minimum Ratio |
|---|---|
| Body text | 4.5:1 |
| Large text (18sp+) | 3:1 |
| UI components | 3:1 |
| Type | Size |
|---|---|
| Minimum | 48 × 48dp |
| Recommended (primary actions) | 56 × 56dp |
| Kids apps | 56dp+ |
| Spacing between targets | 8dp minimum |
| 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 |
| Category | Sizes |
|---|---|
| Display | 57sp, 45sp, 36sp |
| Headline | 32sp, 28sp, 24sp |
| Title | 22sp, 16sp, 14sp |
| Body | 16sp, 14sp, 12sp |
| Label | 14sp, 12sp, 11sp |
| Type | Duration |
|---|---|
| Micro (ripples) | 50-100ms |
| Short (simple) | 100-200ms |
| Medium (expand/collapse) | 200-300ms |
| Long (complex) | 300-500ms |
| Component | Height | Min Width |
|---|---|---|
| Button | 40dp | 64dp |
| FAB | 56dp | 56dp |
| Text Field | 56dp | 280dp |
| App Bar | 64dp | - |
| Bottom Nav | 80dp | - |
🔴 CHECKPOINT — Visual validation before delivery: run or inspect the changed screen in light/dark mode and at least one compact and one expanded size when UI changed. If no emulator/device/browser-preview equivalent is available, report the missing visual evidence and do not claim full UI verification.
| Topic | Reference |
|---|---|
| Colors, Typography, Spacing, Shapes | Visual Design |
| Animation & Transitions | Motion System |
| Accessibility Guidelines | Accessibility |
| Large Screens & Foldables | Adaptive Screens |
| Android Vitals & Performance | Performance & Stability |
| Privacy & Security | Privacy & Security |
| Audio, Video, Notifications | Functional Requirements |
| App Style by Category | Design Style Guide |
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.
Before adding test dependencies, inspect the project's existing versions to avoid conflicts:
gradle/libs.versions.toml — if present, add test deps using the project's version catalog stylebuild.gradle.kts for already-pinned dependency versionsVersion 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: JUnit/Robolectric/MockK/coroutines-test for local unit tests, AndroidX Test/Espresso/UI Automator for instrumentation, and compose-ui-test-junit4 plus ui-test-manifest for Compose UI tests.
Note: If the project uses a Compose BOM,
ui-test-junit4andui-test-manifestdon't need explicit versions — the BOM manages them.
Enable Robolectric resource support in the android block:
android {
testOptions {
unitTests.isIncludeAndroidResources = true // required for Robolectric
}
}
| 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.
# 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