Introduction
We've got our Quotes app running across Android, iOS, and Desktop with shared UI. In this final part, let's refine the favorite/unfavorite feature and introduce proper state management.
Instead of holding mutable lists directly in the composable, we'll manage state using a ViewModel-style approach in shared code.
Step 1: Create a Quote Model
We already have a basic model:
1data class Quote(2 val id: Int,3 val text: String,4 val author: String,5 val isFavorite: Boolean = false6)
Adding an id
helps with stable updates.
Step 2: Create a ViewModel (Shared Logic)
Inside commonMain
, add:
1class QuotesViewModel {2 private val _quotes = mutableStateListOf(3 Quote(1, "Compose once, run anywhere.", "JetBrains"),4 Quote(2, "Simplicity is the soul of efficiency.", "Austin Freeman"),5 Quote(3, "Stay hungry, stay foolish.", "Steve Jobs")6 )7 val quotes: SnapshotStateList<Quote> = _quotes8910 fun toggleFavorite(id: Int) {11 val index = _quotes.indexOfFirst { it.id == id }12 if (index != -1) {13 _quotes[index] = _quotes[index].copy(isFavorite = !_quotes[index].isFavorite)14 }15 }16}
👉 This centralizes the app's state in one place.
Step 3: Update the Screen
Now our UI observes the ViewModel's state instead of owning it directly.
1@Composable2fun QuotesScreen(viewModel: QuotesViewModel = QuotesViewModel()) {3 val quotes = viewModel.quotes45 LazyColumn {6 items(quotes.size) { index ->7 val quote = quotes[index]8 QuoteCard(9 text = quote.text,10 author = quote.author,11 isFavorite = quote.isFavorite12 ) {13 viewModel.toggleFavorite(quote.id)14 }15 }16 }17}
Step 4: Run It
On Desktop → Run Window { QuotesScreen() }
.
On Android → setContent { QuotesScreen() }
.
On iOS → ComposeUIViewController { QuotesScreen() }
.
Now, toggling a favorite updates everywhere consistently.
Outro
And that's a wrap 🎉 You've built a cross-platform Quotes App with real state management:
- One shared UI codebase.
- ViewModel-style state handling.
- Favorites that persist across recompositions.
From here, you could extend this with persistence (SQLDelight), or live data sources (Ktor), but the foundation is already solid.
Final Result
Here's what our completed Quotes app looks like on Android:

🎉 Series Complete! You've successfully built a complete Compose Multiplatform app with shared UI and state management across Android, iOS, and Desktop.