AgentStack
SKILL verified MIT Self-run

Android Native Dev

skill-martgueritainaccurate875-skills-android-native-dev · by martgueritainaccurate875

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.

No reviews yet
0 installs
16 views
0.0% view→install

Install

$ agentstack add skill-martgueritainaccurate875-skills-android-native-dev

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Android Native Dev? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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 {
    // 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: > ``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 {
    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()  // 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(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.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+):


    
    

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:


#FFFFFF
#000000

#FFFFFF
#000000
// ❌ 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

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

  1. Read the complete error message first: Locate file and line number
  2. Check recent changes: Problem

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.