Koin DI, Repository Pattern & ViewModel in Kotlin Compose Desktop | Lesson 16
Video: Koin DI, Repository Pattern & ViewModel in Kotlin Compose Desktop | Lesson 16 by Taught by Celeste AI - AI Coding Coach
Watch full page →Koin DI, Repository Pattern & ViewModel in Kotlin Compose Desktop
This lesson demonstrates how to build a clean architecture Book Library app using Kotlin Compose Desktop with Koin for dependency injection. You’ll learn to implement the repository pattern, manage UI state reactively with StateFlow in a ViewModel, and wire everything together using Koin modules and injection.
Code
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.dsl.module
// Data model representing a book
data class Book(val id: Int, val title: String, val author: String, val isFavorite: Boolean = false)
// Repository interface for book data
interface BookRepository {
fun getBooks(): List<Book>
fun searchBooks(query: String): List<Book>
fun toggleFavorite(bookId: Int)
}
// In-memory repository implementation
class InMemoryBookRepository : BookRepository {
private val books = mutableListOf(
Book(1, "1984", "George Orwell"),
Book(2, "Brave New World", "Aldous Huxley"),
// ... more books
)
override fun getBooks() = books.toList()
override fun searchBooks(query: String) =
books.filter { it.title.contains(query, ignoreCase = true) || it.author.contains(query, ignoreCase = true) }
override fun toggleFavorite(bookId: Int) {
books.find { it.id == bookId }?.let {
val index = books.indexOf(it)
books[index] = it.copy(isFavorite = !it.isFavorite)
}
}
}
// UI state data class
data class LibraryUiState(
val books: List<Book> = emptyList(),
val searchQuery: String = "",
val selectedBook: Book? = null
)
// ViewModel managing UI state and repository interaction
class LibraryViewModel(private val repository: BookRepository) : KoinComponent {
private val _uiState = MutableStateFlow(LibraryUiState())
val uiState: StateFlow<LibraryUiState> = _uiState
init {
loadBooks()
}
private fun loadBooks() {
_uiState.value = _uiState.value.copy(books = repository.getBooks())
}
fun onSearch(query: String) {
val filtered = if (query.isBlank()) repository.getBooks() else repository.searchBooks(query)
_uiState.value = _uiState.value.copy(books = filtered, searchQuery = query)
}
fun onToggleFavorite(bookId: Int) {
repository.toggleFavorite(bookId)
// Refresh book list after toggle
onSearch(_uiState.value.searchQuery)
}
fun onSelectBook(book: Book) {
_uiState.value = _uiState.value.copy(selectedBook = book)
}
}
// Koin modules for DI
val dataModule = module {
single { InMemoryBookRepository() }
}
val uiModule = module {
single { LibraryViewModel(get()) }
}
// In Compose, inject ViewModel with koinInject()
// @Composable
// fun LibraryScreen(viewModel: LibraryViewModel = koinInject()) {
// val uiState by viewModel.uiState.collectAsState()
// // UI implementation here
// }
// Main.kt initializes KoinApplication with modules
// startKoin {
// modules(dataModule, uiModule)
// }
Key Points
- Koin simplifies dependency injection by declaring modules and injecting dependencies like the repository and ViewModel.
- The repository pattern abstracts data access, allowing easy swapping of implementations and facilitating testing.
- LibraryViewModel uses StateFlow to expose reactive UI state, enabling Compose to update automatically on changes.
- ViewModel constructor injection with Koin allows clean separation of concerns and easier unit testing.
- Compose’s koinInject() function provides seamless injection of ViewModels inside composables for concise UI code.