A beautiful, modern Pokemon application built with Compose Multiplatform featuring MVI architecture, type-safe navigation, and dynamic theming. Explore Pokemon, manage favorites, and enjoy a seamless experience across Android, Desktop, and iOS platforms.
🎯 Core Features
- 📱 Multiplatform: Android, Desktop, and iOS support
- 🏗️ MVI Architecture: Clean, predictable state management
- 🧭 Type-Safe Navigation: Kotlin Serialization-based routing
- 🎨 Material 3 Design: Modern UI with dynamic theming
- 🌓 Theme Management: Dark mode + Android Dynamic Colors
- 💾 Offline Support: Room database for favorites
- 🔄 Reactive UI: Real-time updates with StateFlow
🐾 Pokemon Features
- 🔍 Pokemon List: Browse all Pokemon with infinite scrolling
- ❤️ Favorites Management: Add/remove Pokemon from favorites
- 📊 Detailed View: Stats, abilities, types, and more
- 🎨 Type-based Theming: Colors based on Pokemon types
- ✨ Shimmer Loading: Beautiful loading animations
🎭 UI/UX Features
- 🌊 Smooth Animations: Page transitions and micro-interactions
- 📱 Adaptive UI: Responsive design for all screen sizes
- 👆 Swipe Actions: Swipe-to-delete favorites
- 🌈 Dynamic Colors: Android 12+ Material You support
- ⚡ Performance: Optimized with lazy loading and caching
- Clone this repository:
git clone https://github.com/Coding-Meet/CMP-MVI-Template.git
- Open in the latest version of Android Studio or intellij idea.
- This project includes a demo of how to use BuildConfig. (Note: The API key here is just a placeholder and not used in the app.) Create or update your local.properties with:
API_KEY=API_KEY Is_Debug_Build=true
- For Android, run the
composeAppmodule by selecting theappconfiguration. If you need help with the configuration, follow this link for android - For iOS, run the
composeAppmodule by selecting theiosAppconfiguration. If you need help with the configuration, follow this link for Ios - For Desktop, run
./gradlew :composeApp:run - For Desktop with hot reload, run
./gradlew desktopRun -DmainClass=com.example.cmp_mvi_template.MainKt
![]() | ![]() | ![]() | ![]() |
![]() | ![]() | ![]() | ![]() |
![]() | ![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
📱 Presentation Layer (UI) ├── 🎭 Compose Screens ├── 🧠 ViewModels (MVI) └── 📊 State Management 💼 Domain Layer (Business Logic) ├── 📋 Use Cases ├── 🏪 Repository Interfaces └── 📦 Domain Models 💾 Data Layer (Data Sources) ├── 🌐 Remote (Ktor + PokéAPI) ├── 💿 Local (Room Database) └── 🔄 Repository Implementation - 🎯 UI: Compose Multiplatform + Material 3
- 🏗️ Architecture: MVI + Clean Architecture + Use Cases
- 🧭 Navigation: Compose Navigation + Type-safe routes
- 🌐 Networking: Ktor Client + JSON Serialization
- 💾 Database: Room + SQLite (multiplatform)
- 🎨 Theming: DataStore Preferences + Dynamic Colors
- 🔧 Dependency Injection: Koin
- 🖼️ Images: Coil3 (async image loading)
CMP-MVI-Template/ ├── composeApp/ # ✅ Main Compose Multiplatform app module │ ├── build.gradle.kts # ➕ Gradle config for this module │ ├── setting.preferences_pb # 📦 Proto DataStore schema for user settings (theme, etc.) │ │ └── src/ │ ├── androidMain/ # 🤖 Android-specific code │ │ ├── AndroidManifest.xml # 📄 Manifest file for Android │ │ └── kotlin/ │ │ └── com/example/cmp_mvi_template/ │ │ ├── MainActivity.kt # 🚀 Entry point for Android app │ │ ├── MyApplication.kt # 🏁 Application class for Koin setup │ │ └── core/platform/ # 🔌 Android actual implementations for platform interfaces │ │ ├── iosMain/ # 🍎 iOS-specific code (uses Kotlin/Native) │ │ └── kotlin/ │ │ └── com/example/cmp_mvi_template/ │ │ ├── MainViewController.kt # 🧭 iOS screen entry point (UIKit) │ │ └── core/platform/ # 🔌 iOS actual implementations for platform interfaces │ │ ├── desktopMain/ # 🖥 Desktop-specific entry point │ │ └── kotlin/ │ │ └── com/example/cmp_mvi_template/ │ │ └── main.kt # 💻 Desktop launcher with ComposeWindow │ │ ├── commonMain/ # 🔁 Shared code between all platforms │ │ ├── composeResources/ # 🎨 Compose Multiplatform resources (fonts, strings, etc.) │ │ └── kotlin/ │ │ └── com/example/cmp_mvi_template/ │ │ │ │ ├── di/ # 🧩 Dependency Injection modules using Koin │ │ │ ├── AppModule.kt │ │ │ └── PlatformModule.kt │ │ │ │ ├── app/ # 🌐 App-level shared state (theme, scaffold, etc.) │ │ │ ├── AppViewModel.kt │ │ │ └── AppScaffold.kt │ │ │ │ ├── core/ # 📚 Core layer for base domain, utils, and data sources │ │ │ ├── utility/ # 🔧 Utility helpers (formatters, validators, etc.) │ │ │ ├── domain/ # 📦 Base models like pagination, error handling │ │ │ ├── data/ # 🗃️ Base local + remote source contracts or shared logic │ │ │ └── platform/ # 🌍 Expect interfaces for platform-specific functionality │ │ │ │ ├── ui/ # 🧱 Reusable UI components │ │ │ ├── Button/ │ │ │ ├── Dialog/ │ │ │ ├── Layout/ │ │ │ └── Theme/ # 🎨 Theme definitions (Typography, Colors, Dimens) │ │ │ │ └── feature/ # 🌟 Feature modules – each screen or flow has its own folder │ │ ├── pokemon/ # 🐱 Pokemon feature (MVI pattern) │ │ │ ├── domain/ # 🔁 Business logic interfaces & models │ │ │ ├── data/ # 💾 Repository, fake/local/remote data │ │ │ ├── presentation/ # 🎭 UI state, event, screen, and ViewModel │ │ │ └── di/ # 🧩 Feature-specific DI │ │ │ │ ├── setting/ # ⚙️ App settings (e.g., theme selection) │ │ │ └── presentation/ # 🎭 UI state + screen for settings │ │ │ │ └── sample_example/ # 🧪 Optional example/template feature │ │ ├── presentation/ │ │ └── domain/ │ ├── commonTest/ # 🧪 Shared unit tests │ │ └── com/example/cmp_mvi_template/ │ │ └── ComposeAppCommonTest.kt # ✅ Sample shared test └── CMP-MVI-Template/ ├── composeApp/ │ ├── setting.preferences_pb │ ├── build.gradle.kts │ └── src/ │ ├── androidMain/ │ │ ├── AndroidManifest.xml │ │ ├── res/ │ │ │ └── values/ │ │ │ └── strings.xml │ │ │ │ │ └── kotlin/ │ │ └── com/ │ │ └── example/ │ │ └── cmp_mvi_template/ │ │ ├── MyApplication.kt │ │ ├── MainActivity.kt │ │ └── core/ │ │ └── platform/ │ │ ├── theme/ │ │ │ └── PlatformTheme.android.kt │ │ ├── datastore/ │ │ │ └── createDataStore.android.kt │ │ ├── toast/ │ │ │ ├── AndroidToastManager.kt │ │ │ └── ToastManager.android.kt │ │ ├── http_engine/ │ │ │ └── GetHttpClientEngine.android.kt │ │ └── database/ │ │ └── getDatabaseBuilder.android.kt │ ├── commonMain/ │ │ ├── composeResources/ │ │ │ ├── values/ │ │ │ │ └── strings.xml │ │ │ └── drawable/ │ │ │ └── compose-multiplatform.xml │ │ └── kotlin/ │ │ └── com/ │ │ └── example/ │ │ └── cmp_mvi_template/ │ │ ├── di/ │ │ │ ├── initKoin.kt │ │ │ └── CoreModule.kt │ │ ├── app/ │ │ │ ├── presentation/ │ │ │ │ ├── App.kt │ │ │ │ ├── AppThemeState.kt │ │ │ │ └── AppViewModel.kt │ │ │ └── di/ │ │ │ └── AppModule.kt │ │ ├── core/ │ │ │ ├── utility/ │ │ │ │ ├── ComposableExtensions.kt │ │ │ │ ├── UiText.kt │ │ │ │ ├── IconResource.kt │ │ │ │ ├── AppLogger.kt │ │ │ │ └── StateController.kt │ │ │ ├── domain/ │ │ │ │ ├── DataErrorToUiTextExtension.kt │ │ │ │ ├── Paginator.kt │ │ │ │ ├── DataError.kt │ │ │ │ ├── Error.kt │ │ │ │ └── ResultWrapper.kt │ │ │ ├── data/ │ │ │ │ ├── datastore/ │ │ │ │ │ ├── ThemeMode.kt │ │ │ │ │ └── ThemePreferences.kt │ │ │ │ ├── local/ │ │ │ │ │ ├── PokemonDatabase.kt │ │ │ │ │ ├── PokemonDatabaseConstructor.kt │ │ │ │ │ └── dao/ │ │ │ │ │ └── PokemonDao.kt │ │ │ │ └── network/ │ │ │ │ ├── NetworkExtensions.kt │ │ │ │ └── HttpClientFactory.kt │ │ │ └── platform/ │ │ │ ├── theme/ │ │ │ │ └── PlatformTheme.kt │ │ │ ├── datastore/ │ │ │ │ ├── createDataStore.kt │ │ │ │ └── AppSettings.kt │ │ │ ├── toast/ │ │ │ │ ├── ToastManager.kt │ │ │ │ ├── ToastManagerFactory.kt │ │ │ │ └── ToastDuration.kt │ │ │ ├── http_engine/ │ │ │ │ └── GetHttpClientEngine.kt │ │ │ └── database/ │ │ │ └── getDatabaseBuilder.kt │ │ ├── ui/ │ │ │ ├── theme/ │ │ │ │ ├── AnnotationPreview.kt │ │ │ │ ├── Color.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ ├── dialog/ │ │ │ │ ├── ToastPopup.kt │ │ │ │ └── LoadingDialog.kt │ │ │ ├── navigation/ │ │ │ │ ├── AppDestination.kt │ │ │ │ ├── AdaptiveNavigation.kt │ │ │ │ ├── NavigationExtensions.kt │ │ │ │ └── bottom_navigation_bar/ │ │ │ │ ├── NavigationItem.kt │ │ │ │ └── BottomNavigationBar.kt │ │ │ ├── layout/ │ │ │ │ └── ErrorMessageLayout.kt │ │ │ └── component/ │ │ │ ├── badges/ │ │ │ │ └── Badges.kt │ │ │ ├── text/ │ │ │ │ └── Text.kt │ │ │ ├── radio_button/ │ │ │ │ └── RadioButton.kt │ │ │ ├── divider/ │ │ │ │ └── Divider.kt │ │ │ ├── navigation_bar/ │ │ │ │ └── NavigationBar.kt │ │ │ ├── progress_indicator/ │ │ │ │ └── ProgressIndicators.kt │ │ │ ├── button/ │ │ │ │ └── Button.kt │ │ │ ├── switch_custom/ │ │ │ │ └── Switch.kt │ │ │ ├── slider/ │ │ │ │ └── Slider.kt │ │ │ ├── icon_button/ │ │ │ │ └── IconButton.kt │ │ │ ├── checkbox/ │ │ │ │ └── Checkbox.kt │ │ │ ├── fab/ │ │ │ │ └── Fab.kt │ │ │ ├── segmented_button/ │ │ │ │ └── SegmentedButton.kt │ │ │ ├── bottom_app_bar/ │ │ │ │ └── BottomAppBar.kt │ │ │ └── top_app_bar/ │ │ │ └── TopAppBar.kt │ │ └── feature/ │ │ ├── setting/ │ │ │ ├── di/ │ │ │ │ └── SettingModule.kt │ │ │ └── presentation/ │ │ │ ├── screen/ │ │ │ │ ├── SettingScreen.kt │ │ │ │ ├── SettingEvent.kt │ │ │ │ ├── SettingState.kt │ │ │ │ ├── SettingViewModel.kt │ │ │ │ └── SettingEffect.kt │ │ │ └── component/ │ │ │ ├── DynamicThemeToggleAndroidOnly.kt │ │ │ ├── ThemeSelectionRow.kt │ │ │ ├── ThemeSelectionDialog.kt │ │ │ └── ThemePreview.kt │ │ ├── pokemon/ │ │ │ ├── di/ │ │ │ │ └── PokemonModule.kt │ │ │ ├── presentation/ │ │ │ │ ├── component/ │ │ │ │ │ ├── PokemonCard.kt │ │ │ │ │ └── ShimmerEffect.kt │ │ │ │ ├── pokemon_details/ │ │ │ │ │ ├── screen/ │ │ │ │ │ │ ├── PokemonDetailsViewModel.kt │ │ │ │ │ │ ├── PokemonDetailsEvent.kt │ │ │ │ │ │ ├── PokemonDetailsScreen.kt │ │ │ │ │ │ ├── PokemonDetailsEffect.kt │ │ │ │ │ │ └── PokemonDetailsState.kt │ │ │ │ │ └── component/ │ │ │ │ │ ├── PokemonAbilitiesSection.kt │ │ │ │ │ ├── PokemonDetailsContent.kt │ │ │ │ │ ├── PokemonStatsSection.kt │ │ │ │ │ ├── PokemonBasicInfoSection.kt │ │ │ │ │ └── PokemonImageSection.kt │ │ │ │ ├── pokemon_list/ │ │ │ │ │ └── screen/ │ │ │ │ │ ├── PokemonListEvent.kt │ │ │ │ │ ├── PokemonListViewModel.kt │ │ │ │ │ ├── PokemonListScreen.kt │ │ │ │ │ ├── PokemonListEffect.kt │ │ │ │ │ └── PokemonListState.kt │ │ │ │ └── favorites/ │ │ │ │ ├── screen/ │ │ │ │ │ ├── FavoritesState.kt │ │ │ │ │ ├── FavoritesScreen.kt │ │ │ │ │ ├── FavoritesEffect.kt │ │ │ │ │ ├── FavoritesEvent.kt │ │ │ │ │ └── FavoritesViewModel.kt │ │ │ │ └── component/ │ │ │ │ ├── SwipeToDeletePokemonCard.kt │ │ │ │ ├── FavoritesListContent.kt │ │ │ │ └── EmptyFavoritesScreen.kt │ │ │ ├── domain/ │ │ │ │ ├── usecase/ │ │ │ │ │ ├── GetFavoritesPokemonUseCase.kt │ │ │ │ │ ├── GetPokemonDetailsUseCase.kt │ │ │ │ │ ├── CheckIfFavoriteUseCase.kt │ │ │ │ │ ├── GetPokemonListUseCase.kt │ │ │ │ │ └── ToggleFavoriteUseCase.kt │ │ │ │ ├── entity/ │ │ │ │ │ ├── PokemonEntity.kt │ │ │ │ │ └── Pokemon.kt │ │ │ │ └── repository/ │ │ │ │ └── PokemonRepository.kt │ │ │ └── data/ │ │ │ ├── mapper/ │ │ │ │ └── toDomain.kt │ │ │ ├── repository/ │ │ │ │ └── PokemonRepositoryImpl.kt │ │ │ └── remote/ │ │ │ ├── api/ │ │ │ │ ├── PokemonApiServiceImpl.kt │ │ │ │ └── PokemonApiService.kt │ │ │ └── dto/ │ │ │ └── PokemonListResponseDto.kt │ │ └── sample_example/ │ │ ├── di/ │ │ │ └── SampleExampleModule.kt │ │ └── presentation/ │ │ └── screen/ │ │ ├── SampleEffect.kt │ │ ├── SampleExampleScreen.kt │ │ ├── SampleEvent.kt │ │ ├── SampleState.kt │ │ └── SampleExampleViewModel.kt │ ├── desktopMain/ │ │ └── kotlin/ │ │ └── com/ │ │ └── example/ │ │ ├── .DS_Store │ │ └── cmp_mvi_template/ │ │ ├── .DS_Store │ │ ├── main.kt │ │ └── core/ │ │ └── platform/ │ │ ├── theme/ │ │ │ └── PlatformTheme.desktop.kt │ │ ├── datastore/ │ │ │ └── createDataStore.desktop.kt │ │ ├── toast/ │ │ │ ├── RoundedPanel.kt │ │ │ ├── ToastManager.desktop.kt │ │ │ └── DesktopToastManager.kt │ │ ├── http_engine/ │ │ │ └── GetHttpClientEngine.desktop.kt │ │ └── database/ │ │ └── getDatabaseBuilder.desktop.kt │ ├── commonTest/ │ │ └── kotlin/ │ │ └── com/ │ │ └── example/ │ │ └── cmp_mvi_template/ │ │ └── ComposeAppCommonTest.kt │ └── iosMain/ │ └── kotlin/ │ └── com/ │ └── example/ │ └── cmp_mvi_template/ │ ├── MainViewController.kt │ └── core/ │ └── platform/ │ ├── theme/ │ │ └── PlatformTheme.ios.kt │ ├── datastore/ │ │ └── createDataStore.ios.kt │ ├── toast/ │ │ ├── ToastManager.ios.kt │ │ └── IosToastManager.kt │ ├── http_engine/ │ │ └── GetHttpClientEngine.ios.kt │ └── database/ │ └── getDatabaseBuilder.ios.kt Here are the upcoming tasks and feature enhancements planned for the project:
-
Koin Annotations Integration
- Use Koin Annotation processing (@Single, @Factory, etc.) to simplify and reduce boilerplate for dependency injection.
-
🔄 GraphQL Support for Pokémon API
- Implement a GraphQL version of the Pokémon API using Ktor Client. Will be explored in a separate branch for experimentation.
-
🗃️ SQLDelight Sample Integration
- Integrate SQLDelight only as a working code sample in a separate module/branch.
- Purpose: Keep reusable code ready for future use or cross-platform Kotlin projects.
- ✅ Room will continue as the primary local storage solution for this app.
-
⚙️ Dev Tooling Scripts (Automation)
- Build Gradle or Kotlin-based scripts for:
- 🔁 Renaming package names along with folder structure
- 📦 Creating distributable builds per platform
- 🚀 Auto-generating feature modules with basic files (UI, ViewModel, State, Events) by providing just a feature name
- Build Gradle or Kotlin-based scripts for:
-
🧩 Component Showcase Screen
- All UI components are implemented but not visible inside the app.
- A new Component Explorer Screen will be added where:
- You can view all components (buttons, cards, inputs, etc.)
- Helpful for testing and design consistency on a real device
-
🧪 Unit & Instrumented Testing
- Add unit tests for core business logic and ViewModels
- Add instrumented UI tests for critical user flows
- Integrate test coverage tools for quality tracking
-
📱 Maestro Integration (UI Flow Testing)
- Set up Maestro to define UI flow test scripts
Feel free to contribute to this project by submitting issues, pull requests, or providing valuable feedback. Your contributions are always welcome! 🙌
Give a ⭐️ if this project helped you!
Your generosity is greatly appreciated! Thank you for supporting this project.
Meet







































