kmp-repository-creator
Generate Repository classes for Kotlin Multiplatform (KMP) projects using Ktor for networking and multiplatform-settings for local storage. Use when: (1) Creating data layer repositories, (2) Implementing network data fetching with Ktor, (3) Adding local caching with multiplatform-settings, (4) Writing repository unit tests, (5) Following TDD workflow for repository implementation. Triggers: repository, data layer, ktor, multiplatform-settings, network fetch, local storage, cache, data source, TDD repository, repository pattern, kmp repository.
安装与使用
请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。
查看来源 ↗使用指南技能说明
KMP Repository Creator
Generate Repository classes following TDD methodology with Ktor networking and multiplatform-settings caching.
Coding Standards
MANDATORY: Follow kotlin-compose-standards:
- Trailing comma EXCEPT for Modifier arguments
- New line at end of file
TDD Workflow
Always follow Red-Green-Refactor cycle:
- RED - Write failing test first
- GREEN - Implement minimal code to pass
- REFACTOR - Improve code while keeping tests green
See references/tdd-workflow.md for detailed TDD patterns.
Repository Structure
feature/
data/
repository/
UserRepository.kt # Interface
UserRepositoryImpl.kt # Implementation
datasource/
UserRemoteDataSource.kt # Ktor network calls
UserLocalDataSource.kt # multiplatform-settings
model/
UserDto.kt # Network DTOs
domain/
model/
User.kt # Domain models
Quick Start
1. Define Repository Interface
interface UserRepository {
suspend fun getUser(id: String): User
suspend fun getUsers(): List<User>
fun observeUser(id: String): Flow<User?>
}
2. Write Tests First (TDD)
class UserRepositoryImplTest {
private lateinit var repository: UserRepository
private val remoteDataSource = FakeUserRemoteDataSource()
private val localDataSource = FakeUserLocalDataSource()
@BeforeTest
fun setup() {
repository = UserRepositoryImpl(remoteDataSource, localDataSource)
}
@Test
fun `getUser returns cached user when available`() = runTest {
// Given
val cachedUser = User(id = "1", name = "John")
localDataSource.saveUser(cachedUser)
// When
val result = repository.getUser("1")
// Then
assertEquals(cachedUser, result)
}
@Test
fun `getUser fetches from remote when cache empty`() = runTest {
// Given
val remoteUser = UserDto(id = "1", name = "John")
remoteDataSource.setResponse(remoteUser)
// When
val result = repository.getUser("1")
// Then
assertEquals("John", result.name)
}
@Test
fun `getUser throws exception when network fails`() = runTest {
// Given
remoteDataSource.setError(IOException("Network error"))
// When/Then
assertFailsWith<IOException> {
repository.getUser("1")
}
}
}
3. Implement Repository
class UserRepositoryImpl(
private val remoteDataSource: UserRemoteDataSource,
private val localDataSource: UserLocalDataSource,
) : UserRepository {
override suspend fun getUser(id: String): User {
// Try cache first
localDataSource.getUser(id)?.let { return it }
// Fetch from remote
val dto = remoteDataSource.getUser(id)
val user = dto.toDomain()
localDataSource.saveUser(user)
return user
}
override suspend fun getUsers(): List<User> {
return remoteDataSource.getUsers().map { it.toDomain() }
}
override fun observeUser(id: String): Flow<User?> {
return localDataSource.observeUser(id)
}
}
Workflow
- Analyze requirements - What data? Caching strategy?
- Define interface - Repository contract with suspend functions
- Write tests - Cover cache hit, cache miss, error cases
- Implement minimal - Just enough to pass tests
- Refactor - Extract data sources, improve error handling
- Verify - Run tests, ensure all pass
References
| File | When to Read |
|---|---|
| ktor-patterns.md | Network calls, error handling, serialization |
| multiplatform-settings.md | Local storage, caching patterns |
| tdd-workflow.md | TDD cycle, test patterns, fake implementations |
| testing-patterns.md | Test doubles, coroutine testing |
Koin Integration
Repository injected via Koin DSL:
val dataModule = module {
// Data sources
single { UserRemoteDataSource(get()) }
single { UserLocalDataSource(get()) }
// Repository
single<UserRepository> { UserRepositoryImpl(get(), get()) }
}
ViewModel receives repository:
class UserViewModel(
private val userRepository: UserRepository,
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
try {
val user = userRepository.getUser(id)
_uiState.value = UserUiState.Success(user)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e.message)
}
}
}
}
Best Practices
- Repository throws exceptions for errors (ViewModel handles with try-catch)
- Use
Flow<T>for observable data - Keep domain models separate from DTOs
- Cache strategy: cache-first with network fallback
- Test all paths: success, cache hit, network error
使用示例
查看来源仓库中的使用示例与配置说明。
查看来源 ↗来源历史
- 收录时间
- 2026-01-20
- 索引更新时间
- 2026-01-26
- Branch
- main
收录与同步日期属于本站记录。源码修改历史请查看来源仓库。
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!