Skillquality 0.45

android-architecture

27 Android skills for AI agents (Claude Code, Codex, Cursor). Fixes Supabase auth, Hilt errors, design inconsistency, kapt→ksp, missing UiState states. Reduced my token bills 5×. FitGenZ AI shipped in 18 days.

Price
free
Protocol
skill
Verified
no

What it does

Android Architecture

MVVM + Clean Architecture with Unidirectional Data Flow. These patterns prevent the most common structural mistakes AI agents make when building Android apps.

The three layers — strict separation

Presentation (UI)          →  only depends on Domain
    ViewModel              →  calls UseCases, exposes UiState + Events
    Composables            →  observes ViewModel, sends user actions

Domain (Business Logic)    →  pure Kotlin, zero Android dependencies
    UseCases               →  orchestrate one business operation
    Repository interfaces  →  contracts, implemented in Data layer
    Domain models          →  pure data classes

Data                       →  implements Domain interfaces
    Repository impls       →  coordinate local + remote sources
    Remote DataSource      →  Retrofit API calls
    Local DataSource       →  Room DAO calls
    DTOs / Mappers         →  never expose DTOs to Domain

Rule: Domain layer must never import android.*. If it does, the architecture is broken.

Package structure

com.company.app/
├── di/                        ← Hilt modules only
│   ├── AppModule.kt
│   └── NetworkModule.kt
├── ui/
│   ├── theme/
│   ├── navigation/
│   │   └── AppNavGraph.kt
│   └── feature/
│       └── home/
│           ├── HomeScreen.kt      ← Composable
│           ├── HomeViewModel.kt
│           └── HomeUiState.kt
├── domain/
│   ├── model/
│   │   └── Item.kt               ← pure data class
│   ├── repository/
│   │   └── ItemRepository.kt     ← interface
│   └── usecase/
│       └── GetItemsUseCase.kt
└── data/
    ├── repository/
    │   └── ItemRepositoryImpl.kt
    ├── remote/
    │   ├── ItemApiService.kt
    │   └── dto/
    │       └── ItemDto.kt
    └── local/
        ├── ItemDao.kt
        └── entity/
            └── ItemEntity.kt

ViewModel — complete pattern

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val getItems: GetItemsUseCase,
    private val toggleFavorite: ToggleFavoriteUseCase
) : ViewModel() {

    private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
    val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

    private val _events = MutableSharedFlow<HomeEvent>()
    val events: SharedFlow<HomeEvent> = _events.asSharedFlow()

    init {
        loadItems()
    }

    fun loadItems() {
        viewModelScope.launch {
            _uiState.value = HomeUiState.Loading
            getItems()
                .onSuccess { items ->
                    _uiState.value = if (items.isEmpty()) HomeUiState.Empty
                    else HomeUiState.Success(items)
                }
                .onFailure { error ->
                    _uiState.value = HomeUiState.Error(error.message ?: "Unknown error")
                }
        }
    }

    fun onItemClick(itemId: String) {
        viewModelScope.launch {
            _events.emit(HomeEvent.NavigateToDetail(itemId))
        }
    }

    fun onFavoriteClick(itemId: String) {
        viewModelScope.launch {
            toggleFavorite(itemId)
                .onFailure { _events.emit(HomeEvent.ShowError("Failed to update favorite")) }
        }
    }
}

UiState + UiEvent — define them together

sealed interface HomeUiState {
    data object Loading : HomeUiState
    data object Empty : HomeUiState
    data class Success(val items: List<Item>) : HomeUiState
    data class Error(val message: String) : HomeUiState
}

sealed interface HomeEvent {
    data class NavigateToDetail(val itemId: String) : HomeEvent
    data class ShowError(val message: String) : HomeEvent
    data object NavigateToCreate : HomeEvent
}

UseCase — single responsibility

// ✅ One UseCase = one business operation
class GetItemsUseCase @Inject constructor(
    private val repository: ItemRepository
) {
    suspend operator fun invoke(): Result<List<Item>> =
        repository.getItems()
}

// ✅ UseCases return Result<T> — never throw exceptions to ViewModel
class ToggleFavoriteUseCase @Inject constructor(
    private val repository: ItemRepository
) {
    suspend operator fun invoke(itemId: String): Result<Unit> =
        repository.toggleFavorite(itemId)
}

// ❌ UseCase doing too much — split into separate UseCases
class ItemUseCase @Inject constructor(val repo: ItemRepository) {
    suspend fun getItems() = repo.getItems()
    suspend fun createItem(item: Item) = repo.create(item)
    suspend fun deleteItem(id: String) = repo.delete(id)
    // This is a service, not a use case
}

