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.
- For Android, run the
composeApp
module by selecting theapp
configuration. If you need help with the configuration, follow this link for android - For iOS, run the
composeApp
module by selecting theiosApp
configuration. 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