android-dev
SKILL.md
Android Native Development
Task Router
| Task | Jump to |
|---|---|
| Jetpack Compose UI (composables, recomposition, state, layouts) | references/jetpack-compose-fundamentals.md |
| Kotlin Coroutines & Flow (StateFlow, SharedFlow, operators, testing) | references/kotlin-coroutines-flow.md |
| App Architecture (Clean Architecture, ViewModel, Hilt, Navigation, Use Cases) | references/architecture-patterns.md |
| Room database (entities, DAO, migrations, relations, testing) | references/room-database.md |
| Networking (Retrofit, OkHttp, Kotlinx Serialization, Coil, Ktor) | references/networking-serialization.md |
| Material 3 UI (components, theming, dark mode, adaptive UI, foldables) | references/ui-material3.md |
| Background work (WorkManager, AlarmManager, Services, FCM, Doze) | references/background-work.md |
| Testing (MockK, Turbine, Compose tests, Espresso, Paparazzi, Hilt) | references/testing.md |
| Play Store & CI/CD (AAB, signing, flavors, ProGuard, Fastlane, GitHub Actions) | references/play-store-cicd.md |
| Performance & debugging (Profilers, Baseline Profiles, LeakCanary, ANR, Macrobenchmark) | references/performance-debugging.md |
Before Starting
Ask or verify before diving in:
- Min API level? API 26 (Android 8) = safe default (95%+ device coverage). API 21 if you need maximum reach. API 31+ if you can drop legacy workarounds.
- Compose vs Views? Default to Compose for all new screens. Only use Views if integrating into an existing View-based codebase or for custom SurfaceView-heavy work.
- Architecture? Default: Clean Architecture + MVVM (ViewModel + StateFlow + Hilt). For small apps: skip domain layer Use Cases, keep flat repository pattern.
- Target API 35 (Android 15) required for all new Play Store submissions from August 2025.
- Enable edge-to-edge? Yes for all new apps.
enableEdgeToEdge()in Activity + use WindowInsets in Compose. - KMP (Kotlin Multiplatform)? If targeting iOS too, structure domain layer as pure Kotlin, use KMP-compatible libraries (Kotlinx Serialization, Ktor, SQLDelight instead of Room).
Quick Reference
Kotlin vs Java Interop
| Kotlin | Java Equivalent | Notes |
|---|---|---|
data class |
POJO + equals/hashCode/toString | Add @JvmRecord for record compat |
object |
Singleton class | INSTANCE field exposed to Java |
companion object |
static methods/fields |
Use @JvmStatic, @JvmField for clean Java access |
val/var |
final/mutable field |
Generates getter/setter by default |
fun |
method | @JvmOverloads for default args |
suspend fun |
Not directly callable | Wrap with CoroutinesKt.future() or RxJava bridge |
sealed class |
Abstract class | Each subclass must be in same package/file |
extension fun |
Utility static method | Not inheritable, resolved at compile time |
inline fun |
Can't inline in Java | Java sees it as regular function |
API Level Feature Table (Recent)
| API | Android | Key Features for Developers |
|---|---|---|
| 26 | 8.0 Oreo | Notification channels (required), Background limits, Autofill |
| 28 | 9 Pie | Display cutout APIs, Privacy improvements, TLS 1.3 |
| 29 | 10 | Scoped storage, Dark theme, Bubble notifications |
| 30 | 11 | One-time permissions, Scoped storage enforced, Package visibility |
| 31 | 12 | Exact alarm permission, Splash Screen API, dynamic color (Material You) |
| 32 | 12L | Large screen APIs, WindowSizeClass |
| 33 | 13 | Photo picker, Notification permission, Predictive back (developer opt-in) |
| 34 | 14 | Photo picker improvements, Health Connect, Credential Manager |
| 35 | 15 | Edge-to-edge enforced, predictive back enforced, SQLite improvements, new foreground service types |
Decision Trees
WorkManager vs AlarmManager vs Foreground Service
Do you need the work to run even if the app is killed?
├─ YES → WorkManager
│ ├─ Time-sensitive (start immediately)? → .setExpedited()
│ ├─ Periodic? → PeriodicWorkRequest (min 15 min)
│ └─ Chained pipeline? → WorkContinuation
└─ NO → viewModelScope / LifecycleScope coroutine
Do you need an exact time trigger (calendar reminder, alarm clock)?
├─ YES → AlarmManager.setExactAndAllowWhileIdle()
│ └─ Android 12+: check canScheduleExactAlarms() first
└─ NO → WorkManager with flex window
Is it an ongoing operation visible to the user (music, GPS, download)?
└─ YES → Foreground Service (declare type in manifest)
└─ Android 14: must declare foregroundServiceType
System event reaction (boot, connectivity change, SMS)?
└─ YES → BroadcastReceiver
├─ App must be running → runtime register + unregister
└─ App may be stopped → manifest register (system broadcasts only)
Flow vs LiveData
Are you in a 100% Compose project?
├─ YES → Use Flow / StateFlow everywhere
│ └─ Collect with collectAsStateWithLifecycle() in Compose
└─ NO (Views + Compose hybrid) →
├─ ViewModel → UI state → StateFlow.asLiveData() for View binding
└─ Compose screens → StateFlow + collectAsStateWithLifecycle()
Flow wins over LiveData when:
- Need operators (map, filter, combine, debounce)
- Need coroutine integration (callbackFlow, channelFlow)
- Targeting KMP
- Writing unit tests (Turbine > LiveData testing)
LiveData still useful when:
- Existing View-based codebase
- Need automatic lifecycle awareness without coroutines
Room vs DataStore
What are you storing?
├─ Structured relational data (users, articles, relationships) → Room
│ └─ Need full-text search? → Add FTS table with @Fts4/@Fts5
├─ Simple key-value preferences (settings, flags, user prefs) → DataStore Preferences
│ └─ Typed proto data with schema? → DataStore Proto
└─ Large binary blobs (images, documents) → File system + Room for metadata
Never use SharedPreferences in new code — DataStore is its replacement.
Room and DataStore can coexist in the same app.
Coroutine Dispatcher Selection
What kind of work?
├─ UI update / Compose state write → Dispatchers.Main
├─ Network / File I/O / Database → Dispatchers.IO
├─ CPU-intensive (JSON parsing, image processing, crypto) → Dispatchers.Default
└─ Testing → UnconfinedTestDispatcher / StandardTestDispatcher
Architecture Quick-Start Template
// Standard layer stack for a new feature:
// 1. Domain model (pure Kotlin)
data class Article(val id: String, val title: String, val isBookmarked: Boolean)
// 2. Repository interface (domain layer)
interface ArticleRepository {
fun getArticles(category: String): Flow<List<Article>>
suspend fun toggleBookmark(id: String)
}
// 3. Use Case (optional for simple delegation, useful for business rules)
class GetArticlesUseCase @Inject constructor(val repo: ArticleRepository) {
operator fun invoke(category: String) = repo.getArticles(category)
}
// 4. UiState
sealed interface ArticleUiState {
data object Loading : ArticleUiState
data class Success(val articles: List<Article>) : ArticleUiState
data class Error(val msg: String) : ArticleUiState
}
// 5. ViewModel
@HiltViewModel
class ArticleViewModel @Inject constructor(getArticles: GetArticlesUseCase) : ViewModel() {
val uiState: StateFlow<ArticleUiState> = getArticles("tech")
.map<List<Article>, ArticleUiState> { ArticleUiState.Success(it) }
.onStart { emit(ArticleUiState.Loading) }
.catch { e -> emit(ArticleUiState.Error(e.message ?: "")) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), ArticleUiState.Loading)
}
// 6. Composable
@Composable
fun ArticleScreen(vm: ArticleViewModel = hiltViewModel()) {
val state by vm.uiState.collectAsStateWithLifecycle()
when (state) {
is ArticleUiState.Loading -> CircularProgressIndicator()
is ArticleUiState.Success -> ArticleList((state as ArticleUiState.Success).articles)
is ArticleUiState.Error -> ErrorMessage((state as ArticleUiState.Error).msg)
}
}
Common Gotchas
- CancellationException must always be rethrown — catching it silently breaks structured concurrency
- Never use GlobalScope — no structured concurrency, leaks survive app process
- StateFlow replays last value on rotation — use SharedFlow for one-shot navigation/snackbar events
- Room @Query returning Flow — auto-updates on any table change; use
@QuerywithLIMITfor performance - Compose List stability — use
ImmutableListfrom kotlinx-collections-immutable, not stdlibList, for stable composable params stateInwithWhileSubscribed(5_000)— 5s grace period handles screen rotation without re-fetching- Hilt
@ViewModelScoped— requires@InstallIn(ViewModelComponent::class), notSingletonComponent - Play Store AAB mandatory — target API 35 required for new/updated apps from August 2025
- Edge-to-edge enforced on Android 15 —
enableEdgeToEdge()+WindowInsetshandling required collectAsStatevscollectAsStateWithLifecycle— always prefer the latter; saves battery by pausing when backgrounded
Weekly Installs
1
Repository
george11642/geo…-pluginsGitHub Stars
2
First Seen
9 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1