Commit bb98deffa3c8
Changed files (11)
dots
.config
claude
skills
Android
dots/.config/claude/skills/Android/workflows/Build.md
@@ -1,6 +1,25 @@
# Build Workflow
-Build Android applications (APK/AAB) using Gradle build system.
+Build Android applications and Go libraries for Android, manage dependencies with Gradle, and publish to Google Play Store.
+
+## Quick Start
+
+```bash
+# Navigate to project
+cd /path/to/android-project
+
+# Build debug APK
+./gradlew assembleDebug
+
+# Build release APK
+./gradlew assembleRelease
+
+# Build release AAB (for Play Store)
+./gradlew bundleRelease
+
+# Install debug on device
+./gradlew installDebug
+```
## Build Types
@@ -16,53 +35,636 @@ Build Android applications (APK/AAB) using Gradle build system.
- Cannot be directly installed
- Use for: Play Store releases
-## Quick Start
+## Gradle Fundamentals
-```bash
-# Navigate to project
-cd /path/to/android-project
+### Project Structure
-# Build debug APK
-./gradlew assembleDebug
-
-# Build release APK
-./gradlew assembleRelease
-
-# Build release AAB
-./gradlew bundleRelease
-
-# Install debug on device
-./gradlew installDebug
+```
+myproject/
+├── gradle/
+│ ├── libs.versions.toml # Version catalog (recommended)
+│ └── wrapper/
+│ ├── gradle-wrapper.jar
+│ └── gradle-wrapper.properties
+├── app/
+│ └── build.gradle.kts # App module build file
+├── build.gradle.kts # Root build file
+├── settings.gradle.kts # Project settings
+├── gradle.properties # Global Gradle properties
+└── local.properties # Local config (not in git)
```
-## Build Variants
+### Version Catalogs (Modern Approach)
-Android projects typically have:
+**gradle/libs.versions.toml:**
+```toml
+[versions]
+agp = "8.3.0"
+kotlin = "1.9.22"
+compileSdk = "34"
+minSdk = "24"
+targetSdk = "34"
-| Variant | Use Case | Signed | Minified |
-|---------|----------|--------|----------|
-| Debug | Development | Auto | No |
-| Release | Production | Manual | Yes |
+androidx-core = "1.12.0"
+material = "1.11.0"
-### Debug Build
+[libraries]
+androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+```
+
+**Root build.gradle.kts:**
+```kotlin
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
+}
+```
+
+**App build.gradle.kts:**
+```kotlin
+plugins {
+ alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace = "com.example.myapp"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.example.myapp"
+ minSdk = 24
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ kotlinOptions {
+ jvmTarget = "17"
+ }
+}
+
+dependencies {
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.material)
+
+ // Gomobile AAR
+ implementation(files("libs/mylib.aar"))
+}
+```
+
+### Managing Dependencies
+
+**Add dependency to version catalog:**
+```toml
+[versions]
+retrofit = "2.9.0"
+
+[libraries]
+retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
+```
+
+**Use in build.gradle.kts:**
+```kotlin
+dependencies {
+ implementation(libs.retrofit)
+}
+```
+
+**Dependency scopes:**
+```kotlin
+dependencies {
+ implementation("...") // Recommended: Not exposed to consumers
+ api("...") // Exposed to consumers
+ compileOnly("...") // Compile only (not in APK)
+ runtimeOnly("...") // Runtime only
+
+ testImplementation("...") // Unit tests
+ androidTestImplementation("...")// Instrumentation tests
+
+ debugImplementation("...") // Debug build only
+}
+```
+
+**Local AAR/JAR files:**
+```kotlin
+dependencies {
+ // Single AAR
+ implementation(files("libs/mylib.aar"))
+
+ // All AARs in libs directory
+ implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
+}
+```
+
+### gradle.properties
+
+```properties
+# Performance
+org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
+org.gradle.daemon=true
+org.gradle.parallel=true
+org.gradle.caching=true
+
+# AndroidX
+android.useAndroidX=true
+android.enableJetifier=true
+
+# Build
+android.nonTransitiveRClass=true
+```
+
+### Build Variants
+
+**Build types:**
+```kotlin
+android {
+ buildTypes {
+ debug {
+ applicationIdSuffix = ".debug"
+ isDebuggable = true
+ }
+
+ release {
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(...)
+ }
+
+ create("staging") {
+ initWith(getByName("debug"))
+ applicationIdSuffix = ".staging"
+ }
+ }
+}
+```
+
+**Product flavors:**
+```kotlin
+android {
+ flavorDimensions += "version"
+
+ productFlavors {
+ create("free") {
+ dimension = "version"
+ applicationIdSuffix = ".free"
+ }
+
+ create("paid") {
+ dimension = "version"
+ applicationIdSuffix = ".paid"
+ }
+ }
+}
+```
+
+Build specific variant:
+```bash
+./gradlew assembleFreeDebug
+./gradlew assemblePaidRelease
+```
+
+### Common Gradle Tasks
```bash
-# Build
+# List all tasks
+./gradlew tasks
+
+# Clean build outputs
+./gradlew clean
+
+# Build all variants
+./gradlew build
+
+# Check dependencies
+./gradlew dependencies
+
+# Show dependency tree
+./gradlew app:dependencies --configuration debugRuntimeClasspath
+
+# Refresh dependencies
+./gradlew --refresh-dependencies
+```
+
+## Building Go Libraries (gomobile bind)
+
+Use gomobile bind to create Android AAR packages from Go code. This allows you to use Go libraries in Android apps written in Java/Kotlin.
+
+### When to Use
+
+- You have existing Go code (networking, crypto, business logic)
+- You want to integrate Go into an existing Android app
+- You need Go's concurrency or standard library in Android
+- You want to share code between backend and mobile
+
+### Prerequisites
+
+```bash
+# Install gomobile
+go install golang.org/x/mobile/cmd/gomobile@latest
+
+# Initialize (downloads Android toolchain)
+gomobile init
+
+# Verify
+gomobile version
+```
+
+### Go Code Requirements
+
+**What works in gomobile:**
+- ✅ Exported functions with basic types
+- ✅ Exported structs with exported fields
+- ✅ Methods on exported structs
+- ✅ Interfaces (for callbacks)
+- ✅ Error return values (become exceptions)
+- ✅ Slices of basic types (`[]byte`, `[]int`, `[]string`)
+
+**What doesn't work:**
+- ❌ Maps (use structs instead)
+- ❌ Channels (use callbacks via interfaces)
+- ❌ Generics
+- ❌ Unexported types in function signatures
+- ❌ Variadic functions
+
+**Example Go library:**
+```go
+// File: golib/network.go
+package network
+
+import (
+ "fmt"
+ "net/http"
+)
+
+// FetchURL fetches content from a URL
+func FetchURL(url string) (string, error) {
+ resp, err := http.Get(url)
+ if err != nil {
+ return "", err
+ }
+ defer resp.Body.Close()
+
+ // Implementation...
+ return "content", nil
+}
+
+// Config represents network configuration
+type Config struct {
+ Timeout int
+ BaseURL string
+}
+
+// NewConfig creates a new Config
+func NewConfig(timeout int, baseURL string) *Config {
+ return &Config{
+ Timeout: timeout,
+ BaseURL: baseURL,
+ }
+}
+
+// Callback interface for async operations
+type Callback interface {
+ OnSuccess(data string)
+ OnError(err error)
+}
+
+// FetchAsync fetches URL asynchronously
+func FetchAsync(url string, callback Callback) {
+ go func() {
+ result, err := FetchURL(url)
+ if err != nil {
+ callback.OnError(err)
+ return
+ }
+ callback.OnSuccess(result)
+ }()
+}
+```
+
+### Build AAR
+
+**Basic build (all architectures):**
+```bash
+gomobile bind -target=android -o mylib.aar ./golib
+```
+
+**Build specific architectures:**
+```bash
+# ARM64 only (most modern devices)
+gomobile bind -target=android/arm64 -o mylib.aar ./golib
+
+# ARM64 and ARM32
+gomobile bind -target=android/arm64,android/arm -o mylib.aar ./golib
+
+# All common architectures
+gomobile bind -target=android/arm64,android/arm,android/amd64,android/386 -o mylib.aar ./golib
+```
+
+**Architecture options:**
+- `android/arm64`: 64-bit ARM (arm64-v8a) - modern devices
+- `android/arm`: 32-bit ARM (armeabi-v7a) - older devices
+- `android/amd64`: 64-bit x86 (x86_64) - emulators
+- `android/386`: 32-bit x86 (x86) - old emulators
+
+### Integrate AAR into Android Project
+
+**Copy AAR:**
+```bash
+cp mylib.aar /path/to/android-project/app/libs/
+```
+
+**Add dependency in app/build.gradle.kts:**
+```kotlin
+dependencies {
+ implementation(files("libs/mylib.aar"))
+}
+```
+
+**Sync Gradle:**
+```bash
+./gradlew sync
+```
+
+### Use in Android Code
+
+**Kotlin example:**
+```kotlin
+import golib.Network
+
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ // Call Go function
+ try {
+ val result = Network.fetchURL("https://example.com")
+ println("Result: $result")
+ } catch (e: Exception) {
+ println("Error: ${e.message}")
+ }
+
+ // Use Go struct
+ val config = Network.newConfig(30, "https://api.example.com")
+ println("Timeout: ${config.timeout}")
+
+ // Async with callback
+ Network.fetchAsync("https://example.com", object : Network.Callback {
+ override fun onSuccess(data: String) {
+ println("Success: $data")
+ }
+
+ override fun onError(err: Exception) {
+ println("Error: ${err.message}")
+ }
+ })
+ }
+}
+```
+
+### Type Conversion Reference
+
+| Go Type | Java/Kotlin Type | Notes |
+|---------|------------------|-------|
+| `bool` | `boolean` / `Boolean` | |
+| `int`, `int32` | `int` / `Int` | 32-bit signed |
+| `int64` | `long` / `Long` | 64-bit signed |
+| `float64` | `double` / `Double` | |
+| `string` | `String` | UTF-8 encoded |
+| `[]byte` | `byte[]` / `ByteArray` | |
+| `error` | `Exception` | Go errors become Java exceptions |
+| `struct` | `class` | Exported fields become getters/setters |
+| `interface` | `interface` | Must have exported methods only |
+
+### Optimization Tips
+
+**Minimize boundary crossings:**
+
+Bad (many crossings):
+```go
+func ProcessItem(item string) string { ... }
+// Called 1000 times from Android = 1000 Go calls
+```
+
+Good (single crossing):
+```go
+func ProcessItems(items []string) []string { ... }
+// Called once from Android = 1 Go call
+```
+
+**Use appropriate architectures:**
+
+For development (faster builds):
+```bash
+gomobile bind -target=android/arm64 -o mylib.aar ./golib
+```
+
+For release (wider compatibility):
+```bash
+gomobile bind -target=android/arm64,android/arm -o mylib.aar ./golib
+```
+
+### Build Automation Script
+
+```bash
+#!/usr/bin/env bash
+set -euo pipefail
+
+echo "Building AAR..."
+
+TARGETS=${ANDROID_TARGETS:-"android/arm64,android/arm"}
+OUTPUT=${AAR_OUTPUT:-"mylib.aar"}
+PACKAGE=${GO_PACKAGE:-"./golib"}
+
+gomobile bind \
+ -target="$TARGETS" \
+ -o "$OUTPUT" \
+ "$PACKAGE"
+
+echo "AAR built: $OUTPUT"
+
+# Copy to Android project if path is set
+if [ -n "${ANDROID_PROJECT:-}" ]; then
+ cp "$OUTPUT" "$ANDROID_PROJECT/app/libs/"
+ echo "Copied to Android project"
+fi
+```
+
+Usage:
+```bash
+# Build for ARM64 only
+ANDROID_TARGETS=android/arm64 ./build-aar.sh
+
+# Build and copy to Android project
+ANDROID_PROJECT=/path/to/android-app ./build-aar.sh
+```
+
+## Building Standalone Go Apps (gomobile build)
+
+Build standalone Android applications entirely in Go using `gomobile build`. Creates a complete APK with Go code as the main application logic.
+
+### When to Use vs gomobile bind
+
+| Feature | gomobile build | gomobile bind |
+|---------|----------------|---------------|
+| UI | OpenGL only | Full Android UI |
+| Android APIs | Limited | Full access |
+| Development speed | Fast | Medium |
+| Production apps | Demos/utilities | Production ready |
+| Learning curve | Low | Medium |
+
+**Use gomobile build when:**
+- You want to write the entire app in Go
+- You're building a simple utility or demo
+- You want to prototype quickly
+- You're comfortable with limited UI
+
+**Use gomobile bind when:**
+- You need complex native Android UI
+- You need full Android framework APIs
+- You're building a production app with rich UI
+
+### Basic App Example
+
+```go
+package main
+
+import (
+ "log"
+ "golang.org/x/mobile/app"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/gl"
+)
+
+func main() {
+ app.Main(func(a app.App) {
+ var glctx gl.Context
+
+ for e := range a.Events() {
+ switch e := a.Filter(e).(type) {
+ case lifecycle.Event:
+ switch e.Crosses(lifecycle.StageVisible) {
+ case lifecycle.CrossOn:
+ glctx, _ = e.DrawContext.(gl.Context)
+ log.Println("App started")
+ case lifecycle.CrossOff:
+ log.Println("App stopped")
+ glctx = nil
+ }
+
+ case paint.Event:
+ if glctx == nil {
+ continue
+ }
+ // Clear screen
+ glctx.ClearColor(0.2, 0.2, 0.3, 1.0)
+ glctx.Clear(gl.COLOR_BUFFER_BIT)
+ a.Publish()
+ }
+ }
+ })
+}
+```
+
+### Build APK
+
+**Basic build:**
+```bash
+gomobile build -target=android .
+```
+
+**Build with custom app ID:**
+```bash
+gomobile build -target=android -appid=com.example.myapp .
+```
+
+**Build for specific architectures:**
+```bash
+# ARM64 only
+gomobile build -target=android/arm64 -appid=com.example.myapp .
+
+# ARM64 and ARM32
+gomobile build -target=android/arm64,android/arm -appid=com.example.myapp .
+```
+
+**Build with custom icon:**
+```bash
+# Place icon at assets/icon.png
+gomobile build -target=android -icon=assets/icon.png .
+```
+
+### Install and Run
+
+```bash
+# Build and install
+gomobile install -target=android -appid=com.example.myapp .
+
+# Or build then install separately
+gomobile build -target=android -appid=com.example.myapp .
+adb install -r myapp.apk
+```
+
+**View logs:**
+```bash
+adb logcat | grep GoLog
+```
+
+### Limitations
+
+**UI Limitations:**
+- ❌ No native Android UI widgets
+- ❌ No Material Design components
+- ❌ Limited text rendering
+- ✅ OpenGL ES 2.0 for custom graphics
+- ✅ Touch event handling
+
+**Platform Limitations:**
+- ❌ No direct access to Android framework APIs
+- ❌ Can't use Java/Kotlin libraries
+- ✅ Network access (HTTP, TCP, UDP)
+- ✅ File I/O
+- ✅ Concurrency with goroutines
+
+## Building Android Apps
+
+### Build Debug
+
+```bash
+# Build debug APK
./gradlew assembleDebug
# Output location
ls app/build/outputs/apk/debug/app-debug.apk
-# Install on connected device
+# Install on device
adb install app/build/outputs/apk/debug/app-debug.apk
# Or use Gradle install task
./gradlew installDebug
```
-### Release Build
-
-Requires signing configuration (see **Publish** workflow).
+### Build Release
```bash
# Build signed release APK
@@ -78,151 +680,7 @@ ls app/build/outputs/apk/release/app-release.apk
ls app/build/outputs/bundle/release/app-release.aab
```
-## Build Configuration
-
-### Gradle Wrapper
-
-Always use the wrapper (./gradlew) for consistent builds:
-
-```bash
-# Linux/Mac
-./gradlew <task>
-
-# Windows
-gradlew.bat <task>
-
-# Update wrapper
-./gradlew wrapper --gradle-version=8.6
-```
-
-### Common Gradle Tasks
-
-```bash
-# List all tasks
-./gradlew tasks
-
-# Clean build outputs
-./gradlew clean
-
-# Build all variants
-./gradlew build
-
-# Build and run tests
-./gradlew build test
-
-# Check dependencies
-./gradlew dependencies
-
-# Show project structure
-./gradlew projects
-```
-
-## Build Optimization
-
-### Enable Build Cache
-
-`gradle.properties`:
-```properties
-org.gradle.caching=true
-org.gradle.parallel=true
-org.gradle.daemon=true
-org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
-```
-
-### Optimize Build Time
-
-```kotlin
-// app/build.gradle.kts
-android {
- // Use only needed ABIs during development
- splits {
- abi {
- isEnable = false
- }
- }
-
- // Disable PNG crunching in debug
- buildTypes {
- debug {
- isCrunchPngs = false
- }
- }
-}
-```
-
-### Incremental Builds
-
-Gradle automatically handles incremental builds. Avoid `clean` unless necessary.
-
-```bash
-# Good: Incremental build
-./gradlew assembleDebug
-
-# Bad: Full rebuild (slower)
-./gradlew clean assembleDebug
-```
-
-## Multi-Module Projects
-
-For projects with multiple modules:
-
-```bash
-# Build specific module
-./gradlew :app:assembleDebug
-./gradlew :library:build
-
-# Build all modules
-./gradlew build
-```
-
-## Build with Different ABIs
-
-### Split APKs by ABI
-
-Reduces APK size by creating separate APKs per architecture.
-
-```kotlin
-// app/build.gradle.kts
-android {
- splits {
- abi {
- isEnable = true
- reset()
- include("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
- isUniversalApk = false // Set true to also create universal APK
- }
- }
-}
-```
-
-Build output:
-```
-app/build/outputs/apk/release/
-├── app-arm64-v8a-release.apk
-├── app-armeabi-v7a-release.apk
-├── app-x86_64-release.apk
-└── app-x86-release.apk
-```
-
-### Filter ABIs (Development)
-
-Include only needed ABIs for faster builds:
-
-```kotlin
-android {
- defaultConfig {
- ndk {
- // Only build for emulator during development
- abiFilters.addAll(listOf("x86_64"))
-
- // Or for physical device
- // abiFilters.addAll(listOf("arm64-v8a"))
- }
- }
-}
-```
-
-## Build with Gomobile AAR
+### Build with Gomobile AAR
If using gomobile bind:
@@ -258,59 +716,274 @@ cd ..
echo "Done! APK: app/build/outputs/apk/debug/app-debug.apk"
```
-## Build Flavors
+### Build Optimization
-Create different versions of your app:
+**Enable build cache (gradle.properties):**
+```properties
+org.gradle.caching=true
+org.gradle.parallel=true
+org.gradle.daemon=true
+org.gradle.jvmargs=-Xmx4096m
+```
+**Optimize build time:**
```kotlin
-// app/build.gradle.kts
android {
- flavorDimensions += "version"
-
- productFlavors {
- create("free") {
- dimension = "version"
- applicationIdSuffix = ".free"
- versionNameSuffix = "-free"
+ // Use only needed ABIs during development
+ splits {
+ abi {
+ isEnable = false
}
+ }
- create("paid") {
- dimension = "version"
- applicationIdSuffix = ".paid"
- versionNameSuffix = "-paid"
+ // Disable PNG crunching in debug
+ buildTypes {
+ debug {
+ isCrunchPngs = false
}
}
}
```
-Build specific flavor:
+**Incremental builds:**
```bash
-# Free debug
-./gradlew assembleFreeDebug
+# Good: Incremental build
+./gradlew assembleDebug
-# Paid release
-./gradlew assemblePaidRelease
-
-# All flavors and variants
-./gradlew assemble
+# Bad: Full rebuild (slower)
+./gradlew clean assembleDebug
```
-## Continuous Integration
+## Signing and Publishing
-### GitHub Actions Example
+### Create Signing Key
-`.github/workflows/android.yml`:
+Generate keystore:
+```bash
+keytool -genkey -v \
+ -keystore release.keystore \
+ -alias myapp-key \
+ -keyalg RSA \
+ -keysize 2048 \
+ -validity 10000
+```
+
+**Important:**
+- **NEVER commit keystore to git**
+- **Backup keystore securely**
+- **Remember passwords**
+
+View keystore details:
+```bash
+keytool -list -v -keystore release.keystore
+```
+
+### Configure Signing in Gradle
+
+**Using environment variables (recommended):**
+
+app/build.gradle.kts:
+```kotlin
+android {
+ signingConfigs {
+ create("release") {
+ storeFile = file("../release.keystore")
+ storePassword = System.getenv("KEYSTORE_PASSWORD")
+ keyAlias = "myapp-key"
+ keyPassword = System.getenv("KEY_PASSWORD")
+ }
+ }
+
+ buildTypes {
+ release {
+ signingConfig = signingConfigs.getByName("release")
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+}
+```
+
+Build with environment variables:
+```bash
+export KEYSTORE_PASSWORD="your-keystore-password"
+export KEY_PASSWORD="your-key-password"
+./gradlew bundleRelease
+```
+
+### ProGuard / R8
+
+**Enable code shrinking:**
+```kotlin
+android {
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+}
+```
+
+**proguard-rules.pro:**
+```proguard
+# Keep gomobile generated classes
+-keep class go.** { *; }
+-keep class mylib.** { *; }
+
+# Keep model classes
+-keep class com.example.myapp.models.** { *; }
+
+# Kotlin
+-keep class kotlin.** { *; }
+-keep class kotlin.Metadata { *; }
+```
+
+### Build Release AAB
+
+```bash
+./gradlew bundleRelease
+
+# Output location
+ls app/build/outputs/bundle/release/app-release.aab
+```
+
+### Verify Signature
+
+```bash
+# Check AAB signature
+jarsigner -verify -verbose app-release.aab
+
+# Check APK signature
+apksigner verify --verbose app-release.apk
+```
+
+### Test Release Build
+
+**Generate APKs from AAB (using bundletool):**
+```bash
+# Download bundletool from:
+# https://github.com/google/bundletool/releases
+
+# Generate APKs
+java -jar bundletool-all.jar build-apks \
+ --bundle=app-release.aab \
+ --output=app.apks \
+ --mode=universal
+
+# Extract universal APK
+unzip app.apks universal.apk
+
+# Install
+adb install universal.apk
+```
+
+**Test checklist:**
+- [ ] App installs successfully
+- [ ] App launches without crashes
+- [ ] All features work correctly
+- [ ] ProGuard hasn't broken anything
+- [ ] Gomobile integration works
+- [ ] Network requests succeed
+
+### Versioning
+
+**Version code (monotonically increasing):**
+```kotlin
+android {
+ defaultConfig {
+ versionCode = 1 // Increment for each release
+ versionName = "1.0"
+ }
+}
+```
+
+**Automated versioning from git:**
+```kotlin
+fun getVersionCode(): Int {
+ val process = Runtime.getRuntime().exec("git rev-list --count HEAD")
+ return process.inputStream.bufferedReader().readText().trim().toInt()
+}
+
+fun getVersionName(): String {
+ val process = Runtime.getRuntime().exec("git describe --tags --always")
+ return process.inputStream.bufferedReader().readText().trim()
+}
+
+android {
+ defaultConfig {
+ versionCode = getVersionCode()
+ versionName = getVersionName()
+ }
+}
+```
+
+### Upload to Play Console
+
+**Prerequisites:**
+1. Google Play Developer account ($25 one-time)
+2. App created in Play Console
+3. Store listing completed
+
+**Upload AAB:**
+1. Go to [Play Console](https://play.google.com/console)
+2. Select your app
+3. Production → Releases
+4. Create new release
+5. Upload AAB file
+6. Fill release notes
+7. Review and rollout
+
+**Release tracks:**
+| Track | Purpose | Audience |
+|-------|---------|----------|
+| Internal testing | Quick testing | Up to 100 testers |
+| Closed testing | Alpha/beta | Invited testers |
+| Open testing | Public beta | Anyone can join |
+| Production | Public release | All users |
+
+**Staged rollout (recommended):**
+1. Start with 5-10%
+2. Monitor crashes/issues
+3. Increase to 25%, 50%, 100%
+
+### Store Listing Requirements
+
+**Required assets:**
+- Screenshots: Minimum 2, recommended 4-8
+- Icon: 512x512 PNG
+- Feature graphic: 1024x500 PNG
+
+**Required information:**
+- App name
+- Short description (80 characters)
+- Full description (4000 characters)
+- Category
+- Content rating
+- Privacy policy URL (if app collects data)
+- Contact email
+
+### CI/CD Publishing Example
+
+**.github/workflows/release.yml:**
```yaml
-name: Android CI
+name: Release
on:
push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
+ tags:
+ - 'v*'
jobs:
- build:
+ release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -321,55 +994,54 @@ jobs:
distribution: 'temurin'
java-version: '17'
- - name: Setup Android SDK
- uses: android-actions/setup-android@v3
-
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- - name: Install gomobile
+ - name: Build gomobile AAR
run: |
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
-
- - name: Build Go library
- run: |
cd golib
gomobile bind -target=android -o mylib.aar .
cp mylib.aar ../app/libs/
- - name: Build with Gradle
- run: ./gradlew assembleDebug
+ - name: Build Release AAB
+ env:
+ KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
+ KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
+ run: |
+ echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > release.keystore
+ ./gradlew bundleRelease
- - name: Upload APK
- uses: actions/upload-artifact@v4
+ - name: Upload to Play Store
+ uses: r0adkll/upload-google-play@v1
with:
- name: app-debug
- path: app/build/outputs/apk/debug/app-debug.apk
+ serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
+ packageName: com.example.myapp
+ releaseFiles: app/build/outputs/bundle/release/app-release.aab
+ track: production
```
## Troubleshooting
-### Build fails with "SDK not found"
+### Build Issues
+**SDK not found:**
```bash
# Create local.properties
echo "sdk.dir=$ANDROID_HOME" > local.properties
```
-### OutOfMemoryError during build
-
-Increase Gradle memory in `gradle.properties`:
+**OutOfMemoryError during build:**
```properties
+# Increase Gradle memory in gradle.properties
org.gradle.jvmargs=-Xmx8192m -XX:MaxMetaspaceSize=2048m
```
-### Duplicate class errors
-
+**Duplicate class errors:**
```kotlin
-// Use packagingOptions to exclude duplicates
android {
packagingOptions {
resources {
@@ -379,55 +1051,112 @@ android {
}
```
-### AAR not found
+### Gomobile Issues
-```kotlin
-// Ensure AAR is in libs directory
+**"gomobile: command not found":**
+```bash
+# Ensure Go bin is in PATH
+export PATH=$PATH:$(go env GOPATH)/bin
+
+# Reinstall gomobile
+go install golang.org/x/mobile/cmd/gomobile@latest
+```
+
+**"NDK not found":**
+```bash
+# Set ANDROID_HOME
+export ANDROID_HOME=$HOME/Android/Sdk
+
+# Install NDK
+sdkmanager "ndk;26.1.10909125"
+
+# Reinitialize gomobile
+gomobile init
+```
+
+**"Cannot use map in function signature":**
+```go
+// Bad - maps don't bind
+func BadFunc(data map[string]string) error { ... }
+
+// Good - use struct instead
+type KeyValue struct {
+ Key string
+ Value string
+}
+func GoodFunc(data []KeyValue) error { ... }
+```
+
+**AAR is very large:**
+```bash
+# Build only needed architectures
+gomobile bind -target=android/arm64 -o mylib.aar ./golib
+```
+
+### Dependency Issues
+
+**Dependency conflicts:**
+```bash
+# View conflict resolution
+./gradlew app:dependencies
+
+# Force specific version
dependencies {
- implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
- // Or
- implementation(files("libs/mylib.aar"))
+ implementation("com.example:library:1.0") {
+ force = true
+ }
+}
+
+# Exclude transitive dependency
+dependencies {
+ implementation("com.example:library:1.0") {
+ exclude(group = "com.unwanted", module = "module")
+ }
}
```
-## Build Output
-
-### APK Structure
-
-```
-app-debug.apk
-├── AndroidManifest.xml
-├── classes.dex # Compiled Kotlin/Java code
-├── lib/ # Native libraries
-│ ├── arm64-v8a/
-│ ├── armeabi-v7a/
-│ └── x86_64/
-├── res/ # Resources
-└── resources.arsc # Compiled resources
-```
-
-### Inspect APK
-
+**Sync issues:**
```bash
-# Extract APK
-unzip app-debug.apk -d extracted
+# Refresh dependencies
+./gradlew --refresh-dependencies
-# View manifest
-aapt dump badging app-debug.apk
-
-# View method count
-# Install dex-method-counts first
-dex-method-counts app-debug.apk
+# Clear cache
+./gradlew clean cleanBuildCache
```
+### Signing Issues
+
+**"App not signed" error:**
+```bash
+# Check signing configuration
+./gradlew :app:signingReport
+
+# Verify keystore exists and credentials are correct
+```
+
+**ProGuard breaks app:**
+```proguard
+# Add keep rules in proguard-rules.pro
+-keep class com.your.package.** { *; }
+```
+
+**Upload rejected by Play Console:**
+- Version code not incremented
+- Signature mismatch
+- Missing required permissions
+- Policy violations
+
## Next Steps
-- **Install and test** - Use the **Debug** workflow
-- **Sign and publish** - Use the **Publish** workflow
-- **Run tests** - Use the **Test** workflow
+- **Debug and test** - Use the **Debug** workflow
+- **Monitor app performance** - Play Console metrics
+- **Respond to user feedback** - Reviews and ratings
+- **Plan updates** - New features and bug fixes
## Resources
- [Gradle Build Documentation](https://developer.android.com/studio/build)
-- [Configure Build Variants](https://developer.android.com/studio/build/build-variants)
-- [Shrink, Obfuscate, and Optimize](https://developer.android.com/studio/build/shrink-code)
+- [Gomobile Documentation](https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile)
+- [Publish Your App](https://developer.android.com/studio/publish)
+- [Play Console](https://play.google.com/console)
+- [App Signing](https://developer.android.com/studio/publish/app-signing)
dots/.config/claude/skills/Android/workflows/Debug.md
@@ -1,6 +1,6 @@
# Debug Workflow
-Debug Android applications using adb, logcat, and Android debugging tools.
+Debug, test, and profile Android applications using adb, emulator, logcat, and testing frameworks.
## ADB (Android Debug Bridge)
@@ -13,6 +13,12 @@ adb devices
# Connect to specific device (if multiple)
adb -s <device-id> <command>
+# Target emulator specifically
+adb -e <command>
+
+# Target physical device specifically
+adb -d <command>
+
# Check device info
adb shell getprop ro.product.model
adb shell getprop ro.build.version.release
@@ -57,6 +63,81 @@ adb shell am force-stop com.example.myapp
adb shell am kill com.example.myapp
```
+### File Operations
+
+```bash
+# Push file to device
+adb push local-file.txt /sdcard/
+
+# Pull file from device
+adb pull /sdcard/file.txt ./
+
+# List files
+adb shell ls /sdcard/
+
+# View file content
+adb shell cat /sdcard/file.txt
+
+# Remove file
+adb shell rm /sdcard/file.txt
+```
+
+### Screenshots and Screen Recording
+
+```bash
+# Take screenshot
+adb shell screencap /sdcard/screen.png
+adb pull /sdcard/screen.png
+adb shell rm /sdcard/screen.png
+
+# Or in one command
+adb exec-out screencap -p > screen.png
+
+# Record screen (max 3 minutes)
+adb shell screenrecord /sdcard/demo.mp4
+# Press Ctrl+C to stop
+adb pull /sdcard/demo.mp4
+adb shell rm /sdcard/demo.mp4
+```
+
+### System Information
+
+```bash
+# CPU info
+adb shell cat /proc/cpuinfo
+
+# Memory info
+adb shell cat /proc/meminfo
+
+# Battery status
+adb shell dumpsys battery
+
+# Display info
+adb shell dumpsys display
+
+# Running processes
+adb shell ps
+
+# App processes
+adb shell ps | grep com.example.myapp
+```
+
+### Network
+
+```bash
+# Check network connectivity
+adb shell ping -c 4 google.com
+
+# View network interfaces
+adb shell ip addr
+
+# Port forwarding (device port to local port)
+adb forward tcp:8080 tcp:8080
+
+# Reverse port forwarding (local port to device)
+adb reverse tcp:9000 tcp:9000
+```
+
## Logcat
### View Logs
@@ -151,6 +232,575 @@ adb logcat | grep "panic:"
adb logcat | grep "GoLog"
```
+## Emulator
+
+### System Images
+
+**Image Types:**
+
+| Type | Google Apps | Play Store | Use Case |
+|------|-------------|------------|----------|
+| `default` | ❌ | ❌ | AOSP, minimal |
+| `google_apis` | ✅ | ❌ | Google Maps, etc. |
+| `google_apis_playstore` | ✅ | ✅ | Full Play Services |
+
+**Install System Images:**
+
+```bash
+# List available system images
+sdkmanager --list | grep system-images
+
+# Android 14 (API 34) - x86_64 (for Intel/AMD)
+sdkmanager "system-images;android-34;google_apis;x86_64"
+
+# Android 14 (API 34) - ARM64 (for Apple Silicon)
+sdkmanager "system-images;android-34;google_apis;arm64-v8a"
+
+# Android 13 (API 33)
+sdkmanager "system-images;android-33;google_apis;x86_64"
+```
+
+**Choose architecture:**
+- Intel/AMD processors: `x86_64`
+- Apple Silicon (M1/M2/M3): `arm64-v8a`
+
+### Create AVD
+
+```bash
+# Quick start
+avdmanager create avd \
+ -n Pixel7 \
+ -k "system-images;android-34;google_apis;x86_64" \
+ -d "pixel_7"
+
+# List available device definitions
+avdmanager list device
+
+# Common devices: pixel_7, pixel_7_pro, pixel_6, pixel_5, pixel_tablet
+```
+
+**Examples:**
+
+```bash
+# Pixel 6 with Android 13
+avdmanager create avd \
+ -n Pixel6 \
+ -k "system-images;android-33;google_apis;x86_64" \
+ -d "pixel_6"
+
+# Tablet (Pixel Tablet)
+avdmanager create avd \
+ -n PixelTablet \
+ -k "system-images;android-34;google_apis;x86_64" \
+ -d "pixel_tablet"
+
+# Custom configuration
+avdmanager create avd \
+ -n CustomPhone \
+ -k "system-images;android-34;google_apis;x86_64" \
+ -d "pixel_7" \
+ -c 512M \ # SD card size
+ --force # Overwrite if exists
+```
+
+### Manage AVDs
+
+```bash
+# List all AVDs
+avdmanager list avd
+
+# List with paths
+avdmanager list avd -c
+
+# Delete AVD
+avdmanager delete avd -n Pixel7
+```
+
+### Start Emulator
+
+**Basic:**
+
+```bash
+# Start AVD
+emulator -avd Pixel7
+
+# Start in background
+emulator -avd Pixel7 &
+
+# Start with GPU acceleration
+emulator -avd Pixel7 -gpu host
+```
+
+**Common Options:**
+
+```bash
+# Wipe data (factory reset)
+emulator -avd Pixel7 -wipe-data
+
+# Read-only system
+emulator -avd Pixel7 -read-only
+
+# Increase RAM
+emulator -avd Pixel7 -memory 4096
+
+# Cold boot (ignore snapshot)
+emulator -avd Pixel7 -no-snapshot-load
+
+# No audio
+emulator -avd Pixel7 -no-audio
+
+# Maximum performance
+emulator -avd Pixel7 \
+ -gpu host \
+ -memory 4096 \
+ -cores 4 \
+ -no-boot-anim \
+ -no-snapshot
+```
+
+**Network Options:**
+
+```bash
+# Use specific DNS
+emulator -avd Pixel7 -dns-server 8.8.8.8
+
+# Network latency simulation
+emulator -avd Pixel7 -netdelay gprs # or edge, umts, none
+
+# Network speed simulation
+emulator -avd Pixel7 -netspeed full # or gsm, hscsd, gprs, edge, umts, hsdpa
+
+# Proxy
+emulator -avd Pixel7 -http-proxy http://localhost:8888
+```
+
+### Emulator Control
+
+**Via Telnet:**
+
+```bash
+# Connect to emulator console
+telnet localhost 5554 # Port shown when emulator starts
+
+# In telnet session:
+# auth <token> # Token is in ~/.emulator_console_auth_token
+# help
+# sms send 1234567890 "Test message"
+# geo fix -122.084 37.422 # Set GPS location
+# quit
+```
+
+**Via ADB:**
+
+```bash
+# Rotate screen
+adb shell settings put system accelerometer_rotation 0
+adb shell settings put system user_rotation 1 # 0=0°, 1=90°, 2=180°, 3=270°
+
+# Simulate low battery
+adb shell dumpsys battery set level 10
+
+# Reset battery
+adb shell dumpsys battery reset
+```
+
+### Multiple Emulators
+
+```bash
+# Start first emulator
+emulator -avd Pixel7 &
+
+# Start second emulator (different AVD)
+emulator -avd Pixel6 &
+
+# Install on specific emulator
+adb -s emulator-5554 install app-debug.apk
+adb -s emulator-5556 install app-debug.apk
+```
+
+**Emulator Ports:**
+- First emulator: console 5554, adb 5555
+- Second emulator: console 5556, adb 5557
+
+## Testing
+
+### Test Types
+
+| Type | Runs On | Speed | Use For |
+|------|---------|-------|---------|
+| Unit Tests | JVM | Fast | Business logic, utilities |
+| Instrumentation Tests | Android device/emulator | Slow | Android APIs, database |
+| UI Tests | Android device/emulator | Slowest | User interface, interactions |
+
+### Unit Tests (Local Tests)
+
+**Location:** `app/src/test/java/com/example/myapp/`
+
+**Dependencies:**
+
+```kotlin
+dependencies {
+ testImplementation("junit:junit:4.13.2")
+ testImplementation("org.mockito:mockito-core:5.7.0")
+ testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
+}
+```
+
+**Example:**
+
+```kotlin
+package com.example.myapp
+
+import org.junit.Test
+import org.junit.Assert.*
+
+class CalculatorTest {
+
+ @Test
+ fun addition_isCorrect() {
+ val calculator = Calculator()
+ val result = calculator.add(2, 3)
+ assertEquals(5, result)
+ }
+
+ @Test
+ fun division_byZero_throwsException() {
+ val calculator = Calculator()
+ assertThrows(ArithmeticException::class.java) {
+ calculator.divide(10, 0)
+ }
+ }
+}
+```
+
+**Run Unit Tests:**
+
+```bash
+# All unit tests
+./gradlew test
+
+# Specific variant
+./gradlew testDebug
+
+# Specific test class
+./gradlew test --tests CalculatorTest
+
+# With coverage report
+./gradlew testDebugUnitTest jacocoTestReport
+```
+
+**View Results:**
+
+```bash
+# HTML report
+open app/build/reports/tests/testDebugUnitTest/index.html
+
+# Coverage report
+open app/build/reports/jacoco/jacocoTestReport/html/index.html
+```
+
+### Instrumentation Tests
+
+**Location:** `app/src/androidTest/java/com/example/myapp/`
+
+**Dependencies:**
+
+```kotlin
+android {
+ defaultConfig {
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+}
+
+dependencies {
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test:runner:1.5.2")
+ androidTestImplementation("androidx.test:rules:1.5.0")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+}
+```
+
+**Example:**
+
+```kotlin
+package com.example.myapp
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.Assert.*
+
+@RunWith(AndroidJUnit4::class)
+class DatabaseTest {
+
+ @Test
+ fun useAppContext() {
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.myapp", appContext.packageName)
+ }
+}
+```
+
+**Run Instrumentation Tests:**
+
+```bash
+# Connect device/emulator first
+adb devices
+
+# All instrumentation tests
+./gradlew connectedAndroidTest
+
+# Specific variant
+./gradlew connectedDebugAndroidTest
+
+# Specific test
+./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.myapp.DatabaseTest
+```
+
+### UI Tests (Espresso)
+
+**Dependencies:**
+
+```kotlin
+dependencies {
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
+ androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1")
+}
+```
+
+**Example:**
+
+```kotlin
+package com.example.myapp
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.*
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.*
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MainActivityTest {
+
+ @get:Rule
+ val activityRule = ActivityScenarioRule(MainActivity::class.java)
+
+ @Test
+ fun button_click_showsText() {
+ // Type text in EditText
+ onView(withId(R.id.editText))
+ .perform(typeText("Hello"), closeSoftKeyboard())
+
+ // Click button
+ onView(withId(R.id.button))
+ .perform(click())
+
+ // Verify text is displayed
+ onView(withId(R.id.textView))
+ .check(matches(withText("Hello")))
+ }
+}
+```
+
+**Common Espresso Operations:**
+
+```kotlin
+// Find views
+onView(withId(R.id.viewId))
+onView(withText("text"))
+onView(withContentDescription("description"))
+
+// Actions
+perform(click())
+perform(typeText("text"))
+perform(replaceText("new text"))
+perform(clearText())
+perform(closeSoftKeyboard())
+perform(swipeLeft())
+perform(swipeRight())
+perform(scrollTo())
+
+// Assertions
+check(matches(isDisplayed()))
+check(matches(withText("expected")))
+check(matches(isEnabled()))
+check(matches(isChecked()))
+check(doesNotExist())
+```
+
+### Testing Gomobile Integration
+
+**Unit Test Go Code (Before binding):**
+
+```bash
+cd golib
+go test ./...
+go test -v ./...
+go test -cover ./...
+```
+
+**Test AAR Integration:**
+
+```kotlin
+package com.example.myapp
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import mylib.Mylib // Import gomobile generated package
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.Assert.*
+
+@RunWith(AndroidJUnit4::class)
+class GomobileTest {
+
+ @Test
+ fun goFunction_returnsExpectedResult() {
+ val result = Mylib.fetchURL("https://example.com")
+ assertNotNull(result)
+ assertTrue(result.isNotEmpty())
+ }
+
+ @Test
+ fun goFunction_handlesError() {
+ try {
+ Mylib.fetchURL("invalid-url")
+ fail("Should have thrown exception")
+ } catch (e: Exception) {
+ assertTrue(e.message?.contains("error") == true)
+ }
+ }
+}
+```
+
+### Code Coverage (JaCoCo)
+
+**Configure:**
+
+```kotlin
+plugins {
+ id("jacoco")
+}
+
+android {
+ buildTypes {
+ debug {
+ enableAndroidTestCoverage = true
+ enableUnitTestCoverage = true
+ }
+ }
+}
+
+tasks.register<JacocoReport>("jacocoTestReport") {
+ dependsOn("testDebugUnitTest", "createDebugCoverageReport")
+
+ reports {
+ xml.required.set(true)
+ html.required.set(true)
+ }
+
+ val fileFilter = listOf(
+ "**/R.class",
+ "**/R$*.class",
+ "**/BuildConfig.*",
+ "**/Manifest*.*"
+ )
+
+ val debugTree = fileTree("${project.buildDir}/intermediates/javac/debug") {
+ exclude(fileFilter)
+ }
+
+ val mainSrc = "${project.projectDir}/src/main/java"
+
+ sourceDirectories.setFrom(files(mainSrc))
+ classDirectories.setFrom(files(debugTree))
+ executionData.setFrom(fileTree(project.buildDir) {
+ include("jacoco/testDebugUnitTest.exec", "outputs/code_coverage/debugAndroidTest/connected/**/*.ec")
+ })
+}
+```
+
+**Run Coverage:**
+
+```bash
+./gradlew jacocoTestReport
+open app/build/reports/jacoco/jacocoTestReport/html/index.html
+```
+
+### Test Best Practices
+
+**1. Follow AAA Pattern:**
+
+```kotlin
+@Test
+fun testName() {
+ // Arrange - Set up test data
+ val calculator = Calculator()
+
+ // Act - Execute the function
+ val result = calculator.add(2, 3)
+
+ // Assert - Verify the result
+ assertEquals(5, result)
+}
+```
+
+**2. Use Descriptive Names:**
+
+```kotlin
+// Good
+@Test
+fun addition_withPositiveNumbers_returnsSum() { }
+
+@Test
+fun division_withZeroDivisor_throwsException() { }
+
+// Bad
+@Test
+fun test1() { }
+```
+
+**3. Test One Thing:**
+
+```kotlin
+// Good - separate tests
+@Test
+fun add_returnsCorrectSum() { }
+
+@Test
+fun add_handlesNegativeNumbers() { }
+```
+
+**4. Mock External Dependencies:**
+
+```kotlin
+import org.mockito.Mock
+import org.mockito.Mockito.*
+
+class UserRepositoryTest {
+
+ @Mock
+ lateinit var api: ApiService
+
+ @Test
+ fun getUser_callsApi() {
+ val userId = "123"
+ `when`(api.fetchUser(userId)).thenReturn(User("John"))
+
+ val repository = UserRepository(api)
+ val user = repository.getUser(userId)
+
+ verify(api).fetchUser(userId)
+ assertEquals("John", user.name)
+ }
+}
+```
+
## Debugging from Code
### Android (Kotlin/Java)
@@ -183,7 +833,8 @@ class MainActivity : AppCompatActivity() {
}
```
-View in logcat:
+**View in logcat:**
+
```bash
adb logcat -s MainActivity
```
@@ -198,7 +849,6 @@ import "log"
func FetchData(url string) (string, error) {
log.Printf("Fetching URL: %s", url) // Appears in logcat with GoLog tag
- // Your code
result, err := doFetch(url)
if err != nil {
@@ -211,87 +861,35 @@ func FetchData(url string) (string, error) {
}
```
-View in logcat:
+**View in logcat:**
+
```bash
adb logcat | grep GoLog
```
-## Device/Emulator Commands
+### Interactive Debugging
-### File Operations
+**Enable Debug Mode:**
-```bash
-# Push file to device
-adb push local-file.txt /sdcard/
-
-# Pull file from device
-adb pull /sdcard/file.txt ./
-
-# List files
-adb shell ls /sdcard/
-
-# View file content
-adb shell cat /sdcard/file.txt
-
-# Remove file
-adb shell rm /sdcard/file.txt
+```kotlin
+// app/build.gradle.kts
+android {
+ buildTypes {
+ debug {
+ isDebuggable = true
+ }
+ }
+}
```
-### Screenshots and Screen Recording
+**Using Android Studio Debugger:**
-```bash
-# Take screenshot
-adb shell screencap /sdcard/screen.png
-adb pull /sdcard/screen.png
-adb shell rm /sdcard/screen.png
-
-# Or in one command
-adb exec-out screencap -p > screen.png
-
-# Record screen (max 3 minutes)
-adb shell screenrecord /sdcard/demo.mp4
-# Press Ctrl+C to stop
-adb pull /sdcard/demo.mp4
-adb shell rm /sdcard/demo.mp4
-```
-
-### System Information
-
-```bash
-# CPU info
-adb shell cat /proc/cpuinfo
-
-# Memory info
-adb shell cat /proc/meminfo
-
-# Battery status
-adb shell dumpsys battery
-
-# Display info
-adb shell dumpsys display
-
-# Running processes
-adb shell ps
-
-# App processes
-adb shell ps | grep com.example.myapp
-```
-
-### Network
-
-```bash
-# Check network connectivity
-adb shell ping -c 4 google.com
-
-# View network interfaces
-adb shell ip addr
-
-# Port forwarding (device port to local port)
-adb forward tcp:8080 tcp:8080
-
-# Reverse port forwarding (local port to device)
-adb reverse tcp:9000 tcp:9000
-```
+1. Build debug APK: `./gradlew assembleDebug`
+2. Install on device: `adb install app-debug.apk`
+3. In Android Studio: Run → Attach Debugger to Android Process
+4. Select your app
+5. Set breakpoints in code
+6. Trigger the code path
## Performance Profiling
@@ -363,7 +961,8 @@ java.lang.NullPointerException: Attempt to invoke virtual method '...' on a null
### 4. Common Crash Types
-**NullPointerException**:
+**NullPointerException:**
+
```kotlin
// Bad
val user = getUser() // Returns null
@@ -374,7 +973,8 @@ val user = getUser()
val name = user?.name ?: "Unknown"
```
-**ClassCastException**:
+**ClassCastException:**
+
```kotlin
// Bad
val text = view as TextView // Crash if not TextView!
@@ -383,7 +983,8 @@ val text = view as TextView // Crash if not TextView!
val text = view as? TextView
```
-**ActivityNotFoundException**:
+**ActivityNotFoundException:**
+
```kotlin
// Check if intent can be handled
if (intent.resolveActivity(packageManager) != null) {
@@ -391,43 +992,6 @@ if (intent.resolveActivity(packageManager) != null) {
}
```
-## Interactive Debugging
-
-### Enable Debug Mode in App
-
-```kotlin
-// app/build.gradle.kts
-android {
- buildTypes {
- debug {
- isDebuggable = true
- }
- }
-}
-```
-
-### Using Android Studio Debugger
-
-1. Build debug APK: `./gradlew assembleDebug`
-2. Install on device: `adb install app-debug.apk`
-3. In Android Studio: Run → Attach Debugger to Android Process
-4. Select your app
-5. Set breakpoints in code
-6. Trigger the code path
-
-### Command-line Debugging (jdb)
-
-```bash
-# Find app process
-adb shell ps | grep com.example.myapp
-
-# Forward debug port
-adb forward tcp:8000 jdwp:<pid>
-
-# Attach jdb
-jdb -attach localhost:8000
-```
-
## Network Debugging
### HTTP Traffic Inspection
@@ -446,9 +1010,11 @@ adb reverse tcp:8888 tcp:8888
adb shell dumpsys netstats
```
-## Common Issues
+## Troubleshooting
-### "adb: device offline"
+### ADB Issues
+
+**"adb: device offline":**
```bash
adb kill-server
@@ -456,7 +1022,7 @@ adb start-server
# Reconnect device
```
-### "adb: device unauthorized"
+**"adb: device unauthorized":**
- Check device screen for authorization prompt
- Accept "Always allow from this computer"
@@ -467,7 +1033,7 @@ adb start-server
adb start-server
```
-### "Logcat shows nothing"
+**"Logcat shows nothing":**
```bash
# Clear buffer
@@ -480,21 +1046,92 @@ adb logcat -g
adb logcat -G 16M
```
-### App not showing in process list
+### Emulator Issues
+
+**Emulator won't start:**
```bash
-# Check if app is running
-adb shell "ps -A | grep com.example.myapp"
+# Check emulator location
+which emulator
-# Start app if not running
-adb shell am start -n com.example.myapp/.MainActivity
+# Run with verbose logging
+emulator -avd Pixel7 -verbose
+
+# Check for errors
+emulator -avd Pixel7 -debug init
```
-## Debug Helpers
+**Slow emulator - Enable hardware acceleration:**
-### Monitor script
+**Linux (KVM):**
+
+```bash
+# Check KVM support
+egrep -c '(vmx|svm)' /proc/cpuinfo # Should be > 0
+
+# Check KVM device
+ls -l /dev/kvm
+
+# Add user to kvm group
+sudo usermod -a -G kvm $USER
+# Log out and back in
+
+# Verify
+emulator -accel-check
+```
+
+**macOS:** Automatically uses Hypervisor Framework
+
+**Windows (HAXM):**
+
+```bash
+sdkmanager "extras;intel;Hardware_Accelerated_Execution_Manager"
+```
+
+**Graphics issues:**
+
+```bash
+# Try different GPU modes
+emulator -avd Pixel7 -gpu auto
+emulator -avd Pixel7 -gpu host
+emulator -avd Pixel7 -gpu swiftshader_indirect
+emulator -avd Pixel7 -gpu angle_indirect
+```
+
+### Test Issues
+
+**Tests fail on CI but pass locally:**
+
+- Check Android API level compatibility
+- Verify emulator configuration
+- Check for timing issues (add timeouts)
+- Ensure deterministic test data
+
+**Instrumentation tests hang:**
+
+```bash
+# Clear app data between tests
+adb shell pm clear com.example.myapp
+
+# Restart adb
+adb kill-server
+adb start-server
+```
+
+**Espresso can't find view:**
+
+```kotlin
+// Use idling resources for async operations
+import androidx.test.espresso.IdlingRegistry
+import androidx.test.espresso.IdlingResource
+```
+
+## Helper Scripts
+
+### Monitor App Script
Create `monitor-app.sh`:
+
```bash
#!/usr/bin/env bash
set -euo pipefail
@@ -509,9 +1146,10 @@ adb logcat -c
adb logcat | grep --color=always -E "$APP_PACKAGE|$LOG_TAG|FATAL|ERROR"
```
-### Install and monitor script
+### Install and Monitor Script
Create `debug-install.sh`:
+
```bash
#!/usr/bin/env bash
set -euo pipefail
@@ -531,14 +1169,120 @@ adb logcat -c
adb logcat | grep "$PACKAGE"
```
+### Start Emulator and Wait
+
+Create `start-emulator.sh`:
+
+```bash
+#!/usr/bin/env bash
+set -euo pipefail
+
+AVD_NAME="Pixel7"
+
+echo "Starting emulator: $AVD_NAME"
+emulator -avd "$AVD_NAME" -no-snapshot-save &
+
+# Wait for device to boot
+echo "Waiting for device to boot..."
+adb wait-for-device
+
+# Wait for boot animation to finish
+while [ "$(adb shell getprop init.svc.bootanim | tr -d '\r')" = "running" ]; do
+ sleep 1
+done
+
+echo "Emulator ready!"
+
+# Optional: Install app
+if [ -f "app-debug.apk" ]; then
+ echo "Installing app..."
+ adb install -r app-debug.apk
+fi
+
+echo "Done!"
+```
+
+## CI/CD Testing
+
+### GitHub Actions Example
+
+`.github/workflows/test.yml`:
+
+```yaml
+name: Android CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+
+ - name: Setup Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.22'
+
+ - name: Test Go code
+ run: |
+ cd golib
+ go test -v ./...
+
+ - name: Build gomobile AAR
+ run: |
+ go install golang.org/x/mobile/cmd/gomobile@latest
+ gomobile init
+ cd golib
+ gomobile bind -target=android -o mylib.aar .
+ cp mylib.aar ../app/libs/
+
+ - name: Run unit tests
+ run: ./gradlew test
+
+ - name: Run instrumented tests
+ uses: reactivecircus/android-emulator-runner@v2
+ with:
+ api-level: 34
+ target: google_apis
+ arch: x86_64
+ script: ./gradlew connectedCheck
+
+ - name: Upload test results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-results
+ path: app/build/reports/tests/
+
+ - name: Upload coverage
+ uses: codecov/codecov-action@v3
+ with:
+ files: app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml
+```
+
## Next Steps
-- **Run tests** - Use the **Test** workflow
-- **Profile performance** - Advanced profiling techniques
-- **Publish app** - Use the **Publish** workflow
+- **Build and publish** - Use the **Build** workflow for release builds
+- **Increase coverage** - Aim for >80% test coverage
+- **Profile performance** - Identify and fix bottlenecks
+- **Monitor crashes** - Set up crash reporting (Firebase Crashlytics)
## Resources
- [ADB Documentation](https://developer.android.com/studio/command-line/adb)
- [Logcat Command-line Tool](https://developer.android.com/studio/command-line/logcat)
- [Debug Your App](https://developer.android.com/studio/debug)
+- [Android Testing](https://developer.android.com/training/testing)
+- [Espresso Documentation](https://developer.android.com/training/testing/espresso)
+- [Emulator Documentation](https://developer.android.com/studio/run/emulator)
dots/.config/claude/skills/Android/workflows/Emulator.md
@@ -1,471 +0,0 @@
-# Emulator Workflow
-
-Create, configure, and manage Android Virtual Devices (AVDs) for testing apps without physical devices.
-
-## Quick Start
-
-```bash
-# List available system images
-sdkmanager --list | grep system-images
-
-# Install system image (API 34, x86_64, Google APIs)
-sdkmanager "system-images;android-34;google_apis;x86_64"
-
-# Create AVD
-avdmanager create avd \
- -n Pixel7 \
- -k "system-images;android-34;google_apis;x86_64" \
- -d "pixel_7"
-
-# List AVDs
-avdmanager list avd
-
-# Start emulator
-emulator -avd Pixel7
-```
-
-## System Images
-
-### Image Types
-
-| Type | Google Apps | Play Store | Use Case |
-|------|-------------|------------|----------|
-| `default` | ❌ | ❌ | AOSP, minimal |
-| `google_apis` | ✅ | ❌ | Google Maps, etc. |
-| `google_apis_playstore` | ✅ | ✅ | Full Play Services |
-
-### Common System Images
-
-```bash
-# Android 14 (API 34) - x86_64 (for Intel/AMD)
-sdkmanager "system-images;android-34;google_apis;x86_64"
-
-# Android 14 (API 34) - ARM64 (for Apple Silicon with Android Emulator for M1)
-sdkmanager "system-images;android-34;google_apis;arm64-v8a"
-
-# Android 13 (API 33)
-sdkmanager "system-images;android-33;google_apis;x86_64"
-
-# Android 12 (API 31)
-sdkmanager "system-images;android-31;google_apis;x86_64"
-```
-
-**Choose architecture based on your CPU:**
-- Intel/AMD processors: `x86_64`
-- Apple Silicon (M1/M2/M3): `arm64-v8a` (requires Android Emulator for M1+)
-
-## Create AVD
-
-### Interactive (using avdmanager)
-
-```bash
-avdmanager create avd \
- -n <name> \
- -k <system-image> \
- -d <device-definition>
-```
-
-**Examples:**
-
-```bash
-# Pixel 7 with Android 14
-avdmanager create avd \
- -n Pixel7 \
- -k "system-images;android-34;google_apis;x86_64" \
- -d "pixel_7"
-
-# Pixel 6 with Android 13
-avdmanager create avd \
- -n Pixel6 \
- -k "system-images;android-33;google_apis;x86_64" \
- -d "pixel_6"
-
-# Tablet (Pixel Tablet)
-avdmanager create avd \
- -n PixelTablet \
- -k "system-images;android-34;google_apis;x86_64" \
- -d "pixel_tablet"
-```
-
-### Custom Configuration
-
-```bash
-# Custom resolution and RAM
-avdmanager create avd \
- -n CustomPhone \
- -k "system-images;android-34;google_apis;x86_64" \
- -d "pixel_7" \
- -c 512M \ # SD card size
- --force # Overwrite if exists
-```
-
-### List Device Definitions
-
-```bash
-avdmanager list device
-```
-
-Common devices:
-- `pixel_7`, `pixel_7_pro`
-- `pixel_6`, `pixel_6_pro`
-- `pixel_5`
-- `pixel_tablet`
-
-## Manage AVDs
-
-### List AVDs
-
-```bash
-# List all AVDs
-avdmanager list avd
-
-# List with paths
-avdmanager list avd -c
-```
-
-### Delete AVD
-
-```bash
-avdmanager delete avd -n <name>
-
-# Example
-avdmanager delete avd -n Pixel7
-```
-
-### Move AVD
-
-AVDs are stored in `~/.android/avd/`. To move:
-
-```bash
-# Move AVD directory
-mv ~/.android/avd/Pixel7.avd /new/location/
-
-# Update .ini file
-echo "path=/new/location/Pixel7.avd" > ~/.android/avd/Pixel7.ini
-```
-
-## Start Emulator
-
-### Basic Start
-
-```bash
-# Start AVD
-emulator -avd Pixel7
-
-# Start in background
-emulator -avd Pixel7 &
-
-# Start with GPU acceleration
-emulator -avd Pixel7 -gpu host
-```
-
-### Start Options
-
-```bash
-# Wipe data (factory reset)
-emulator -avd Pixel7 -wipe-data
-
-# Read-only system
-emulator -avd Pixel7 -read-only
-
-# Specific resolution
-emulator -avd Pixel7 -skin 1080x1920
-
-# Increase RAM
-emulator -avd Pixel7 -memory 4096
-
-# Cold boot (ignore snapshot)
-emulator -avd Pixel7 -no-snapshot-load
-
-# No audio
-emulator -avd Pixel7 -no-audio
-```
-
-### Performance Options
-
-```bash
-# Maximum performance
-emulator -avd Pixel7 \
- -gpu host \
- -memory 4096 \
- -cores 4 \
- -no-boot-anim \
- -no-snapshot
-
-# Quick boot (use snapshot)
-emulator -avd Pixel7 -no-snapshot-save
-```
-
-### Network Options
-
-```bash
-# Use specific DNS
-emulator -avd Pixel7 -dns-server 8.8.8.8
-
-# Network latency simulation
-emulator -avd Pixel7 -netdelay gprs # or edge, umts, none
-
-# Network speed simulation
-emulator -avd Pixel7 -netspeed full # or gsm, hscsd, gprs, edge, umts, hsdpa
-
-# Proxy
-emulator -avd Pixel7 -http-proxy http://localhost:8888
-```
-
-## Emulator Control
-
-### From Command Line
-
-While emulator is running:
-
-```bash
-# Connect to emulator console
-telnet localhost 5554 # Port is shown when emulator starts
-
-# In telnet session:
-# auth <token> # Token is in ~/.emulator_console_auth_token
-# help
-# sms send 1234567890 "Test message"
-# geo fix -122.084 37.422 # Set GPS location
-# quit
-```
-
-### Using ADB
-
-```bash
-# List running emulators
-adb devices
-
-# Install app
-adb -e install app-debug.apk # -e targets emulator
-
-# Rotate screen
-adb shell settings put system accelerometer_rotation 0
-adb shell settings put system user_rotation 1 # 0=0°, 1=90°, 2=180°, 3=270°
-
-# Simulate low battery
-adb shell dumpsys battery set level 10
-
-# Reset battery
-adb shell dumpsys battery reset
-```
-
-## Configuration Files
-
-### config.ini
-
-Located at `~/.android/avd/<name>.avd/config.ini`
-
-**Common settings:**
-```ini
-# RAM
-hw.ramSize=4096
-
-# Storage
-disk.dataPartition.size=8G
-
-# GPU
-hw.gpu.enabled=yes
-hw.gpu.mode=host
-
-# Camera
-hw.camera.back=emulated
-hw.camera.front=emulated
-
-# Keyboard
-hw.keyboard=yes
-
-# Network
-hw.net.speed=full
-hw.net.delay=none
-```
-
-## Snapshots
-
-### Quick Boot (Snapshots)
-
-Emulator saves state for faster startup.
-
-```bash
-# Start without loading snapshot (cold boot)
-emulator -avd Pixel7 -no-snapshot-load
-
-# Start without saving snapshot (testing)
-emulator -avd Pixel7 -no-snapshot-save
-
-# Disable snapshots completely
-emulator -avd Pixel7 -no-snapshot
-```
-
-### Manage Snapshots
-
-```bash
-# List snapshots
-emulator -avd Pixel7 -list-snapshots
-
-# Load specific snapshot
-emulator -avd Pixel7 -snapshot <name>
-```
-
-## Troubleshooting
-
-### Emulator won't start
-
-```bash
-# Check emulator location
-which emulator
-
-# Run with verbose logging
-emulator -avd Pixel7 -verbose
-
-# Check for errors
-emulator -avd Pixel7 -debug init
-```
-
-### Slow emulator
-
-**Enable hardware acceleration:**
-
-**Linux (KVM):**
-```bash
-# Check KVM support
-egrep -c '(vmx|svm)' /proc/cpuinfo # Should be > 0
-
-# Check KVM device
-ls -l /dev/kvm
-
-# Add user to kvm group
-sudo usermod -a -G kvm $USER
-# Log out and back in
-
-# Verify
-emulator -accel-check
-```
-
-**macOS (Hypervisor Framework):**
-Automatically used on modern macOS.
-
-**Windows (HAXM):**
-Install Intel HAXM:
-```bash
-sdkmanager "extras;intel;Hardware_Accelerated_Execution_Manager"
-```
-
-### "PANIC: Cannot find AVD system path"
-
-```bash
-# Check ANDROID_AVD_HOME
-echo $ANDROID_AVD_HOME
-
-# Set if needed
-export ANDROID_AVD_HOME=$HOME/.android/avd
-```
-
-### Graphics issues
-
-```bash
-# Try different GPU modes
-emulator -avd Pixel7 -gpu auto
-emulator -avd Pixel7 -gpu host
-emulator -avd Pixel7 -gpu swiftshader_indirect
-emulator -avd Pixel7 -gpu angle_indirect
-```
-
-## Multiple Emulators
-
-### Run Multiple Instances
-
-```bash
-# Start first emulator
-emulator -avd Pixel7 &
-
-# Start second emulator (different AVD)
-emulator -avd Pixel6 &
-
-# Install on specific emulator
-adb -s emulator-5554 install app-debug.apk
-adb -s emulator-5556 install app-debug.apk
-```
-
-### Emulator Ports
-
-- First emulator: console port 5554, adb port 5555
-- Second emulator: console port 5556, adb port 5557
-- And so on...
-
-## Automation Scripts
-
-### Start emulator and wait for boot
-
-```bash
-#!/usr/bin/env bash
-set -euo pipefail
-
-AVD_NAME="Pixel7"
-
-echo "Starting emulator: $AVD_NAME"
-emulator -avd "$AVD_NAME" -no-snapshot-save &
-
-# Wait for device to boot
-echo "Waiting for device to boot..."
-adb wait-for-device
-
-# Wait for boot animation to finish
-while [ "$(adb shell getprop init.svc.bootanim | tr -d '\r')" = "running" ]; do
- sleep 1
-done
-
-echo "Emulator ready!"
-
-# Optional: Install app
-if [ -f "app-debug.apk" ]; then
- echo "Installing app..."
- adb install -r app-debug.apk
-fi
-
-echo "Done!"
-```
-
-### Create standard test emulator
-
-```bash
-#!/usr/bin/env bash
-set -euo pipefail
-
-AVD_NAME="TestDevice"
-SYSTEM_IMAGE="system-images;android-34;google_apis;x86_64"
-
-# Install system image if not already installed
-sdkmanager "$SYSTEM_IMAGE"
-
-# Create AVD (force overwrite if exists)
-avdmanager create avd \
- -n "$AVD_NAME" \
- -k "$SYSTEM_IMAGE" \
- -d "pixel_7" \
- --force
-
-echo "AVD '$AVD_NAME' created successfully"
-echo "Start with: emulator -avd $AVD_NAME"
-```
-
-## Best Practices
-
-1. **Use hardware acceleration**: Essential for acceptable performance
-2. **Limit RAM**: Don't exceed 50% of your system RAM
-3. **Close when not in use**: Emulators consume significant resources
-4. **Use snapshots**: Enable quick boot for faster startup
-5. **Match target devices**: Create AVDs matching your target devices
-6. **Test on real devices**: Emulators don't perfectly match real hardware
-7. **Use x86_64 images**: Faster than ARM on x86 computers (except Apple Silicon)
-
-## Next Steps
-
-- **Install and test app** - Use the **Build** and **Debug** workflows
-- **Run automated tests** - Use the **Test** workflow
-
-## Resources
-
-- [AVD Manager Documentation](https://developer.android.com/studio/command-line/avdmanager)
-- [Emulator Documentation](https://developer.android.com/studio/run/emulator)
-- [Configure Hardware Acceleration](https://developer.android.com/studio/run/emulator-acceleration)
dots/.config/claude/skills/Android/workflows/GomobileApp.md
@@ -1,670 +0,0 @@
-# GomobileApp Workflow
-
-Build standalone Android applications entirely in Go using `gomobile build`. This creates a complete APK with Go code as the main application logic.
-
-## When to Use
-
-- You want to write the entire app in Go
-- You're building a simple utility or demo app
-- You want to prototype quickly without Java/Kotlin
-- You're comfortable with limited UI capabilities
-- You want to share code between CLI and mobile versions
-
-## When NOT to Use
-
-- You need complex native Android UI (use GomobileBind + Kotlin instead)
-- You need full access to Android framework APIs
-- You're building a production app with rich UI
-- You need Material Design components
-
-## Prerequisites
-
-- Go installed (`go version`)
-- Android SDK and NDK installed
-- `ANDROID_HOME` environment variable set
-- Gomobile installed (`gomobile version`)
-
-## Steps
-
-### 1. Install and Initialize Gomobile
-
-```bash
-# Install gomobile
-go install golang.org/x/mobile/cmd/gomobile@latest
-
-# Initialize (downloads Android toolchain)
-gomobile init
-
-# Verify
-gomobile version
-```
-
-### 2. Create Go Mobile App
-
-**Project structure:**
-```
-myapp/
-├── main.go
-├── go.mod
-└── assets/ # Optional: app resources
- └── icon.png
-```
-
-**Basic app (`main.go`):**
-```go
-package main
-
-import (
- "log"
-
- "golang.org/x/mobile/app"
- "golang.org/x/mobile/event/lifecycle"
- "golang.org/x/mobile/event/paint"
- "golang.org/x/mobile/event/size"
- "golang.org/x/mobile/event/touch"
- "golang.org/x/mobile/gl"
-)
-
-func main() {
- app.Main(func(a app.App) {
- var glctx gl.Context
- var sz size.Event
-
- for e := range a.Events() {
- switch e := a.Filter(e).(type) {
- case lifecycle.Event:
- switch e.Crosses(lifecycle.StageVisible) {
- case lifecycle.CrossOn:
- glctx, _ = e.DrawContext.(gl.Context)
- onStart(glctx)
- case lifecycle.CrossOff:
- onStop(glctx)
- glctx = nil
- }
-
- case size.Event:
- sz = e
-
- case paint.Event:
- if glctx == nil || e.External {
- continue
- }
- onPaint(glctx, sz)
- a.Publish()
- a.Send(paint.Event{}) // Keep animating
-
- case touch.Event:
- // Handle touch events
- log.Printf("Touch: %v", e)
- }
- }
- })
-}
-
-func onStart(glctx gl.Context) {
- log.Println("App started")
- // Initialize OpenGL resources
-}
-
-func onStop(glctx gl.Context) {
- log.Println("App stopped")
- // Clean up resources
-}
-
-func onPaint(glctx gl.Context, sz size.Event) {
- // Clear screen
- glctx.ClearColor(0.2, 0.2, 0.3, 1.0)
- glctx.Clear(gl.COLOR_BUFFER_BIT)
-
- // Draw your content here
-}
-```
-
-**Simple text-based app (no OpenGL):**
-```go
-package main
-
-import (
- "fmt"
- "log"
-
- "golang.org/x/mobile/app"
- "golang.org/x/mobile/event/lifecycle"
- "golang.org/x/mobile/event/touch"
-)
-
-func main() {
- app.Main(func(a app.App) {
- for e := range a.Events() {
- switch e := a.Filter(e).(type) {
- case lifecycle.Event:
- switch e.Crosses(lifecycle.StageAlive) {
- case lifecycle.CrossOn:
- log.Println("App is alive")
- // Do work here
- processData()
- case lifecycle.CrossOff:
- log.Println("App is closing")
- }
-
- case touch.Event:
- if e.Type == touch.TypeBegin {
- log.Printf("Touch at: %v, %v", e.X, e.Y)
- }
- }
- }
- })
-}
-
-func processData() {
- // Your app logic
- fmt.Println("Processing...")
-}
-```
-
-**Practical example: Network utility**
-```go
-package main
-
-import (
- "fmt"
- "io"
- "log"
- "net/http"
- "time"
-
- "golang.org/x/mobile/app"
- "golang.org/x/mobile/event/lifecycle"
-)
-
-func main() {
- app.Main(func(a app.App) {
- for e := range a.Events() {
- switch e := a.Filter(e).(type) {
- case lifecycle.Event:
- if e.Crosses(lifecycle.StageAlive) == lifecycle.CrossOn {
- go runNetworkCheck()
- }
- }
- }
- })
-}
-
-func runNetworkCheck() {
- ticker := time.NewTicker(5 * time.Second)
- defer ticker.Stop()
-
- for range ticker.C {
- resp, err := http.Get("https://www.google.com")
- if err != nil {
- log.Printf("Network check failed: %v", err)
- continue
- }
- io.Copy(io.Discard, resp.Body)
- resp.Body.Close()
-
- log.Printf("Network OK: %s", resp.Status)
- }
-}
-```
-
-### 3. Create go.mod
-
-```bash
-go mod init example.com/myapp
-go get golang.org/x/mobile/app
-go get golang.org/x/mobile/event/lifecycle
-go mod tidy
-```
-
-### 4. Build APK
-
-**Basic build:**
-```bash
-gomobile build -target=android .
-```
-
-This creates `myapp.apk` in the current directory.
-
-**Build with custom app ID:**
-```bash
-gomobile build -target=android -appid=com.example.myapp .
-```
-
-**Build for specific architectures:**
-```bash
-# ARM64 only (smaller APK)
-gomobile build -target=android/arm64 -appid=com.example.myapp .
-
-# ARM64 and ARM32
-gomobile build -target=android/arm64,android/arm -appid=com.example.myapp .
-```
-
-**Build with custom icon:**
-```bash
-# Place icon at assets/icon.png
-gomobile build -target=android -icon=assets/icon.png .
-```
-
-**Build with version:**
-```bash
-gomobile build \
- -target=android \
- -appid=com.example.myapp \
- -ldflags="-X main.version=1.0.0" \
- .
-```
-
-### 5. Install and Run
-
-**Install on connected device:**
-```bash
-# Build and install
-gomobile install -target=android -appid=com.example.myapp .
-
-# Or build then install separately
-gomobile build -target=android -appid=com.example.myapp .
-adb install -r myapp.apk
-```
-
-**View logs:**
-```bash
-adb logcat | grep GoLog
-```
-
-### 6. Test the App
-
-**List installed apps:**
-```bash
-adb shell pm list packages | grep myapp
-```
-
-**Launch app:**
-```bash
-adb shell am start -n com.example.myapp/.MainActivity
-```
-
-**Stop app:**
-```bash
-adb shell am force-stop com.example.myapp
-```
-
-**Uninstall:**
-```bash
-adb uninstall com.example.myapp
-```
-
-## App Package Structure
-
-The gomobile app package provides:
-
-### Event Types
-
-```go
-import "golang.org/x/mobile/event"
-
-// Lifecycle events
-lifecycle.Event // App start, stop, pause, resume
-
-// Touch events
-touch.Event // Touch screen interactions
-touch.TypeBegin // Finger down
-touch.TypeMove // Finger dragging
-touch.TypeEnd // Finger up
-
-// Paint events
-paint.Event // Redraw requests
-
-// Size events
-size.Event // Screen size, orientation changes
-
-// Key events
-key.Event // Keyboard input
-```
-
-### GL Context
-
-```go
-import "golang.org/x/mobile/gl"
-
-// OpenGL ES 2.0 context
-glctx gl.Context
-
-// Common operations
-glctx.ClearColor(r, g, b, a float32)
-glctx.Clear(gl.COLOR_BUFFER_BIT)
-glctx.Enable(gl.BLEND)
-```
-
-## Advanced Examples
-
-### HTTP Server App
-
-```go
-package main
-
-import (
- "fmt"
- "log"
- "net/http"
-
- "golang.org/x/mobile/app"
- "golang.org/x/mobile/event/lifecycle"
-)
-
-func main() {
- app.Main(func(a app.App) {
- var server *http.Server
-
- for e := range a.Events() {
- switch e := a.Filter(e).(type) {
- case lifecycle.Event:
- switch e.Crosses(lifecycle.StageAlive) {
- case lifecycle.CrossOn:
- server = startServer()
- case lifecycle.CrossOff:
- if server != nil {
- server.Close()
- }
- }
- }
- }
- })
-}
-
-func startServer() *http.Server {
- mux := http.NewServeMux()
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "Hello from Go on Android!")
- })
-
- server := &http.Server{
- Addr: ":8080",
- Handler: mux,
- }
-
- go func() {
- log.Println("Starting server on :8080")
- if err := server.ListenAndServe(); err != http.ErrServerClosed {
- log.Printf("Server error: %v", err)
- }
- }()
-
- return server
-}
-```
-
-### File Operations
-
-```go
-package main
-
-import (
- "log"
- "os"
- "path/filepath"
-
- "golang.org/x/mobile/app"
- "golang.org/x/mobile/event/lifecycle"
-)
-
-func main() {
- app.Main(func(a app.App) {
- for e := range a.Events() {
- switch e := a.Filter(e).(type) {
- case lifecycle.Event:
- if e.Crosses(lifecycle.StageAlive) == lifecycle.CrossOn {
- workWithFiles()
- }
- }
- }
- })
-}
-
-func workWithFiles() {
- // Get app data directory
- dataDir, err := os.UserConfigDir()
- if err != nil {
- log.Printf("Error getting data dir: %v", err)
- return
- }
-
- // Create file
- filePath := filepath.Join(dataDir, "mydata.txt")
- err = os.WriteFile(filePath, []byte("Hello, Android!"), 0644)
- if err != nil {
- log.Printf("Error writing file: %v", err)
- return
- }
-
- // Read file
- data, err := os.ReadFile(filePath)
- if err != nil {
- log.Printf("Error reading file: %v", err)
- return
- }
-
- log.Printf("File content: %s", string(data))
-}
-```
-
-## Customization
-
-### AndroidManifest.xml
-
-Gomobile generates AndroidManifest.xml automatically. To customize:
-
-1. **Build APK first:**
- ```bash
- gomobile build -target=android -appid=com.example.myapp .
- ```
-
-2. **Extract APK:**
- ```bash
- unzip myapp.apk -d myapp-extracted
- ```
-
-3. **View manifest:**
- ```bash
- cat myapp-extracted/AndroidManifest.xml
- ```
-
-4. **For advanced customization**, use gomobile bind instead and create a full Android project.
-
-### Permissions
-
-Gomobile apps have minimal permissions by default. For additional permissions, you'll need to:
-
-1. Use gomobile bind to create AAR
-2. Create full Android project
-3. Add permissions in AndroidManifest.xml
-
-## Limitations
-
-### UI Limitations
-
-- ❌ No native Android UI widgets (Button, TextView, etc.)
-- ❌ No Material Design components
-- ❌ Limited text rendering capabilities
-- ✅ OpenGL ES 2.0 for custom graphics
-- ✅ Touch event handling
-- ✅ Basic drawing and animations
-
-### Platform Limitations
-
-- ❌ No direct access to Android framework APIs
-- ❌ Can't use Java/Kotlin libraries
-- ❌ No system services (Camera, GPS, etc.)
-- ✅ Network access (HTTP, TCP, UDP)
-- ✅ File I/O
-- ✅ Concurrency with goroutines
-
-### When to Use gomobile build vs gomobile bind
-
-| Feature | gomobile build | gomobile bind |
-|---------|----------------|---------------|
-| UI | OpenGL only | Full Android UI |
-| Android APIs | Limited | Full access |
-| Development speed | Fast | Medium |
-| Code sharing | Easy | Moderate |
-| Production apps | Demos/utilities | Production ready |
-| Learning curve | Low | Medium |
-
-## Build Automation
-
-**build.sh script:**
-```bash
-#!/usr/bin/env bash
-set -euo pipefail
-
-APP_ID="com.example.myapp"
-VERSION="1.0.0"
-TARGET="android/arm64,android/arm"
-
-echo "Building Android app..."
-
-# Build APK
-gomobile build \
- -target="$TARGET" \
- -appid="$APP_ID" \
- -ldflags="-X main.version=$VERSION" \
- .
-
-echo "APK built: myapp.apk"
-
-# Optional: Install on device
-if adb devices | grep -q "device$"; then
- echo "Installing on device..."
- adb install -r myapp.apk
- echo "Launch with: adb shell am start -n $APP_ID/.MainActivity"
-else
- echo "No device connected. Install with: adb install myapp.apk"
-fi
-```
-
-## Debugging
-
-### View Logs
-
-```bash
-# All logs
-adb logcat
-
-# Go logs only
-adb logcat | grep GoLog
-
-# Your app logs
-adb logcat | grep "com.example.myapp"
-
-# Clear logs first
-adb logcat -c && adb logcat
-```
-
-### Add Debug Logging
-
-```go
-import "log"
-
-func main() {
- log.SetPrefix("[MyApp] ")
- log.Println("App starting...")
-
- // Your code
-}
-```
-
-### Check App Status
-
-```bash
-# Check if app is running
-adb shell ps | grep myapp
-
-# Check app info
-adb shell dumpsys package com.example.myapp
-```
-
-## Performance Tips
-
-1. **Use goroutines wisely**: They're not free, even in Go
-2. **Minimize allocations**: GC pauses affect frame rate
-3. **Use buffered channels**: Reduce goroutine blocking
-4. **Profile your code**: Use pprof before deploying
-5. **Test on real devices**: Emulators don't show real performance
-
-## Common Issues
-
-### Issue: "No Android devices detected"
-
-```bash
-# Check connected devices
-adb devices
-
-# Restart adb if needed
-adb kill-server
-adb start-server
-adb devices
-```
-
-### Issue: "Installation failed"
-
-```bash
-# Uninstall old version first
-adb uninstall com.example.myapp
-
-# Reinstall
-adb install myapp.apk
-```
-
-### Issue: "App crashes immediately"
-
-```bash
-# View crash logs
-adb logcat | grep FATAL
-
-# Check for panic
-adb logcat | grep "panic:"
-```
-
-## Migration Path
-
-### From gomobile build to gomobile bind
-
-When your app outgrows gomobile build:
-
-1. **Extract core logic to library:**
- ```go
- // In mylib/network.go
- package mylib
-
- func FetchData(url string) (string, error) {
- // Your logic
- }
- ```
-
-2. **Build AAR:**
- ```bash
- gomobile bind -target=android -o mylib.aar ./mylib
- ```
-
-3. **Create Android project** (see **Build** workflow)
-
-4. **Use AAR from Kotlin:**
- ```kotlin
- import mylib.Mylib
-
- val data = Mylib.fetchData("https://example.com")
- ```
-
-## Next Steps
-
-- **Debug your app** - Use the **Debug** workflow
-- **Test on emulator** - Use the **Emulator** workflow
-- **Build production APK** - Use the **Build** and **Publish** workflows
-- **Add richer UI** - Consider migrating to gomobile bind + Android project
-
-## Resources
-
-- [Gomobile Documentation](https://pkg.go.dev/golang.org/x/mobile)
-- [Mobile App Package](https://pkg.go.dev/golang.org/x/mobile/app)
-- [Mobile GL Package](https://pkg.go.dev/golang.org/x/mobile/gl)
-- [Gomobile Examples](https://github.com/golang/mobile/tree/master/example)
dots/.config/claude/skills/Android/workflows/GomobileBind.md
@@ -1,485 +0,0 @@
-# GomobileBind Workflow
-
-Build Go libraries as Android AAR (Android Archive) packages using gomobile bind. This allows you to use Go code in Android apps written in Java or Kotlin.
-
-## When to Use
-
-- You have existing Go code (networking, crypto, business logic)
-- You want to integrate Go into an existing Android app
-- You need Go's concurrency or standard library in Android
-- You want to share code between backend and mobile
-
-## Prerequisites
-
-- Go installed (`go version`)
-- Android SDK and NDK installed
-- `ANDROID_HOME` environment variable set
-- Write access to the Go package you want to bind
-
-## Steps
-
-### 1. Verify Environment
-
-Check that required tools are available:
-
-```bash
-# Check Go
-go version
-
-# Check Android SDK
-echo $ANDROID_HOME
-ls $ANDROID_HOME/ndk
-
-# Check PATH includes Android tools
-which adb
-```
-
-If Android SDK is not set up, invoke the **Setup** workflow first.
-
-### 2. Install Gomobile
-
-```bash
-# Install gomobile command
-go install golang.org/x/mobile/cmd/gomobile@latest
-
-# Initialize gomobile (downloads Android toolchain)
-gomobile init
-
-# Verify installation
-gomobile version
-```
-
-**Note**: `gomobile init` downloads NDK toolchain and can take several minutes on first run.
-
-### 3. Prepare Go Package
-
-Your Go package must follow gomobile binding rules:
-
-**Good Go code for binding:**
-```go
-// File: golib/network.go
-package network
-
-import (
- "fmt"
- "net/http"
-)
-
-// FetchURL fetches content from a URL
-// Exported function will be available in Android
-func FetchURL(url string) (string, error) {
- resp, err := http.Get(url)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
-
- // Implementation...
- return "content", nil
-}
-
-// Config represents network configuration
-// Exported struct becomes Android class
-type Config struct {
- Timeout int
- BaseURL string
-}
-
-// NewConfig creates a new Config
-func NewConfig(timeout int, baseURL string) *Config {
- return &Config{
- Timeout: timeout,
- BaseURL: baseURL,
- }
-}
-
-// GetTimeout returns the timeout value
-func (c *Config) GetTimeout() int {
- return c.Timeout
-}
-
-// Callback interface for async operations
-type Callback interface {
- OnSuccess(data string)
- OnError(err error)
-}
-
-// FetchAsync fetches URL asynchronously
-func FetchAsync(url string, callback Callback) {
- go func() {
- result, err := FetchURL(url)
- if err != nil {
- callback.OnError(err)
- return
- }
- callback.OnSuccess(result)
- }()
-}
-```
-
-**What works in gomobile:**
-- ✅ Exported functions with basic types
-- ✅ Exported structs with exported fields
-- ✅ Methods on exported structs
-- ✅ Interfaces (for callbacks)
-- ✅ Error return values (become exceptions)
-- ✅ Slices of basic types (`[]byte`, `[]int`, `[]string`)
-
-**What doesn't work:**
-- ❌ Maps (use structs instead)
-- ❌ Channels (use callbacks via interfaces)
-- ❌ Generics (Go 1.18+ type parameters)
-- ❌ Unexported types in function signatures
-- ❌ Variadic functions
-- ❌ Complex nested types
-
-### 4. Create go.mod
-
-```bash
-cd golib
-go mod init example.com/mylib
-go mod tidy
-```
-
-### 5. Build AAR
-
-**Basic build (all architectures):**
-```bash
-gomobile bind -target=android -o mylib.aar ./golib
-```
-
-**Build specific architectures (smaller AAR):**
-```bash
-# ARM64 only (most modern devices)
-gomobile bind -target=android/arm64 -o mylib.aar ./golib
-
-# ARM64 and ARM32
-gomobile bind -target=android/arm64,android/arm -o mylib.aar ./golib
-
-# All common architectures
-gomobile bind -target=android/arm64,android/arm,android/amd64,android/386 -o mylib.aar ./golib
-```
-
-**Build with javadoc comments:**
-```bash
-gomobile bind -target=android -javapkg=com.example.mylib -o mylib.aar ./golib
-```
-
-**Architecture options:**
-- `android/arm64`: 64-bit ARM (arm64-v8a) - modern devices
-- `android/arm`: 32-bit ARM (armeabi-v7a) - older devices
-- `android/amd64`: 64-bit x86 (x86_64) - emulators
-- `android/386`: 32-bit x86 (x86) - old emulators
-
-### 6. Integrate AAR into Android Project
-
-**Copy AAR to Android project:**
-```bash
-cp mylib.aar /path/to/android-project/app/libs/
-```
-
-**Add dependency in `app/build.gradle.kts`:**
-```kotlin
-dependencies {
- implementation(files("libs/mylib.aar"))
-
- // Other dependencies...
-}
-```
-
-**Sync Gradle:**
-```bash
-cd /path/to/android-project
-./gradlew sync
-```
-
-### 7. Use in Android Code
-
-**Kotlin example:**
-```kotlin
-package com.example.myapp
-
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatActivity
-import golib.Network // Import generated package
-
-class MainActivity : AppCompatActivity() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- // Call Go function
- try {
- val result = Network.fetchURL("https://example.com")
- println("Result: $result")
- } catch (e: Exception) {
- println("Error: ${e.message}")
- }
-
- // Use Go struct
- val config = Network.newConfig(30, "https://api.example.com")
- println("Timeout: ${config.timeout}")
-
- // Async with callback
- Network.fetchAsync("https://example.com", object : Network.Callback {
- override fun onSuccess(data: String) {
- println("Success: $data")
- }
-
- override fun onError(err: Exception) {
- println("Error: ${err.message}")
- }
- })
- }
-}
-```
-
-**Java example:**
-```java
-package com.example.myapp;
-
-import golib.Network;
-
-public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- try {
- String result = Network.fetchURL("https://example.com");
- System.out.println("Result: " + result);
- } catch (Exception e) {
- System.out.println("Error: " + e.getMessage());
- }
-
- Network.Config config = Network.newConfig(30, "https://api.example.com");
- System.out.println("Timeout: " + config.getTimeout());
- }
-}
-```
-
-## Type Conversion Reference
-
-| Go Type | Java/Kotlin Type | Notes |
-|---------|------------------|-------|
-| `bool` | `boolean` / `Boolean` | |
-| `int`, `int32` | `int` / `Int` | 32-bit signed |
-| `int64` | `long` / `Long` | 64-bit signed |
-| `float32` | `float` / `Float` | |
-| `float64` | `double` / `Double` | |
-| `string` | `String` | UTF-8 encoded |
-| `[]byte` | `byte[]` / `ByteArray` | |
-| `[]int` | `long[]` / `LongArray` | Note: int becomes long[] |
-| `error` | `Exception` | Go errors become Java exceptions |
-| `struct` | `class` | Exported fields become getters/setters |
-| `interface` | `interface` | Must have exported methods only |
-
-## Optimization Tips
-
-### 1. Minimize Boundary Crossings
-
-Each call from Java/Kotlin to Go has overhead. Batch operations when possible:
-
-**Bad (many crossings):**
-```go
-func ProcessItem(item string) string { ... }
-
-// Called 1000 times from Android
-for item in items {
- ProcessItem(item) // 1000 Go calls
-}
-```
-
-**Good (single crossing):**
-```go
-func ProcessItems(items []string) []string { ... }
-
-// Called once from Android
-ProcessItems(items) // 1 Go call
-```
-
-### 2. Use Appropriate Architectures
-
-**For development (faster builds):**
-```bash
-gomobile bind -target=android/arm64 -o mylib.aar ./golib
-```
-
-**For release (wider compatibility):**
-```bash
-gomobile bind -target=android/arm64,android/arm -o mylib.aar ./golib
-```
-
-### 3. Keep Go API Simple
-
-- Flat function signatures (avoid nested types)
-- Use basic types when possible
-- Return errors as second value
-- Document expected behavior
-
-## Common Issues
-
-### Issue: "gomobile: command not found"
-
-**Solution:**
-```bash
-# Ensure Go bin is in PATH
-export PATH=$PATH:$(go env GOPATH)/bin
-
-# Reinstall gomobile
-go install golang.org/x/mobile/cmd/gomobile@latest
-```
-
-### Issue: "NDK not found"
-
-**Solution:**
-```bash
-# Set ANDROID_HOME
-export ANDROID_HOME=$HOME/Android/Sdk
-
-# Install NDK via sdkmanager
-sdkmanager --install "ndk;26.1.10909125"
-
-# Reinitialize gomobile
-gomobile init
-```
-
-### Issue: "Cannot use map in function signature"
-
-**Problem:**
-```go
-func BadFunc(data map[string]string) error { ... } // Won't bind!
-```
-
-**Solution:** Use struct instead:
-```go
-type KeyValue struct {
- Key string
- Value string
-}
-
-func GoodFunc(data []KeyValue) error { ... } // Works!
-```
-
-### Issue: AAR is very large
-
-**Problem:** All architectures included by default
-
-**Solution:** Build only needed architectures:
-```bash
-# Just ARM64 (most devices)
-gomobile bind -target=android/arm64 -o mylib.aar ./golib
-
-# ARM64 + ARM (smaller than all 4)
-gomobile bind -target=android/arm64,android/arm -o mylib.aar ./golib
-```
-
-## Testing Go Code
-
-Test Go code separately before binding:
-
-```bash
-cd golib
-go test ./...
-go test -v ./...
-go test -cover ./...
-```
-
-## Automation Script
-
-Create `build-aar.sh` for consistent builds:
-
-```bash
-#!/usr/bin/env bash
-set -euo pipefail
-
-# Build AAR for Android
-echo "Building AAR..."
-
-# Set targets (can be overridden)
-TARGETS=${ANDROID_TARGETS:-"android/arm64,android/arm"}
-OUTPUT=${AAR_OUTPUT:-"mylib.aar"}
-PACKAGE=${GO_PACKAGE:-"./golib"}
-
-gomobile bind \
- -target="$TARGETS" \
- -o "$OUTPUT" \
- "$PACKAGE"
-
-echo "AAR built successfully: $OUTPUT"
-
-# Copy to Android project if path is set
-if [ -n "${ANDROID_PROJECT:-}" ]; then
- cp "$OUTPUT" "$ANDROID_PROJECT/app/libs/"
- echo "Copied to Android project"
-fi
-```
-
-Usage:
-```bash
-chmod +x build-aar.sh
-
-# Build for ARM64 only
-ANDROID_TARGETS=android/arm64 ./build-aar.sh
-
-# Build and copy to Android project
-ANDROID_PROJECT=/path/to/android-app ./build-aar.sh
-```
-
-## Debugging Tips
-
-### 1. Check Generated Java Code
-
-Extract AAR to inspect generated Java:
-
-```bash
-unzip mylib.aar -d mylib-extracted
-cd mylib-extracted
-javap -p classes.jar # View Java classes
-```
-
-### 2. Add Logging in Go
-
-Use Android's logging system from Go:
-
-```go
-package mylib
-
-import "log"
-
-func FetchURL(url string) (string, error) {
- log.Printf("Fetching URL: %s", url) // Will appear in logcat
- // ...
-}
-```
-
-View in logcat:
-```bash
-adb logcat | grep GoLog
-```
-
-### 3. Test Type Conversions
-
-Create simple test functions to verify type conversion:
-
-```go
-func TestString(s string) string { return s }
-func TestInt(i int) int { return i }
-func TestBytes(b []byte) []byte { return b }
-```
-
-## Next Steps
-
-After successfully binding your Go library:
-
-1. **Build the Android app** - Use the **Build** workflow
-2. **Test on device/emulator** - Use the **Debug** workflow
-3. **Optimize AAR size** - Build only needed architectures
-4. **Add tests** - Use the **Test** workflow
-5. **Publish app** - Use the **Publish** workflow
-
-## Resources
-
-- [Gomobile Documentation](https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile)
-- [Gomobile Wiki](https://github.com/golang/go/wiki/Mobile)
-- [Gomobile Examples](https://github.com/golang/mobile/tree/master/example)
-- [Go on Mobile Tutorial](https://github.com/golang/mobile/blob/master/README.md)
dots/.config/claude/skills/Android/workflows/Gradle.md
@@ -1,507 +0,0 @@
-# Gradle Workflow
-
-Manage Android project dependencies, build configuration, and Gradle tasks.
-
-## Gradle Basics
-
-### Gradle Wrapper
-
-Always use the wrapper for reproducible builds:
-
-```bash
-# Linux/Mac
-./gradlew <task>
-
-# Windows
-gradlew.bat <task>
-
-# Update wrapper
-./gradlew wrapper --gradle-version=8.6
-```
-
-### Project Structure
-
-```
-myproject/
-├── gradle/
-│ ├── libs.versions.toml # Version catalog (recommended)
-│ └── wrapper/
-│ ├── gradle-wrapper.jar
-│ └── gradle-wrapper.properties
-├── app/
-│ └── build.gradle.kts # App module build file
-├── build.gradle.kts # Root build file
-├── settings.gradle.kts # Project settings
-├── gradle.properties # Global Gradle properties
-└── local.properties # Local config (not in git)
-```
-
-## Version Catalogs (Modern Approach)
-
-### gradle/libs.versions.toml
-
-```toml
-[versions]
-agp = "8.3.0" # Android Gradle Plugin
-kotlin = "1.9.22"
-compileSdk = "34"
-minSdk = "24"
-targetSdk = "34"
-
-# Dependencies
-androidx-core = "1.12.0"
-androidx-appcompat = "1.6.1"
-material = "1.11.0"
-androidx-activity = "1.8.2"
-androidx-constraintlayout = "2.1.4"
-
-# Testing
-junit = "4.13.2"
-androidx-test-ext-junit = "1.1.5"
-espresso-core = "3.5.1"
-
-[libraries]
-androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core" }
-androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
-androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "androidx-activity" }
-androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" }
-
-# Testing
-junit = { group = "junit", name = "junit", version.ref = "junit" }
-androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
-androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
-
-[plugins]
-android-application = { id = "com.android.application", version.ref = "agp" }
-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-```
-
-### Root build.gradle.kts
-
-```kotlin
-// Top-level build file
-plugins {
- alias(libs.plugins.android.application) apply false
- alias(libs.plugins.kotlin.android) apply false
-}
-```
-
-### App build.gradle.kts
-
-```kotlin
-plugins {
- alias(libs.plugins.android.application)
- alias(libs.plugins.kotlin.android)
-}
-
-android {
- namespace = "com.example.myapp"
- compileSdk = 34
-
- defaultConfig {
- applicationId = "com.example.myapp"
- minSdk = 24
- targetSdk = 34
- versionCode = 1
- versionName = "1.0"
-
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-
- buildTypes {
- release {
- isMinifyEnabled = true
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
- }
-
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
- }
-
- kotlinOptions {
- jvmTarget = "17"
- }
-}
-
-dependencies {
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.appcompat)
- implementation(libs.material)
- implementation(libs.androidx.activity)
- implementation(libs.androidx.constraintlayout)
-
- testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.test.ext.junit)
- androidTestImplementation(libs.androidx.espresso.core)
-
- // Gomobile AAR
- implementation(files("libs/mylib.aar"))
-}
-```
-
-## Managing Dependencies
-
-### Add Dependencies
-
-**Using version catalog (recommended):**
-
-1. Add to `gradle/libs.versions.toml`:
-```toml
-[versions]
-retrofit = "2.9.0"
-
-[libraries]
-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
-```
-
-2. Use in `app/build.gradle.kts`:
-```kotlin
-dependencies {
- implementation(libs.retrofit)
-}
-```
-
-**Direct declaration:**
-```kotlin
-dependencies {
- implementation("com.squareup.retrofit2:retrofit:2.9.0")
-}
-```
-
-### Dependency Scopes
-
-```kotlin
-dependencies {
- // Compile and runtime
- implementation("...") // Recommended: Not exposed to consumers
-
- // Compile only (not in APK)
- compileOnly("...") // Example: annotations
-
- // Runtime only
- runtimeOnly("...") // Example: JDBC drivers
-
- // Exposed to consumers
- api("...") // Use sparingly in libraries
-
- // Testing
- testImplementation("...") // Unit tests (JVM)
- androidTestImplementation("...") // Instrumentation tests (Android)
-
- // Debug build only
- debugImplementation("...") // Example: debug tools
-}
-```
-
-### Local AAR/JAR Files
-
-```kotlin
-dependencies {
- // Single AAR
- implementation(files("libs/mylib.aar"))
-
- // All JARs in libs directory
- implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
-
- // All AARs in libs directory
- implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
-}
-```
-
-### Multi-module Dependencies
-
-```kotlin
-dependencies {
- // Depend on another module in the project
- implementation(project(":library"))
-}
-```
-
-## Configuration
-
-### gradle.properties
-
-```properties
-# Performance
-org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
-org.gradle.daemon=true
-org.gradle.parallel=true
-org.gradle.caching=true
-org.gradle.configureondemand=true
-
-# AndroidX
-android.useAndroidX=true
-android.enableJetifier=true
-
-# Kotlin
-kotlin.code.style=official
-
-# Build
-android.nonTransitiveRClass=true
-android.defaults.buildfeatures.buildconfig=true
-```
-
-### local.properties
-
-```properties
-# SDK location (don't commit to git)
-sdk.dir=/home/user/Android/Sdk
-
-# NDK location (optional)
-ndk.dir=/home/user/Android/Sdk/ndk/26.1.10909125
-```
-
-## Build Variants
-
-### Build Types
-
-```kotlin
-android {
- buildTypes {
- debug {
- applicationIdSuffix = ".debug"
- isDebuggable = true
- isMinifyEnabled = false
- }
-
- release {
- isMinifyEnabled = true
- isShrinkResources = true
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
-
- create("staging") {
- initWith(getByName("debug"))
- applicationIdSuffix = ".staging"
- }
- }
-}
-```
-
-### Product Flavors
-
-```kotlin
-android {
- flavorDimensions += "version"
-
- productFlavors {
- create("free") {
- dimension = "version"
- applicationIdSuffix = ".free"
- versionNameSuffix = "-free"
- }
-
- create("paid") {
- dimension = "version"
- applicationIdSuffix = ".paid"
- versionNameSuffix = "-paid"
- }
- }
-}
-```
-
-Build combinations:
-```bash
-./gradlew assembleFreeDebug
-./gradlew assembleFreeRelease
-./gradlew assemblePaidDebug
-./gradlew assemblePaidRelease
-```
-
-## ProGuard / R8
-
-### Enable Code Shrinking
-
-```kotlin
-android {
- buildTypes {
- release {
- isMinifyEnabled = true
- isShrinkResources = true
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
- }
-}
-```
-
-### proguard-rules.pro
-
-```proguard
-# Keep gomobile generated classes
--keep class go.** { *; }
--keep class mylib.** { *; }
-
-# Keep model classes
--keep class com.example.myapp.models.** { *; }
-
-# Retrofit
--keepattributes Signature
--keepattributes Exceptions
-
-# Kotlin
--keep class kotlin.** { *; }
--keep class kotlin.Metadata { *; }
--dontwarn kotlin.**
-```
-
-## Common Tasks
-
-```bash
-# List all tasks
-./gradlew tasks
-
-# List dependencies
-./gradlew app:dependencies
-
-# Show dependency tree
-./gradlew app:dependencies --configuration debugRuntimeClasspath
-
-# Check for updates
-./gradlew dependencyUpdates
-
-# Clean build
-./gradlew clean
-
-# Build all variants
-./gradlew build
-
-# Build specific variant
-./gradlew assembleDebug
-./gradlew assembleRelease
-
-# Run tests
-./gradlew test
-./gradlew connectedAndroidTest
-
-# Install on device
-./gradlew installDebug
-```
-
-## Troubleshooting
-
-### Dependency Conflicts
-
-```bash
-# View conflict resolution
-./gradlew app:dependencies
-
-# Force specific version
-dependencies {
- implementation("com.example:library:1.0") {
- force = true
- }
-}
-
-# Exclude transitive dependency
-dependencies {
- implementation("com.example:library:1.0") {
- exclude(group = "com.unwanted", module = "module")
- }
-}
-```
-
-### Sync Issues
-
-```bash
-# Refresh dependencies
-./gradlew --refresh-dependencies
-
-# Clear cache
-./gradlew clean cleanBuildCache
-
-# Delete .gradle folder
-rm -rf .gradle
-./gradlew build
-```
-
-### Memory Issues
-
-Increase memory in `gradle.properties`:
-```properties
-org.gradle.jvmargs=-Xmx8192m -XX:MaxMetaspaceSize=2048m
-```
-
-## Multi-module Projects
-
-### settings.gradle.kts
-
-```kotlin
-pluginManagement {
- repositories {
- google()
- mavenCentral()
- gradlePluginPortal()
- }
-}
-
-dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
- repositories {
- google()
- mavenCentral()
- }
-}
-
-rootProject.name = "MyProject"
-include(":app")
-include(":library")
-```
-
-### Library Module
-
-`library/build.gradle.kts`:
-```kotlin
-plugins {
- alias(libs.plugins.android.library)
- alias(libs.plugins.kotlin.android)
-}
-
-android {
- namespace = "com.example.library"
- compileSdk = 34
-
- defaultConfig {
- minSdk = 24
- }
-}
-```
-
-Use from app:
-```kotlin
-dependencies {
- implementation(project(":library"))
-}
-```
-
-## Optimization Tips
-
-1. **Use version catalogs**: Centralize dependency versions
-2. **Enable build cache**: Faster incremental builds
-3. **Use Gradle daemon**: Reduces startup time
-4. **Parallel execution**: Builds modules in parallel
-5. **Configure on demand**: Only configure needed projects
-6. **Avoid clean**: Let Gradle handle incremental builds
-
-## Next Steps
-
-- **Build APK/AAB** - Use the **Build** workflow
-- **Manage gomobile integration** - Use the **GomobileBind** workflow
-- **Run tests** - Use the **Test** workflow
-
-## Resources
-
-- [Gradle Plugin User Guide](https://developer.android.com/build)
-- [Version Catalogs](https://docs.gradle.org/current/userguide/platforms.html)
-- [Dependency Management](https://developer.android.com/studio/build/dependencies)
dots/.config/claude/skills/Android/workflows/Nix.md
@@ -1,517 +0,0 @@
-# Nix Workflow
-
-Configure Android development on NixOS and with Nix package manager using nixpkgs androidenv.
-
-## Why Nix for Android?
-
-**Benefits:**
-- Declarative configuration
-- Reproducible builds
-- Version pinning
-- No manual SDK management
-- Share configuration across machines
-
-**Challenges:**
-- More complex initial setup
-- Not all Android SDK versions available
-- May need to use imperative setup for some tools
-
-## NixOS System Configuration
-
-### System Packages
-
-`systems/common/programs/android.nix`:
-```nix
-{ config, pkgs, ... }:
-
-{
- programs.adb.enable = true; # Enable adb system-wide
- users.users.youruser.extraGroups = [ "adbusers" ]; # Add user to adbusers
-
- environment.systemPackages = with pkgs; [
- android-tools # adb, fastboot
- ];
-}
-```
-
-### Home Manager Configuration
-
-`home/common/dev/android.nix`:
-```nix
-{ config, pkgs, ... }:
-
-let
- # Compose Android SDK with specific components
- androidSdk = pkgs.androidenv.composeAndroidPackages {
- platformVersions = [ "34" "33" ];
- buildToolsVersions = [ "34.0.0" "33.0.2" ];
- includeNDK = true;
- ndkVersion = "26.1.10909125";
- includeEmulator = true;
- includeSources = false;
- includeSystemImages = true;
- systemImageTypes = [ "google_apis" ];
- abiVersions = [ "x86_64" "arm64-v8a" ];
- cmakeVersions = [ "3.22.1" ];
- };
-
-in
-{
- home.packages = with pkgs; [
- # Android SDK (composed above)
- androidSdk.androidsdk
-
- # Development tools
- android-studio
- jdk17
-
- # Go and gomobile
- go
- # Note: gomobile needs to be installed via go install
- ];
-
- home.sessionVariables = {
- ANDROID_HOME = "${androidSdk.androidsdk}/libexec/android-sdk";
- ANDROID_SDK_ROOT = "${androidSdk.androidsdk}/libexec/android-sdk";
- };
-
- home.sessionPath = [
- "${androidSdk.androidsdk}/libexec/android-sdk/platform-tools"
- "${androidSdk.androidsdk}/libexec/android-sdk/tools"
- "${androidSdk.androidsdk}/libexec/android-sdk/emulator"
- ];
-}
-```
-
-Apply configuration:
-```bash
-# For NixOS systems
-sudo nixos-rebuild switch
-
-# For home-manager
-home-manager switch --flake .#youruser@hostname
-```
-
-## androidenv.composeAndroidPackages
-
-### Available Options
-
-```nix
-pkgs.androidenv.composeAndroidPackages {
- # Platform versions (Android API levels)
- platformVersions = [ "34" "33" "31" ];
-
- # Build tools versions
- buildToolsVersions = [ "34.0.0" "33.0.2" ];
-
- # Include NDK
- includeNDK = true;
- ndkVersion = "26.1.10909125"; # Specific NDK version
-
- # Include Android Emulator
- includeEmulator = true;
-
- # Include system images for emulator
- includeSystemImages = true;
- systemImageTypes = [ "google_apis" "google_apis_playstore" "default" ];
- abiVersions = [ "x86_64" "arm64-v8a" "armeabi-v7a" ];
-
- # Include platform sources
- includeSources = false;
-
- # Include CMake (for C++ projects)
- cmakeVersions = [ "3.22.1" ];
-
- # Include extras
- includeExtras = [ "extras;google;gcm" ];
-}
-```
-
-### Minimal Configuration
-
-For basic Android development:
-
-```nix
-androidSdk = pkgs.androidenv.composeAndroidPackages {
- platformVersions = [ "34" ];
- buildToolsVersions = [ "34.0.0" ];
- includeNDK = false;
- includeEmulator = false;
-};
-```
-
-### Full Configuration
-
-For complete Android development with emulator:
-
-```nix
-androidSdk = pkgs.androidenv.composeAndroidPackages {
- platformVersions = [ "34" "33" "31" ];
- buildToolsVersions = [ "34.0.0" "33.0.2" ];
- includeNDK = true;
- ndkVersion = "26.1.10909125";
- includeEmulator = true;
- includeSystemImages = true;
- systemImageTypes = [ "google_apis" ];
- abiVersions = [ "x86_64" "arm64-v8a" ];
- includeSources = true;
- cmakeVersions = [ "3.22.1" ];
-};
-```
-
-## Development Shell (nix develop)
-
-Create a project-specific development shell.
-
-`flake.nix`:
-```nix
-{
- description = "Android app with gomobile";
-
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
- flake-utils.url = "github:numtide/flake-utils";
- };
-
- outputs = { self, nixpkgs, flake-utils }:
- flake-utils.lib.eachDefaultSystem (system:
- let
- pkgs = nixpkgs.legacyPackages.${system};
-
- androidSdk = pkgs.androidenv.composeAndroidPackages {
- platformVersions = [ "34" ];
- buildToolsVersions = [ "34.0.0" ];
- includeNDK = true;
- ndkVersion = "26.1.10909125";
- includeEmulator = true;
- includeSystemImages = true;
- systemImageTypes = [ "google_apis" ];
- abiVersions = [ "x86_64" ];
- };
-
- in
- {
- devShells.default = pkgs.mkShell {
- buildInputs = with pkgs; [
- androidSdk.androidsdk
- jdk17
- gradle
- go
- ];
-
- shellHook = ''
- export ANDROID_HOME="${androidSdk.androidsdk}/libexec/android-sdk"
- export ANDROID_SDK_ROOT="$ANDROID_HOME"
- export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$ANDROID_HOME/emulator:$PATH"
-
- # Install gomobile if not already installed
- if ! command -v gomobile &> /dev/null; then
- echo "Installing gomobile..."
- go install golang.org/x/mobile/cmd/gomobile@latest
- gomobile init
- fi
-
- echo "Android development environment ready!"
- echo "ANDROID_HOME: $ANDROID_HOME"
- echo "Java: $(java -version 2>&1 | head -n 1)"
- echo "Gradle: $(gradle --version | grep Gradle)"
- echo "Go: $(go version)"
- '';
- };
- }
- );
-}
-```
-
-Enter development shell:
-```bash
-nix develop
-
-# Or use direnv for automatic activation
-echo "use flake" > .envrc
-direnv allow
-```
-
-## Gomobile with Nix
-
-### Install Gomobile
-
-Gomobile isn't packaged in nixpkgs, so install via Go:
-
-```bash
-# In nix shell
-go install golang.org/x/mobile/cmd/gomobile@latest
-gomobile init
-```
-
-### Build Script
-
-`build-aar.sh`:
-```bash
-#!/usr/bin/env nix-shell
-#! nix-shell -i bash -p go android-tools
-
-set -euo pipefail
-
-# Install gomobile if needed
-if ! command -v gomobile &> /dev/null; then
- go install golang.org/x/mobile/cmd/gomobile@latest
- gomobile init
-fi
-
-# Build AAR
-cd golib
-gomobile bind -target=android -o mylib.aar .
-cp mylib.aar ../app/libs/
-
-echo "AAR built and copied to app/libs/"
-```
-
-Make executable and run:
-```bash
-chmod +x build-aar.sh
-./build-aar.sh
-```
-
-## Android Emulator with Nix
-
-### Create AVD
-
-```bash
-# Enter nix shell with Android SDK
-nix develop
-
-# List available system images
-ls $ANDROID_HOME/system-images/
-
-# Create AVD
-avdmanager create avd \
- -n NixPixel \
- -k "system-images;android-34;google_apis;x86_64" \
- -d "pixel_7"
-
-# Start emulator
-emulator -avd NixPixel
-```
-
-### Emulator Issues on NixOS
-
-**Graphics acceleration:**
-
-```nix
-# System configuration for KVM support
-virtualisation.libvirtd.enable = true;
-users.users.youruser.extraGroups = [ "libvirtd" "kvm" ];
-
-# Or use hardware.opengl
-hardware.opengl.enable = true;
-hardware.opengl.driSupport = true;
-hardware.opengl.driSupport32Bit = true;
-```
-
-**If emulator fails to start:**
-
-```bash
-# Use software rendering
-emulator -avd NixPixel -gpu swiftshader_indirect
-
-# Or check emulator log
-emulator -avd NixPixel -verbose
-```
-
-## Gradle with Nix
-
-### Using Nix-provided Gradle
-
-```nix
-devShells.default = pkgs.mkShell {
- buildInputs = with pkgs; [
- gradle
- jdk17
- ];
-};
-```
-
-### Using Gradle Wrapper (Recommended)
-
-Gradle wrapper is more portable:
-
-```bash
-# Generate wrapper
-gradle wrapper --gradle-version=8.6
-
-# Use wrapper (works without nix shell)
-./gradlew build
-```
-
-## Building Android App with Nix
-
-### Simple Build
-
-`default.nix`:
-```nix
-{ pkgs ? import <nixpkgs> {} }:
-
-let
- androidSdk = pkgs.androidenv.composeAndroidPackages {
- platformVersions = [ "34" ];
- buildToolsVersions = [ "34.0.0" ];
- includeNDK = true;
- ndkVersion = "26.1.10909125";
- };
-
-in
-pkgs.stdenv.mkDerivation {
- pname = "myapp";
- version = "1.0";
-
- src = ./.;
-
- buildInputs = with pkgs; [
- androidSdk.androidsdk
- jdk17
- gradle
- go
- ];
-
- buildPhase = ''
- export ANDROID_HOME="${androidSdk.androidsdk}/libexec/android-sdk"
- export GRADLE_USER_HOME="$PWD/.gradle"
-
- # Build gomobile AAR
- cd golib
- ${pkgs.go}/bin/go install golang.org/x/mobile/cmd/gomobile@latest
- export PATH="$HOME/go/bin:$PATH"
- gomobile bind -target=android -o mylib.aar .
- cp mylib.aar ../app/libs/
- cd ..
-
- # Build APK
- gradle assembleDebug --no-daemon
- '';
-
- installPhase = ''
- mkdir -p $out
- cp app/build/outputs/apk/debug/app-debug.apk $out/
- '';
-}
-```
-
-Build:
-```bash
-nix-build
-# APK at: result/app-debug.apk
-```
-
-## Troubleshooting
-
-### ANDROID_HOME not set
-
-```bash
-# Check variable
-echo $ANDROID_HOME
-
-# Should point to:
-/nix/store/.../libexec/android-sdk
-
-# If not set, add to shell:
-export ANDROID_HOME="$(nix-build '<nixpkgs>' -A androidenv.composeAndroidPackages {...} --no-out-link)/libexec/android-sdk"
-```
-
-### adb permission denied
-
-```bash
-# On NixOS, enable adb
-# In configuration.nix:
-programs.adb.enable = true;
-users.users.youruser.extraGroups = [ "adbusers" ];
-
-# Rebuild
-sudo nixos-rebuild switch
-
-# Log out and back in
-```
-
-### Gradle can't find Android SDK
-
-Create `local.properties`:
-```bash
-echo "sdk.dir=$ANDROID_HOME" > local.properties
-```
-
-### NDK not found by gomobile
-
-```bash
-# Check NDK location
-ls $ANDROID_HOME/ndk/
-
-# gomobile init should find it automatically
-gomobile init
-```
-
-## Hybrid Approach (Recommended)
-
-Use Nix for system tools, imperative for SDK:
-
-**System (Nix):**
-```nix
-environment.systemPackages = with pkgs; [
- android-tools # adb, fastboot
- jdk17
- gradle
- go
-];
-```
-
-**SDK (Imperative):**
-```bash
-# Install SDK manually
-mkdir -p ~/Android/Sdk
-# Download cmdline-tools
-# Use sdkmanager for packages
-```
-
-This provides flexibility while keeping tools reproducible.
-
-## Integration with this Repository
-
-In your home repository, add:
-
-`home/common/dev/android.nix`:
-```nix
-{ config, pkgs, lib, ... }:
-
-{
- home.packages = with pkgs; [
- android-tools
- android-studio # Optional
- jdk17
- ];
-
- # Add to shell init
- programs.zsh.initExtra = lib.mkAfter ''
- # Android SDK (if using manual installation)
- export ANDROID_HOME=$HOME/Android/Sdk
- export PATH=$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH
- '';
-}
-```
-
-Import in your home configuration:
-```nix
-imports = [
- ./common/dev/android.nix
-];
-```
-
-## Next Steps
-
-- **Build Android app** - Use the **Build** workflow
-- **Setup emulator** - Use the **Emulator** workflow
-- **Integrate gomobile** - Use the **GomobileBind** workflow
-
-## Resources
-
-- [Nixpkgs Android Documentation](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/mobile/androidenv/README.md)
-- [androidenv Reference](https://nixos.org/manual/nixpkgs/stable/#android)
-- [Nix Android Examples](https://github.com/NixOS/nixpkgs/tree/master/pkgs/development/mobile/androidenv/examples)
dots/.config/claude/skills/Android/workflows/Publish.md
@@ -1,492 +0,0 @@
-# Publish Workflow
-
-Sign, build, and publish Android applications to Google Play Store.
-
-## Overview
-
-Steps to publish:
-1. Create signing key (keystore)
-2. Configure signing in Gradle
-3. Build release AAB
-4. Test release build
-5. Upload to Play Console
-6. Complete store listing
-7. Submit for review
-
-## Create Signing Key
-
-### Generate Keystore
-
-```bash
-keytool -genkey -v \
- -keystore release.keystore \
- -alias myapp-key \
- -keyalg RSA \
- -keysize 2048 \
- -validity 10000
-
-# You'll be prompted for:
-# - Keystore password
-# - Key password
-# - Name, organization, etc.
-```
-
-**Important:**
-- **NEVER commit keystore to git**
-- **Backup keystore securely** (losing it means you can't update your app)
-- **Remember passwords** (write them down securely)
-
-### Keystore Information
-
-View keystore details:
-```bash
-keytool -list -v -keystore release.keystore
-```
-
-### Alternative: Use Play App Signing
-
-Google can manage signing for you:
-1. Google generates and stores the key
-2. You upload signed AAB
-3. Google re-signs with production key
-4. Safer but requires Play App Signing enrollment
-
-## Configure Gradle Signing
-
-### Option 1: Environment Variables (Recommended)
-
-**Store credentials outside repository:**
-
-`app/build.gradle.kts`:
-```kotlin
-android {
- signingConfigs {
- create("release") {
- storeFile = file("../release.keystore")
- storePassword = System.getenv("KEYSTORE_PASSWORD")
- keyAlias = "myapp-key"
- keyPassword = System.getenv("KEY_PASSWORD")
- }
- }
-
- buildTypes {
- release {
- signingConfig = signingConfigs.getByName("release")
- isMinifyEnabled = true
- isShrinkResources = true
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
- }
-}
-```
-
-**Build with environment variables:**
-```bash
-export KEYSTORE_PASSWORD="your-keystore-password"
-export KEY_PASSWORD="your-key-password"
-./gradlew bundleRelease
-```
-
-### Option 2: gradle.properties (Local only)
-
-**Add to `~/.gradle/gradle.properties` (NOT project gradle.properties):**
-```properties
-MYAPP_RELEASE_STORE_FILE=../release.keystore
-MYAPP_RELEASE_KEY_ALIAS=myapp-key
-MYAPP_RELEASE_STORE_PASSWORD=your-keystore-password
-MYAPP_RELEASE_KEY_PASSWORD=your-key-password
-```
-
-`app/build.gradle.kts`:
-```kotlin
-android {
- signingConfigs {
- create("release") {
- storeFile = file(project.properties["MYAPP_RELEASE_STORE_FILE"] as String)
- storePassword = project.properties["MYAPP_RELEASE_STORE_PASSWORD"] as String
- keyAlias = project.properties["MYAPP_RELEASE_KEY_ALIAS"] as String
- keyPassword = project.properties["MYAPP_RELEASE_KEY_PASSWORD"] as String
- }
- }
-
- buildTypes {
- release {
- signingConfig = signingConfigs.getByName("release")
- }
- }
-}
-```
-
-## Build Release
-
-### Build AAB (Play Store)
-
-```bash
-./gradlew bundleRelease
-
-# Output location
-ls app/build/outputs/bundle/release/app-release.aab
-```
-
-### Build APK (Direct distribution)
-
-```bash
-./gradlew assembleRelease
-
-# Output location
-ls app/build/outputs/apk/release/app-release.apk
-```
-
-### Verify Signature
-
-```bash
-# Check AAB signature
-jarsigner -verify -verbose app-release.aab
-
-# Check APK signature
-apksigner verify --verbose app-release.apk
-```
-
-## Test Release Build
-
-**Important: Test before uploading!**
-
-### Install Release APK
-
-```bash
-# Build APK from AAB (using bundletool)
-# Download bundletool from:
-# https://github.com/google/bundletool/releases
-
-# Generate APKs
-java -jar bundletool-all.jar build-apks \
- --bundle=app-release.aab \
- --output=app.apks \
- --mode=universal
-
-# Extract universal APK
-unzip app.apks universal.apk
-
-# Install
-adb install universal.apk
-```
-
-### Test Checklist
-
-- [ ] App installs successfully
-- [ ] App launches without crashes
-- [ ] All features work correctly
-- [ ] ProGuard hasn't broken anything
-- [ ] Gomobile integration works
-- [ ] Network requests succeed
-- [ ] UI looks correct
-- [ ] Performance is acceptable
-
-## Upload to Play Console
-
-### Prerequisites
-
-1. **Google Play Developer account** ($25 one-time fee)
-2. **App created in Play Console**
-3. **Store listing completed**
-
-### Upload AAB
-
-1. Go to [Play Console](https://play.google.com/console)
-2. Select your app
-3. Production → Releases
-4. Create new release
-5. Upload AAB file
-6. Fill release notes
-7. Review and rollout
-
-### Release Tracks
-
-| Track | Purpose | Audience |
-|-------|---------|----------|
-| Internal testing | Quick testing | Up to 100 testers |
-| Closed testing | Alpha/beta | Invited testers |
-| Open testing | Public beta | Anyone can join |
-| Production | Public release | All users |
-
-## Versioning
-
-### Version Code
-
-Monotonically increasing integer:
-```kotlin
-android {
- defaultConfig {
- versionCode = 1 // Increment for each release
- versionName = "1.0"
- }
-}
-```
-
-### Version Name
-
-Human-readable version string:
-```
-1.0.0 - Major.Minor.Patch (Semantic Versioning)
-```
-
-### Automated Versioning
-
-```kotlin
-// Read version from git tags
-fun getVersionCode(): Int {
- val process = Runtime.getRuntime().exec("git rev-list --count HEAD")
- return process.inputStream.bufferedReader().readText().trim().toInt()
-}
-
-fun getVersionName(): String {
- val process = Runtime.getRuntime().exec("git describe --tags --always")
- return process.inputStream.bufferedReader().readText().trim()
-}
-
-android {
- defaultConfig {
- versionCode = getVersionCode()
- versionName = getVersionName()
- }
-}
-```
-
-## Store Listing
-
-### Required Assets
-
-**Screenshots:**
-- Minimum 2 screenshots
-- Recommended: 4-8 screenshots
-- Resolutions: phone, tablet, TV, Wear OS
-
-**Icon:**
-- 512x512 PNG
-- 32-bit color + alpha channel
-
-**Feature Graphic:**
-- 1024x500 PNG
-- Displayed in Play Store
-
-### Required Information
-
-- App name
-- Short description (80 characters)
-- Full description (4000 characters)
-- Category
-- Content rating (via questionnaire)
-- Privacy policy URL (if app collects data)
-- Contact email
-
-## CI/CD Publishing
-
-### GitHub Actions Example
-
-`.github/workflows/release.yml`:
-```yaml
-name: Release
-
-on:
- push:
- tags:
- - 'v*'
-
-jobs:
- release:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: '17'
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '1.22'
-
- - name: Build gomobile AAR
- run: |
- go install golang.org/x/mobile/cmd/gomobile@latest
- gomobile init
- cd golib
- gomobile bind -target=android -o mylib.aar .
- cp mylib.aar ../app/libs/
-
- - name: Build Release AAB
- env:
- KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
- KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
- run: |
- echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > release.keystore
- ./gradlew bundleRelease
-
- - name: Upload to Play Store
- uses: r0adkll/upload-google-play@v1
- with:
- serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
- packageName: com.example.myapp
- releaseFiles: app/build/outputs/bundle/release/app-release.aab
- track: production
- status: completed
-```
-
-**Setup secrets in GitHub:**
-1. Settings → Secrets → Actions
-2. Add:
- - `KEYSTORE_PASSWORD`
- - `KEY_PASSWORD`
- - `KEYSTORE_BASE64` (base64 encoded keystore file)
- - `SERVICE_ACCOUNT_JSON` (Play Console API credentials)
-
-### Get Play Console API Credentials
-
-1. Google Cloud Console → Create Service Account
-2. Download JSON key
-3. Play Console → API access
-4. Link service account
-5. Grant permissions
-
-## Post-Release
-
-### Monitor Crashes
-
-- Play Console → Quality → Crashes
-- View crash reports and ANR (Application Not Responding) reports
-- Consider using Firebase Crashlytics for detailed reports
-
-### Monitor Reviews
-
-- Respond to user reviews
-- Address common issues in updates
-
-### Track Metrics
-
-- Installs
-- Uninstalls
-- Active users
-- Retention rates
-- Crashes
-
-## Update Releases
-
-### Prepare Update
-
-1. Fix bugs / add features
-2. Increment `versionCode`
-3. Update `versionName`
-4. Update release notes
-
-### Release Process
-
-```bash
-# Build new release
-./gradlew bundleRelease
-
-# Test thoroughly
-# Upload to Play Console
-# Submit for review
-```
-
-### Staged Rollout (Recommended)
-
-Release to percentage of users:
-1. Start with 5-10%
-2. Monitor crashes/issues
-3. Increase to 25%, 50%, 100%
-
-If issues found:
-- Halt rollout
-- Fix and release new version
-
-## Best Practices
-
-### Security
-
-- ✅ Enable ProGuard/R8
-- ✅ Remove logging in release builds
-- ✅ Validate all inputs
-- ✅ Use HTTPS for network
-- ✅ Store keystore securely
-- ✅ Use Play App Signing
-- ❌ Never commit keystore passwords
-
-### Build Configuration
-
-```kotlin
-buildTypes {
- release {
- isMinifyEnabled = true
- isShrinkResources = true
- proguardFiles(...)
-
- // Remove debug code
- buildConfigField("Boolean", "DEBUG_MODE", "false")
-
- // Disable logging
- buildConfigField("Boolean", "ENABLE_LOGGING", "false")
- }
-
- debug {
- applicationIdSuffix = ".debug"
- isDebuggable = true
- buildConfigField("Boolean", "DEBUG_MODE", "true")
- buildConfigField("Boolean", "ENABLE_LOGGING", "true")
- }
-}
-```
-
-### Testing
-
-- Test release build on multiple devices
-- Test all features thoroughly
-- Check ProGuard hasn't broken anything
-- Verify gomobile integration
-- Test with poor network conditions
-- Test edge cases
-
-## Troubleshooting
-
-### "App not signed" error
-
-```bash
-# Check signing configuration
-./gradlew :app:signingReport
-
-# Verify keystore exists and credentials are correct
-```
-
-### ProGuard breaks app
-
-Add keep rules in `proguard-rules.pro`:
-```proguard
--keep class com.your.package.** { *; }
-```
-
-### Upload rejected by Play Console
-
-Common reasons:
-- Version code not incremented
-- Signature mismatch
-- Missing required permissions
-- Policy violations
-
-## Next Steps
-
-- **Monitor app performance** - Play Console metrics
-- **Respond to user feedback** - Reviews and ratings
-- **Plan updates** - New features and bug fixes
-
-## Resources
-
-- [Publish Your App](https://developer.android.com/studio/publish)
-- [Play Console](https://play.google.com/console)
-- [App Signing](https://developer.android.com/studio/publish/app-signing)
-- [Launch Checklist](https://developer.android.com/distribute/best-practices/launch/launch-checklist)
dots/.config/claude/skills/Android/workflows/Setup.md
@@ -1,26 +1,19 @@
# Setup Workflow
-Configure Android development environment including SDK, NDK, command-line tools, and necessary environment variables.
+Configure Android development environment including SDK, NDK, command-line tools, and gomobile for both traditional and NixOS setups.
-## Installation Options
+## Installation Methods
-### Option 1: Android Studio (Recommended for beginners)
+Choose based on your system:
+- **Traditional Linux/Mac/Windows**: Command-line tools or Android Studio
+- **NixOS/Nix**: Declarative configuration with androidenv
+- **Android Studio**: GUI-based (easiest for beginners)
-**Pros**: GUI, automatic SDK management, emulator, IDE
-**Cons**: Large download (~1GB), more resources
+## Traditional Setup (Linux/Mac/Windows)
-**Steps:**
-1. Download [Android Studio](https://developer.android.com/studio)
-2. Install and launch
-3. Follow setup wizard to install SDK and tools
-4. SDK installed at: `~/Android/Sdk` (Linux/Mac) or `C:\Users\<user>\AppData\Local\Android\Sdk` (Windows)
+### Option 1: Command-line Tools (Recommended)
-### Option 2: Command-line Tools (Recommended for this setup)
-
-**Pros**: Lightweight, scriptable, no GUI
-**Cons**: Manual configuration, no IDE
-
-**Steps:**
+Lightweight, scriptable setup without Android Studio.
#### 1. Download Command-line Tools
@@ -75,7 +68,7 @@ Type `y` and press Enter for each license.
# Platform tools (adb, fastboot)
sdkmanager "platform-tools"
-# Latest Android platform (API 34 as of 2025)
+# Latest Android platform (API 34 as of 2026)
sdkmanager "platforms;android-34"
# Build tools
@@ -105,11 +98,182 @@ echo $ANDROID_HOME
ls $ANDROID_HOME
```
-### Option 3: NixOS / Nix Package Manager
+### Option 2: Android Studio
-See the **Nix** workflow for NixOS-specific setup.
+**Pros**: GUI, automatic SDK management, emulator, IDE
+**Cons**: Large download (~1GB), more resources
-## Post-Installation
+**Steps:**
+1. Download [Android Studio](https://developer.android.com/studio)
+2. Install and launch
+3. Follow setup wizard to install SDK and tools
+4. SDK installed at: `~/Android/Sdk` (Linux/Mac) or `C:\Users\<user>\AppData\Local\Android\Sdk` (Windows)
+
+## NixOS / Nix Setup
+
+### Why Nix for Android?
+
+**Benefits:**
+- Declarative configuration
+- Reproducible builds
+- Version pinning
+- No manual SDK management
+
+**Challenges:**
+- More complex initial setup
+- Not all Android SDK versions available
+
+### System Configuration (NixOS)
+
+`systems/common/programs/android.nix`:
+```nix
+{ config, pkgs, ... }:
+
+{
+ programs.adb.enable = true;
+ users.users.youruser.extraGroups = [ "adbusers" ];
+
+ environment.systemPackages = with pkgs; [
+ android-tools # adb, fastboot
+ ];
+}
+```
+
+Apply configuration:
+```bash
+sudo nixos-rebuild switch
+```
+
+### Home Manager Configuration
+
+`home/common/dev/android.nix`:
+```nix
+{ config, pkgs, ... }:
+
+let
+ androidSdk = pkgs.androidenv.composeAndroidPackages {
+ platformVersions = [ "34" "33" ];
+ buildToolsVersions = [ "34.0.0" ];
+ includeNDK = true;
+ ndkVersion = "26.1.10909125";
+ includeEmulator = true;
+ includeSystemImages = true;
+ systemImageTypes = [ "google_apis" ];
+ abiVersions = [ "x86_64" "arm64-v8a" ];
+ };
+
+in
+{
+ home.packages = with pkgs; [
+ androidSdk.androidsdk
+ jdk17
+ go
+ ];
+
+ home.sessionVariables = {
+ ANDROID_HOME = "${androidSdk.androidsdk}/libexec/android-sdk";
+ ANDROID_SDK_ROOT = "${androidSdk.androidsdk}/libexec/android-sdk";
+ };
+
+ home.sessionPath = [
+ "${androidSdk.androidsdk}/libexec/android-sdk/platform-tools"
+ "${androidSdk.androidsdk}/libexec/android-sdk/tools"
+ "${androidSdk.androidsdk}/libexec/android-sdk/emulator"
+ ];
+}
+```
+
+Apply configuration:
+```bash
+home-manager switch --flake .#youruser@hostname
+```
+
+### Nix Development Shell (Project-specific)
+
+Create `flake.nix` in your project:
+
+```nix
+{
+ description = "Android development environment";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+
+ androidSdk = pkgs.androidenv.composeAndroidPackages {
+ platformVersions = [ "34" ];
+ buildToolsVersions = [ "34.0.0" ];
+ includeNDK = true;
+ ndkVersion = "26.1.10909125";
+ };
+
+ in
+ {
+ devShells.default = pkgs.mkShell {
+ buildInputs = with pkgs; [
+ androidSdk.androidsdk
+ jdk17
+ gradle
+ go
+ ];
+
+ shellHook = ''
+ export ANDROID_HOME="${androidSdk.androidsdk}/libexec/android-sdk"
+ export ANDROID_SDK_ROOT="$ANDROID_HOME"
+ export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools:$PATH"
+
+ echo "Android development environment ready!"
+ echo "ANDROID_HOME: $ANDROID_HOME"
+ '';
+ };
+ }
+ );
+}
+```
+
+Enter development shell:
+```bash
+nix develop
+
+# Or use direnv for automatic activation
+echo "use flake" > .envrc
+direnv allow
+```
+
+### Nix androidenv Options
+
+```nix
+pkgs.androidenv.composeAndroidPackages {
+ # Platform versions (Android API levels)
+ platformVersions = [ "34" "33" "31" ];
+
+ # Build tools versions
+ buildToolsVersions = [ "34.0.0" ];
+
+ # Include NDK
+ includeNDK = true;
+ ndkVersion = "26.1.10909125";
+
+ # Include Android Emulator
+ includeEmulator = true;
+
+ # Include system images for emulator
+ includeSystemImages = true;
+ systemImageTypes = [ "google_apis" "google_apis_playstore" ];
+ abiVersions = [ "x86_64" "arm64-v8a" ];
+
+ # Include CMake (for C++ projects)
+ cmakeVersions = [ "3.22.1" ];
+}
+```
+
+## Post-Installation (All Methods)
### Install Gomobile
@@ -120,7 +284,7 @@ gomobile init
This downloads additional toolchains for cross-compilation.
-### Configure Gradle (if using Android projects)
+### Configure Gradle
Create `~/.gradle/gradle.properties`:
@@ -144,7 +308,7 @@ android.enableJetifier=true
### Setup Device for Development
-#### Enable Developer Options on Android Device
+#### Enable Developer Options
1. Go to **Settings** → **About phone**
2. Tap **Build number** 7 times
@@ -264,9 +428,27 @@ adb start-server
# Try different USB port
```
+### NixOS: adb permission denied
+
+```bash
+# Add user to adbusers group (in configuration.nix)
+programs.adb.enable = true;
+users.users.youruser.extraGroups = [ "adbusers" ];
+
+# Rebuild and log out/in
+sudo nixos-rebuild switch
+```
+
+### Nix: Gradle can't find Android SDK
+
+```bash
+# Create local.properties
+echo "sdk.dir=$ANDROID_HOME" > local.properties
+```
+
## Updating Components
-### Update SDK components
+### Traditional Setup
```bash
# List installed and available packages
@@ -280,6 +462,16 @@ sdkmanager "platforms;android-35"
sdkmanager "build-tools;35.0.0"
```
+### Nix Setup
+
+```bash
+# Update flake inputs
+nix flake update
+
+# Rebuild configuration
+nixos-rebuild switch # or home-manager switch
+```
+
### Update Gomobile
```bash
@@ -292,9 +484,8 @@ gomobile init
After successful setup:
- **Build Android app** - Use the **Build** workflow
-- **Create emulator** - Use the **Emulator** workflow
-- **Build Go library** - Use the **GomobileBind** workflow
- **Debug app** - Use the **Debug** workflow
+- **Create emulator** - See emulator section in **Debug** workflow
## Resources
@@ -302,3 +493,4 @@ After successful setup:
- [sdkmanager Reference](https://developer.android.com/studio/command-line/sdkmanager)
- [ADB Documentation](https://developer.android.com/studio/command-line/adb)
- [Gomobile Installation](https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile)
+- [Nixpkgs Android Documentation](https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/mobile/androidenv/README.md)
dots/.config/claude/skills/Android/workflows/Test.md
@@ -1,536 +0,0 @@
-# Test Workflow
-
-Write and run tests for Android applications: unit tests, instrumentation tests, and UI tests.
-
-## Test Types
-
-| Type | Runs On | Speed | Use For |
-|------|---------|-------|---------|
-| Unit Tests | JVM | Fast | Business logic, utilities |
-| Instrumentation Tests | Android device/emulator | Slow | Android APIs, database |
-| UI Tests | Android device/emulator | Slowest | User interface, interactions |
-
-## Unit Tests (Local Tests)
-
-### Location
-
-`app/src/test/java/com/example/myapp/`
-
-### Dependencies
-
-`app/build.gradle.kts`:
-```kotlin
-dependencies {
- testImplementation("junit:junit:4.13.2")
- testImplementation("org.mockito:mockito-core:5.7.0")
- testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
-}
-```
-
-### Example Unit Test
-
-```kotlin
-// app/src/test/java/com/example/myapp/CalculatorTest.kt
-package com.example.myapp
-
-import org.junit.Test
-import org.junit.Assert.*
-
-class CalculatorTest {
-
- @Test
- fun addition_isCorrect() {
- val calculator = Calculator()
- val result = calculator.add(2, 3)
- assertEquals(5, result)
- }
-
- @Test
- fun division_byZero_throwsException() {
- val calculator = Calculator()
- assertThrows(ArithmeticException::class.java) {
- calculator.divide(10, 0)
- }
- }
-}
-```
-
-### Run Unit Tests
-
-```bash
-# All unit tests
-./gradlew test
-
-# Specific variant
-./gradlew testDebug
-./gradlew testRelease
-
-# Specific test class
-./gradlew test --tests CalculatorTest
-
-# Specific test method
-./gradlew test --tests CalculatorTest.addition_isCorrect
-
-# With coverage report
-./gradlew testDebugUnitTest jacocoTestReport
-```
-
-### View Results
-
-```bash
-# HTML report location
-open app/build/reports/tests/testDebugUnitTest/index.html
-
-# Coverage report
-open app/build/reports/jacoco/jacocoTestReport/html/index.html
-```
-
-## Instrumentation Tests
-
-### Location
-
-`app/src/androidTest/java/com/example/myapp/`
-
-### Dependencies
-
-`app/build.gradle.kts`:
-```kotlin
-android {
- defaultConfig {
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-}
-
-dependencies {
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
- androidTestImplementation("androidx.test:runner:1.5.2")
- androidTestImplementation("androidx.test:rules:1.5.0")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
-}
-```
-
-### Example Instrumentation Test
-
-```kotlin
-// app/src/androidTest/java/com/example/myapp/DatabaseTest.kt
-package com.example.myapp
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.Assert.*
-
-@RunWith(AndroidJUnit4::class)
-class DatabaseTest {
-
- @Test
- fun useAppContext() {
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- assertEquals("com.example.myapp", appContext.packageName)
- }
-
- @Test
- fun database_insertAndQuery() {
- val appContext = InstrumentationRegistry.getInstrumentation().targetContext
- // Test database operations
- }
-}
-```
-
-### Run Instrumentation Tests
-
-```bash
-# Connect device/emulator first
-adb devices
-
-# All instrumentation tests
-./gradlew connectedAndroidTest
-
-# Specific variant
-./gradlew connectedDebugAndroidTest
-
-# Specific test
-./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.myapp.DatabaseTest
-```
-
-## UI Tests (Espresso)
-
-### Dependencies
-
-```kotlin
-dependencies {
- androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
- androidTestImplementation("androidx.test.espresso:espresso-intents:3.5.1")
- androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1")
-}
-```
-
-### Example UI Test
-
-```kotlin
-// app/src/androidTest/java/com/example/myapp/MainActivityTest.kt
-package com.example.myapp
-
-import androidx.test.ext.junit.rules.ActivityScenarioRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.action.ViewActions.*
-import androidx.test.espresso.assertion.ViewAssertions.matches
-import androidx.test.espresso.matcher.ViewMatchers.*
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class MainActivityTest {
-
- @get:Rule
- val activityRule = ActivityScenarioRule(MainActivity::class.java)
-
- @Test
- fun button_click_showsText() {
- // Type text in EditText
- onView(withId(R.id.editText))
- .perform(typeText("Hello"), closeSoftKeyboard())
-
- // Click button
- onView(withId(R.id.button))
- .perform(click())
-
- // Verify text is displayed
- onView(withId(R.id.textView))
- .check(matches(withText("Hello")))
- }
-
- @Test
- fun recyclerView_scrollAndClick() {
- // Scroll to position
- onView(withId(R.id.recyclerView))
- .perform(scrollToPosition<RecyclerView.ViewHolder>(10))
-
- // Click item
- onView(withText("Item 10"))
- .perform(click())
- }
-}
-```
-
-### Common Espresso Operations
-
-```kotlin
-// Find views
-onView(withId(R.id.viewId))
-onView(withText("text"))
-onView(withContentDescription("description"))
-
-// Actions
-perform(click())
-perform(typeText("text"))
-perform(replaceText("new text"))
-perform(clearText())
-perform(closeSoftKeyboard())
-perform(swipeLeft())
-perform(swipeRight())
-perform(scrollTo())
-
-// Assertions
-check(matches(isDisplayed()))
-check(matches(withText("expected")))
-check(matches(isEnabled()))
-check(matches(isChecked()))
-check(doesNotExist())
-```
-
-## Testing Gomobile Integration
-
-### Unit Test Go Code (Before binding)
-
-```bash
-cd golib
-go test ./...
-go test -v ./...
-go test -cover ./...
-```
-
-### Test AAR Integration
-
-```kotlin
-// app/src/androidTest/java/com/example/myapp/GomobileTest.kt
-package com.example.myapp
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import mylib.Mylib // Import gomobile generated package
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.Assert.*
-
-@RunWith(AndroidJUnit4::class)
-class GomobileTest {
-
- @Test
- fun goFunction_returnsExpectedResult() {
- val result = Mylib.fetchURL("https://example.com")
- assertNotNull(result)
- assertTrue(result.isNotEmpty())
- }
-
- @Test
- fun goFunction_handlesError() {
- try {
- Mylib.fetchURL("invalid-url")
- fail("Should have thrown exception")
- } catch (e: Exception) {
- // Expected
- assertTrue(e.message?.contains("error") == true)
- }
- }
-}
-```
-
-## Test Configuration
-
-### JaCoCo (Code Coverage)
-
-`app/build.gradle.kts`:
-```kotlin
-plugins {
- id("jacoco")
-}
-
-android {
- buildTypes {
- debug {
- enableAndroidTestCoverage = true
- enableUnitTestCoverage = true
- }
- }
-}
-
-tasks.register<JacocoReport>("jacocoTestReport") {
- dependsOn("testDebugUnitTest", "createDebugCoverageReport")
-
- reports {
- xml.required.set(true)
- html.required.set(true)
- }
-
- val fileFilter = listOf(
- "**/R.class",
- "**/R$*.class",
- "**/BuildConfig.*",
- "**/Manifest*.*"
- )
-
- val debugTree = fileTree("${project.buildDir}/intermediates/javac/debug") {
- exclude(fileFilter)
- }
-
- val mainSrc = "${project.projectDir}/src/main/java"
-
- sourceDirectories.setFrom(files(mainSrc))
- classDirectories.setFrom(files(debugTree))
- executionData.setFrom(fileTree(project.buildDir) {
- include("jacoco/testDebugUnitTest.exec", "outputs/code_coverage/debugAndroidTest/connected/**/*.ec")
- })
-}
-```
-
-Run coverage:
-```bash
-./gradlew jacocoTestReport
-open app/build/reports/jacoco/jacocoTestReport/html/index.html
-```
-
-## Test Best Practices
-
-### 1. Follow AAA Pattern
-
-```kotlin
-@Test
-fun testName() {
- // Arrange - Set up test data
- val calculator = Calculator()
- val a = 2
- val b = 3
-
- // Act - Execute the function
- val result = calculator.add(a, b)
-
- // Assert - Verify the result
- assertEquals(5, result)
-}
-```
-
-### 2. Use Descriptive Names
-
-```kotlin
-// Good
-@Test
-fun addition_withPositiveNumbers_returnsSum() { }
-
-@Test
-fun division_withZeroDivisor_throwsException() { }
-
-// Bad
-@Test
-fun test1() { }
-
-@Test
-fun testAdd() { }
-```
-
-### 3. Test One Thing
-
-```kotlin
-// Good - separate tests
-@Test
-fun add_returnsCorrectSum() { }
-
-@Test
-fun add_handlesNegativeNumbers() { }
-
-// Bad - testing multiple things
-@Test
-fun testAddition() {
- // Tests multiple scenarios in one test
-}
-```
-
-### 4. Mock External Dependencies
-
-```kotlin
-import org.mockito.Mock
-import org.mockito.Mockito.*
-
-class UserRepositoryTest {
-
- @Mock
- lateinit var api: ApiService
-
- @Test
- fun getUser_callsApi() {
- val userId = "123"
- `when`(api.fetchUser(userId)).thenReturn(User("John"))
-
- val repository = UserRepository(api)
- val user = repository.getUser(userId)
-
- verify(api).fetchUser(userId)
- assertEquals("John", user.name)
- }
-}
-```
-
-## CI/CD Testing
-
-### GitHub Actions
-
-`.github/workflows/test.yml`:
-```yaml
-name: Android CI
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- distribution: 'temurin'
- java-version: '17'
-
- - name: Setup Go
- uses: actions/setup-go@v5
- with:
- go-version: '1.22'
-
- - name: Test Go code
- run: |
- cd golib
- go test -v ./...
-
- - name: Build gomobile AAR
- run: |
- go install golang.org/x/mobile/cmd/gomobile@latest
- gomobile init
- cd golib
- gomobile bind -target=android -o mylib.aar .
- cp mylib.aar ../app/libs/
-
- - name: Run unit tests
- run: ./gradlew test
-
- - name: Run instrumented tests
- uses: reactivecircus/android-emulator-runner@v2
- with:
- api-level: 34
- target: google_apis
- arch: x86_64
- script: ./gradlew connectedCheck
-
- - name: Upload test results
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: test-results
- path: app/build/reports/tests/
-
- - name: Upload coverage
- uses: codecov/codecov-action@v3
- with:
- files: app/build/reports/jacoco/jacocoTestReport/jacocoTestReport.xml
-```
-
-## Troubleshooting
-
-### Tests fail on CI but pass locally
-
-- Check Android API level compatibility
-- Verify emulator configuration
-- Check for timing issues (add timeouts)
-- Ensure deterministic test data
-
-### Instrumentation tests hang
-
-```bash
-# Clear app data between tests
-adb shell pm clear com.example.myapp
-
-# Restart adb
-adb kill-server
-adb start-server
-```
-
-### Espresso can't find view
-
-```kotlin
-// Wait for view with idling resources
-import androidx.test.espresso.IdlingRegistry
-import androidx.test.espresso.IdlingResource
-
-// Or use explicit waits (not ideal)
-Thread.sleep(1000) // Avoid this in production tests
-```
-
-## Next Steps
-
-- **Increase coverage** - Aim for >80%
-- **Add UI tests** - Cover critical user flows
-- **Integrate CI/CD** - Automated testing on every commit
-- **Monitor flaky tests** - Fix non-deterministic tests
-
-## Resources
-
-- [Android Testing](https://developer.android.com/training/testing)
-- [Espresso Documentation](https://developer.android.com/training/testing/espresso)
-- [JUnit Documentation](https://junit.org/junit4/)
-- [Mockito Documentation](https://site.mockito.org/)
dots/.config/claude/skills/Android/SKILL.md
@@ -35,16 +35,9 @@ Running the **WorkflowName** workflow from the **Android** skill...
| Workflow | Trigger | File |
|----------|---------|------|
-| **GomobileBind** | "build go library for android", "create AAR", "gomobile bind" | `workflows/GomobileBind.md` |
-| **GomobileApp** | "build android app with go", "gomobile build", "standalone go app" | `workflows/GomobileApp.md` |
-| **Setup** | "setup android dev", "install android sdk", "android environment" | `workflows/Setup.md` |
-| **Build** | "build apk", "build aab", "gradle build", "assemble" | `workflows/Build.md` |
-| **Debug** | "debug android", "adb logcat", "android logs", "troubleshoot app" | `workflows/Debug.md` |
-| **Emulator** | "android emulator", "avd", "virtual device", "start emulator" | `workflows/Emulator.md` |
-| **Gradle** | "gradle dependencies", "build.gradle", "android dependencies" | `workflows/Gradle.md` |
-| **Publish** | "publish to play store", "sign apk", "release build", "keystore" | `workflows/Publish.md` |
-| **Test** | "android tests", "instrumentation", "ui tests", "espresso" | `workflows/Test.md` |
-| **Nix** | "android on nix", "nixpkgs android", "android sdk nix" | `workflows/Nix.md` |
+| **Setup** | "setup android dev", "install android sdk", "android environment", "nixos android", "android on nix" | `workflows/Setup.md` |
+| **Build** | "build apk", "build aab", "gradle build", "assemble", "build go library", "create AAR", "gomobile bind", "gomobile build", "publish to play store", "sign apk", "release build", "keystore", "gradle dependencies" | `workflows/Build.md` |
+| **Debug** | "debug android", "adb logcat", "android logs", "troubleshoot app", "android emulator", "avd", "virtual device", "start emulator", "android tests", "instrumentation", "ui tests", "espresso" | `workflows/Debug.md` |
## Core Principles
@@ -474,7 +467,7 @@ This repository uses NixOS. Android SDK can be configured via:
}
```
-See `workflows/Nix.md` for detailed Nix integration.
+See **Setup** workflow for detailed Nix integration.
## Best Practices
@@ -550,7 +543,7 @@ See `workflows/Nix.md` for detailed Nix integration.
**Example 1: Build Go library as Android AAR**
```
User: "I have a Go package with networking code. How do I use it in my Android app?"
-→ Invokes GomobileBind workflow
+→ Invokes Build workflow
→ Checks gomobile installation
→ Generates AAR with `gomobile bind`
→ Shows how to add AAR to build.gradle.kts
@@ -570,7 +563,7 @@ User: "My app crashes when I click the submit button. How do I debug this?"
**Example 3: Setup Android development on NixOS**
```
User: "How do I set up Android development on my NixOS machine?"
-→ Invokes Nix workflow
+→ Invokes Setup workflow
→ Shows nixpkgs Android SDK configuration
→ Explains how to compose Android packages with androidenv
→ Configures environment variables (ANDROID_HOME, PATH)