Repository — interface in Domain, implementation in Data

// domain/repository/ItemRepository.kt
interface ItemRepository {
    suspend fun getItems(): Result<List<Item>>
    suspend fun getItem(id: String): Result<Item>
    suspend fun toggleFavorite(id: String): Result<Unit>
    fun getItemsStream(): Flow<List<Item>>  // for real-time updates
}

// data/repository/ItemRepositoryImpl.kt
class ItemRepositoryImpl @Inject constructor(
    private val remoteSource: ItemRemoteDataSource,
    private val localSource: ItemLocalDataSource,
    private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : ItemRepository {

    override suspend fun getItems(): Result<List<Item>> = withContext(dispatcher) {
        runCatching {
            val remote = remoteSource.getItems()
            localSource.saveItems(remote.map { it.toEntity() })
            remote.map { it.toDomain() }
        }.recoverCatching {
            localSource.getItems().map { it.toDomain() }  // fallback to cache
        }
    }

    override fun getItemsStream(): Flow<List<Item>> =
        localSource.getItemsFlow().map { entities -> entities.map { it.toDomain() } }
}

Mappers — DTOs never cross the data layer boundary

// ✅ Map at the data layer boundary
fun ItemDto.toDomain() = Item(
    id = id,
    title = title,
    description = description,
    isFavorite = isFavorite
)

fun ItemEntity.toDomain() = Item(
    id = id,
    title = title,
    description = description,
    isFavorite = isFavorite
)

fun Item.toEntity() = ItemEntity(
    id = id,
    title = title,
    description = description,
    isFavorite = isFavorite
)

// ❌ Never expose DTOs or Entities to ViewModel or Composable
class HomeViewModel(val repo: ItemRepositoryImpl) {
    val items: StateFlow<List<ItemDto>> = ...  // DTO leaking into ViewModel!
}

Screen — ViewModel wiring

@Composable
fun HomeScreen(
    viewModel: HomeViewModel = hiltViewModel(),
    navController: NavController
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    LaunchedEffect(Unit) {
        viewModel.events.collect { event ->
            when (event) {
                is HomeEvent.NavigateToDetail -> navController.navigate(DetailRoute(event.itemId))
                is HomeEvent.ShowError -> { /* show snackbar */ }
                is HomeEvent.NavigateToCreate -> navController.navigate(CreateRoute)
            }
        }
    }

    HomeContent(
        uiState = uiState,
        onItemClick = viewModel::onItemClick,
        onFavoriteClick = viewModel::onFavoriteClick,
        onRetry = viewModel::loadItems
    )
}

// ✅ Separate content composable — testable without ViewModel
@Composable
private fun HomeContent(
    uiState: HomeUiState,
    onItemClick: (String) -> Unit,
    onFavoriteClick: (String) -> Unit,
    onRetry: () -> Unit
) {
    when (uiState) {
        is HomeUiState.Loading -> LoadingScreen()
        is HomeUiState.Empty -> EmptyScreen()
        is HomeUiState.Success -> ItemList(uiState.items, onItemClick, onFavoriteClick)
        is HomeUiState.Error -> ErrorScreen(uiState.message, onRetry)
    }
}

Common Mistakes

❌ ViewModel calling Room DAO directly — always go through Repository ❌ Repository in Domain layer — interface in Domain, implementation in Data ❌ Domain model with @Entity or @SerialName annotations — pure Kotlin only ❌ ViewModel holding Context — use Application context via Hilt if needed ❌ StateFlow for navigation events — use SharedFlow ❌ Try/catch in ViewModel — handle in Repository, return Result<T> ❌ Multiple ViewModels per screen — one ViewModel per screen ❌ Business logic in Composable — always in ViewModel or UseCase

Deep-dive references

  • references/multi-module-arch.md — structuring feature and core modules
  • references/flow-patterns.md — stateIn, flatMapLatest, combine patterns

Capabilities

skillsource-piyushverma0skill-android-architecturetopic-agent-skillstopic-ai-agenttopic-androidtopic-antigravitytopic-claude-codetopic-codextopic-cursortopic-gemini-clitopic-hilttopic-jetpack-composetopic-kotlintopic-material3

Install

Quality

0.45/ 1.00

deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 8 github stars · SKILL.md body (8,733 chars)

Provenance

Indexed fromgithub
Enriched2026-05-18 19:09:08Z · deterministic:skill-github:v1 · v1
First seen2026-05-18
Last seen2026-05-18

Agent access