| name | coding-conventions |
| description | Kotlin Multiplatform coding conventions for Mangala Wallet - naming patterns, Clean Architecture rules, ScreenModel/UseCase/Repository patterns, Compose best practices. Auto-applies when writing or editing Kotlin code. |
| user-invocable | false |
Coding Conventions
Follow these conventions when writing or editing Kotlin code in Mangala Wallet.
Naming Patterns
| Component | Pattern | Example |
|---|
| Screen | *Screen | WalletMainScreen |
| ScreenModel | *ScreenModel | WalletScreenModel |
| UseCase | *UseCase | GetBalanceUseCase |
| Repository (interface) | *Repository | WalletRepository |
| Repository (impl) | *RepositoryImpl | WalletRepositoryImpl |
| DataSource | *DataSource | LocalWalletDataSource |
| State | *State | WalletState |
| Event | *Event | WalletEvent |
| Koin Module | *Module | walletModule (val, camelCase) |
Architecture Layer Rules
Presentation (Screen + ScreenModel)
↓ depends on
Domain (UseCase + Repository interface + Entity)
↓ depends on
Data (RepositoryImpl + DataSource + DTO)
NEVER: Domain depends on Data. Presentation depends on Data directly. Data depends on Presentation.
Key Patterns
ScreenModel
class FeatureScreenModel(
private val useCase: FeatureUseCase
) : ScreenModel(), KoinComponent {
private val _state = MutableStateFlow<FeatureState>(FeatureState.Loading)
val state: StateFlow<FeatureState> = _state.asStateFlow()
}
UseCase
class GetFeatureDataUseCase(
private val repository: FeatureRepository
) : UseCase<FeatureData>() {
override suspend fun run(params: Map<String, Any?>): FeatureData {
}
}
State / Event (sealed interface)
sealed interface FeatureState {
data object Loading : FeatureState
data class Success(val data: FeatureData) : FeatureState
data class Error(val message: String) : FeatureState
}
sealed interface FeatureEvent {
data class OnItemClick(val id: String) : FeatureEvent
data object OnRefresh : FeatureEvent
}
Kotlin Rules
- Immutability:
val over var, listOf over mutableListOf, data class with val properties
- Null safety: No double-bang operator. Use
?., ?:, let, require(), check()
- Coroutines: Use
viewModelScope (via ScreenModel), never GlobalScope
- Error handling:
Result<T> or Resource<T> sealed class, never raw try-catch in ScreenModel
Compose Rules
- Composable functions: PascalCase, noun-based (
WalletCard, not showWallet)
- Pass
Modifier as first parameter
- State hoisting: stateless composables, state in ScreenModel
- Use
remember for expensive calculations
- Use
derivedStateOf for computed state
- Use
key() in LazyColumn/LazyRow items
DI (Koin)
val featureModule = module {
factory { FeatureUseCase(get()) }
factory { FeatureRepositoryImpl(get()) as FeatureRepository }
factory { FeatureScreenModel(get()) }
}
Register in the appropriate feature module's DI package. ScreenModel uses by inject() from KoinComponent.