registry/skills/google-app-development/SKILL.md
This skill should be used when the user asks to "build an Android app", "create a Composable", "set up an Android project", "review Android code", "refactor Android", "add a screen", "create a Wear OS app", "build for Android TV", "build for Android Auto", "build for Android Automotive", "adapt for Meta Quest", "build for Fire TV", "build for Fire Tablet", or when generating any Kotlin code targeting Google/Android platforms (including AOSP-based devices). Provides modern Jetpack Compose-first best practices covering UI patterns, app lifecycle, navigation, and platform-specific guidance. Use together with `kotlin-development` for language fundamentals. Always generates Kotlin unless the project explicitly requires Java.
npx skillsauth add provectus/awos-recruitment google-app-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Modern best practices for building apps across Google platforms. Targets Jetpack Compose as the primary UI framework with Kotlin. Covers Android (phone), tablet, foldables, Wear OS, Google TV / Android TV, Android Auto, Android Automotive OS, and AOSP-based platforms (Meta Quest, Amazon Fire TV, Amazon Fire Tablets).
For Kotlin language fundamentals (null safety, coroutines, data modeling, error handling, idiomatic patterns), see the kotlin-development skill. This skill focuses on platform and framework patterns.
references/java-interop.md for bridging patterns.references/view-interop.md for interop patterns.minSdk, Compose BOM version, and existing architecture. Adapt recommendations accordingly.references/compose-patterns.md — Composables, state hoisting, recomposition, remember, LazyColumn, navigation, theming, Material 3, side effectsreferences/concurrency.md — Coroutines in Android context: viewModelScope, lifecycleScope, repeatOnLifecycle, WorkManager, foreground servicesreferences/android-lifecycle.md — Activity/Fragment lifecycle, ViewModel, saved state, process death, configuration changesreferences/view-interop.md — AndroidView, ComposeView, embedding Compose in Views and Views in Compose, migration strategiesreferences/project-structure.md — Gradle setup, multi-module architecture, build variants, version catalogs, convention pluginsreferences/tablet-patterns.md — Adaptive layouts, WindowSizeClass, multi-window, foldable devices, large screen guidelinesreferences/wear-os-patterns.md — Compose for Wear OS, Tiles, complications, Health Services, watch facereferences/tv-patterns.md — Compose for TV, focus management, Leanback, D-pad navigation, Google TV / Android TVreferences/android-auto-patterns.md — Car App Library, templates, phone projection, media apps, messaging appsreferences/android-automotive-patterns.md — Android Automotive OS (AAOS), embedded car system, car hardware APIs, system UIreferences/java-interop.md — Calling Java from Kotlin, nullability annotations, SAM conversions, incremental migrationreferences/meta-quest-patterns.md — Adapting Android APK for Meta Quest, spatial UI, entitlement check, VR input, passthroughreferences/fire-tv-patterns.md — Amazon Fire TV, Appstore, Amazon IAP, Alexa integration, missing Google Play Servicesreferences/fire-tablet-patterns.md — Amazon Fire Tablets, device capabilities, Show Mode, Kids Edition, Special OffersFor Kotlin language style (naming, null safety, type annotations), follow the kotlin-development skill. Below are Android/Compose-specific conventions.
PascalCase for UI-emitting composables, camelCase for composables that return values.Modifier.Preview, annotate with @Preview.AppTheme composable that wraps MaterialTheme with app-specific colors, typography, and shapes. Apply it once at the top level (setContent { AppTheme { ... } }). All UI code must reference theme tokens — never raw values.// 1. Define custom theme (once, in ui/theme/)
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit,
) {
val colorScheme = if (darkTheme) AppDarkColorScheme else AppLightColorScheme
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
shapes = AppShapes,
content = content,
)
}
// 2. Apply at root (once)
setContent { AppTheme { AppNavGraph() } }
// 3. Use theme tokens everywhere — never raw values
Text(
text = "Hello",
style = MaterialTheme.typography.bodyMedium, // not fontSize = 14.sp
color = MaterialTheme.colorScheme.onSurface, // not Color(0xFF1C1B1F)
)
Card(shape = MaterialTheme.shapes.medium) { ... } // not RoundedCornerShape(8.dp)
For values not covered by Material tokens (spacing, elevation, icon sizes), define app-level design tokens:
object AppSpacing {
val small = 8.dp
val medium = 16.dp
val large = 24.dp
}
No magic numbers in UI code. If a numeric value appears in UI, it must be either a Material theme token or an app-defined design constant. For theming details see references/compose-patterns.md.
| Element | Convention | Example |
|---|---|---|
| Composable (UI) | PascalCase | UserProfileScreen, SettingsCard |
| Composable (value) | camelCase | rememberScrollState() |
| ViewModel | Suffix ViewModel | SettingsViewModel |
| Screen composable | Suffix Screen | HomeScreen, ProfileScreen |
| UI State | Suffix UiState | HomeUiState, ProfileUiState |
| Activity | Suffix Activity | MainActivity |
| Fragment (legacy) | Suffix Fragment | HomeFragment |
| Repository | Suffix Repository | UserRepository |
| Use case | Verb phrase | GetUserUseCase, SyncDataUseCase |
| Module | Feature name, kebab-case | :feature:auth, :core:network |
// UI State
data class HomeUiState(
val items: List<Item> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
)
// ViewModel
class HomeViewModel(
private val repository: ItemRepository,
) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
fun load() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
repository.getItems()
.onSuccess { items -> _uiState.update { it.copy(items = items, isLoading = false) } }
.onFailure { e -> _uiState.update { it.copy(error = e.message, isLoading = false) } }
}
}
}
// Composable
@Composable
fun HomeScreen(
viewModel: HomeViewModel = viewModel(),
onItemClick: (Item) -> Unit,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
when {
uiState.isLoading -> LoadingIndicator()
uiState.error != null -> ErrorMessage(uiState.error!!)
else -> ItemList(items = uiState.items, onItemClick = onItemClick)
}
}
Rules:
StateFlow + collectAsStateWithLifecycle() for state in ViewModels.remember for composable-local state, rememberSaveable for state surviving config changes.For navigation, theming, side effects, lists, animations see references/compose-patterns.md.
For coroutines language fundamentals (Flow, structured concurrency, cancellation), see the kotlin-development skill. Below are Android-specific patterns.
// ViewModel scope — cancelled when ViewModel is cleared
class UserViewModel(private val repo: UserRepository) : ViewModel() {
val users = repo.observeUsers()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
}
// Lifecycle-aware collection in Compose
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
val users by viewModel.users.collectAsStateWithLifecycle()
UserList(users)
}
// Lifecycle-aware collection in Activity/Fragment (legacy)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.users.collect { updateUI(it) }
}
}
Rules:
viewModelScope for ViewModel operations — auto-cancelled on clear.collectAsStateWithLifecycle() in Compose — lifecycle-aware, stops collection when not visible.repeatOnLifecycle in Activities/Fragments — restarts collection on lifecycle transitions.WhileSubscribed(5000) for stateIn — keeps upstream active 5s after last subscriber (survives rotation).For WorkManager, foreground services, and advanced patterns see references/concurrency.md.
Recommended: MVI (Model-View-Intent) for new projects. MVI enforces unidirectional data flow with a single immutable state and explicit user intents, which maps naturally to Compose. If the project already uses MVVM, MVP, or MVC — adapt to the existing architecture instead of forcing a rewrite.
// 1. State — single immutable data class per screen
data class HomeUiState(
val items: List<Item> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null,
)
// 2. Intent — sealed interface of all user actions
sealed interface HomeIntent {
data object LoadItems : HomeIntent
data class DeleteItem(val id: String) : HomeIntent
data object RetryLoad : HomeIntent
}
// 3. ViewModel — reduces intents into state
@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: ItemRepository,
) : ViewModel() {
private val _uiState = MutableStateFlow(HomeUiState())
val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()
fun onIntent(intent: HomeIntent) {
when (intent) {
is HomeIntent.LoadItems -> loadItems()
is HomeIntent.DeleteItem -> deleteItem(intent.id)
is HomeIntent.RetryLoad -> loadItems()
}
}
private fun loadItems() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
repository.getItems()
.onSuccess { items -> _uiState.update { it.copy(items = items, isLoading = false) } }
.onFailure { e -> _uiState.update { it.copy(error = e.message, isLoading = false) } }
}
}
private fun deleteItem(id: String) {
viewModelScope.launch {
repository.delete(id)
_uiState.update { it.copy(items = it.items.filter { item -> item.id != id }) }
}
}
}
// 4. View — renders state, emits intents
@Composable
fun HomeScreen(viewModel: HomeViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) { viewModel.onIntent(HomeIntent.LoadItems) }
when {
uiState.isLoading -> LoadingIndicator()
uiState.error != null -> ErrorScreen(
message = uiState.error!!,
onRetry = { viewModel.onIntent(HomeIntent.RetryLoad) },
)
else -> ItemList(
items = uiState.items,
onDelete = { id -> viewModel.onIntent(HomeIntent.DeleteItem(id)) },
)
}
}
app/
├── MainActivity.kt
├── navigation/
│ └── AppNavGraph.kt
├── feature/
│ ├── home/
│ │ ├── HomeScreen.kt
│ │ ├── HomeViewModel.kt
│ │ ├── HomeUiState.kt
│ │ └── HomeIntent.kt
│ ├── profile/
│ └── settings/
├── core/
│ ├── data/
│ │ ├── repository/
│ │ └── model/
│ ├── network/
│ └── database/
└── ui/
├── theme/
└── components/
UiState data class, one StateFlow.For multi-module architecture, Gradle setup, and build variants see references/project-structure.md.
// Hilt (recommended)
@HiltViewModel
class HomeViewModel @Inject constructor(
private val repository: ItemRepository,
) : ViewModel() { ... }
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindItemRepository(impl: DefaultItemRepository): ItemRepository
}
// Manual DI (for small projects or libraries)
class AppContainer {
private val api: ApiService by lazy { RetrofitApiService() }
val repository: ItemRepository by lazy { DefaultItemRepository(api) }
}
Rules:
@Singleton sparingly — scope to the narrowest lifecycle.createComposeRule(), semantic matchers, onNodeWithText.class HomeViewModelTest {
private val fakeRepository = FakeItemRepository()
private val viewModel = HomeViewModel(fakeRepository)
@Test
fun `load items updates state`() = runTest {
fakeRepository.emit(listOf(Item("1", "Test")))
viewModel.load()
assertEquals(listOf(Item("1", "Test")), viewModel.uiState.value.items)
}
}
Test naming: fun 'description of behavior'() with backtick syntax, or test_method_condition_expected().
The core skill covers Android phone by default. For other platforms, consult the corresponding reference:
| Platform | Reference | Key Topics |
|---|---|---|
| Tablet / Foldable | references/tablet-patterns.md | WindowSizeClass, adaptive layouts, multi-window, foldable postures |
| Wear OS | references/wear-os-patterns.md | Compose for Wear, Tiles, complications, Health Services |
| Google TV / Android TV | references/tv-patterns.md | Compose for TV, focus/D-pad navigation, Leanback |
| Android Auto | references/android-auto-patterns.md | Car App Library, templates, phone projection |
| Android Automotive | references/android-automotive-patterns.md | AAOS, embedded system, car hardware APIs |
| Meta Quest | references/meta-quest-patterns.md | Adapting APK for VR, spatial UI, entitlement, passthrough |
| Amazon Fire TV | references/fire-tv-patterns.md | Appstore, Amazon IAP, Alexa, missing GMS |
| Amazon Fire Tablets | references/fire-tablet-patterns.md | Device lineup, Show Mode, Kids Edition |
| Mistake | Fix |
|---|---|
| Collecting Flow without lifecycle awareness | Use collectAsStateWithLifecycle() in Compose |
| Business logic in composables | Move to ViewModel, expose as StateFlow |
| mutableStateOf in ViewModel | Use MutableStateFlow + asStateFlow() |
| Passing Context to ViewModel | Use AndroidViewModel only if truly needed, prefer abstractions |
| LaunchedEffect(Unit) for one-time loads | Consider loading in ViewModel init block |
| Hardcoded strings in composables | Use stringResource(R.string.xxx) |
| Not handling process death | Use SavedStateHandle in ViewModel, rememberSaveable in Compose |
| God ViewModel (500+ lines) | Split by screen, extract use cases |
| Not using Modifier parameter | Always accept modifier: Modifier = Modifier as first optional param |
| remember for complex objects | Use remember with proper keys, or move to ViewModel |
| Ignoring configuration changes | Test with rotation, dark mode, font scale |
| Using GlobalScope | Use viewModelScope or structured concurrency |
| View-based navigation in Compose app | Use Compose Navigation (NavHost) |
| LiveData in new code | Use StateFlow + collectAsStateWithLifecycle() |
| Magic numbers in UI (padding(16.dp), fontSize = 14.sp) | Define design tokens (AppSpacing.medium) or use MaterialTheme tokens |
development
Insurance underwriting domain knowledge for building automated submission processing systems. Covers submission-to-bind lifecycle, document extraction patterns, compliance gates (sanctions, licensing, clearance), human-in-the-loop design for regulated financial services, confidence calibration for extracted fields, operating mode progression (manual to automated), and evidence traceability requirements. Use when designing or implementing underwriting pipelines, extraction agents, compliance workflows, HITL review systems, or decision package assembly for insurance or MGA operations.
development
This skill should be used when the user asks to "write TypeScript code", "create a TypeScript module", "define TypeScript types", "add type annotations", "use generics", "handle errors in TypeScript", "set up tsconfig", "organize TypeScript project", or when writing any TypeScript code that is not tied to a specific library or framework. Covers type system, strict mode, naming conventions, error handling, async patterns, and project structure.
development
Use when working with Terraform or OpenTofu - creating modules, writing tests (native test framework, Terratest), setting up CI/CD pipelines, reviewing configurations, choosing between testing approaches, debugging state issues, implementing security scanning (trivy, checkov), or making infrastructure-as-code architecture decisions. Enforces Provectus opinionated conventions (exact version pinning, etc.) on top of community best practices.
development
This skill should be used when the user asks to "write Swift code", "create a Swift type", "set up a Swift package", "review Swift code", "refactor Swift", "use async/await in Swift", "fix Swift style", or when generating any Swift source code regardless of target platform. Provides modern Swift 6+ best practices covering type system, optionals, concurrency, error handling, protocols, generics, and idiomatic patterns. Does not cover any specific platform or framework.