https://github.com/cerezo074/dailypulse
A Kotlin Multiplatform news app for Android and iOS
https://github.com/cerezo074/dailypulse
android clean-architecture coroutines dependency-injection flow jetpack-compose koin kotlin-multiplatform mvvm navigationpath skie sqldelight structured-concurrency swift swiftui
Last synced: 5 days ago
JSON representation
A Kotlin Multiplatform news app for Android and iOS
- Host: GitHub
- URL: https://github.com/cerezo074/dailypulse
- Owner: cerezo074
- Created: 2025-08-09T12:18:24.000Z (10 months ago)
- Default Branch: main
- Last Pushed: 2026-04-02T12:05:09.000Z (2 months ago)
- Last Synced: 2026-04-03T01:50:56.429Z (2 months ago)
- Topics: android, clean-architecture, coroutines, dependency-injection, flow, jetpack-compose, koin, kotlin-multiplatform, mvvm, navigationpath, skie, sqldelight, structured-concurrency, swift, swiftui
- Language: Kotlin
- Homepage:
- Size: 62.5 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# DailyPulse π°
A news application built with **Kotlin Multiplatform (KMP)** that fetches business news from the News API and displays them on both Android and iOS platforms.
## π Features
- **Cross-platform**: Single codebase for Android and iOS
- **News API integration**: Fetches latest business news
- **Offline caching**: SQLDelight database for local storage
- **Pull-to-refresh**: Native refresh on both platforms
- **Modern UI**: Jetpack Compose (Android) and SwiftUI (iOS)
## ποΈ Architecture
### Core Technologies
- **Kotlin Multiplatform (KMP)** - Cross-platform development
- **Kotlin Coroutines & Flow** - Async operations and reactive streams
- **[KMP-ObservableViewModel](https://github.com/rickclephas/KMP-ObservableViewModel)** - Shared `ViewModel` base class so Kotlin view models integrate with SwiftUI (and AndroidX lifecycle) without a manual Swift wrapper
- **[KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)** - Gradle plugin + runtime that generates Swift-friendly APIs for `Flow` and `suspend` (e.g. `asyncSequence`, `asyncFunction`) from annotated Kotlin APIs
- **Koin** - Dependency injection
- **Ktor** - HTTP client
- **SQLDelight** - Type-safe database
- **BuildKonfig** - API key management
### Project Structure
```
DailyPulse/
βββ shared/ # Shared KMP code
β βββ articles/ # News feature
β β βββ di/ # Feature dependency injection
β β βββ presentation/ # ViewModels and UI state
β β βββ services/ # Network, persistence, repository
β β βββ use_cases/ # Business logic
β βββ di/ # Shared dependency injection modules
β βββ db/ # Database layer (SQLDelight)
β βββ utils/ # Cross-platform utilities
βββ androidApp/ # Android-specific code
β βββ screens/ # Compose UI screens
β βββ di/ # Android DI modules
β βββ MainActivity.kt # Android entry point
βββ iosApp/ # iOS-specific code
βββ Screens/ # SwiftUI screens
βββ Navigation/ # Navigation coordinators
βββ iOSApp.swift # iOS entry point
```
## π οΈ Setup
### Development Environment
- **Android Studio** with Kotlin Multiplatform plugin (Hedgehog or newer recommended)
- **Xcode** (version 14+ should work, 15+ recommended for iOS development)
- **Kotlin** (see `gradle/libs.versions.toml`; project tracks current Kotlin release, e.g. **2.3.20**)
- **JDK 17+** (required for Kotlin 2.0+)
### Setting Up Kotlin Multiplatform
#### Install KMP Plugin in Android Studio
1. Open **Android Studio**
2. Go to **File β Settings** (Windows/Linux) or **Android Studio β Preferences** (macOS)
3. Navigate to **Plugins**
4. Search for **"Kotlin Multiplatform"**
5. Click **Install** and restart Android Studio
#### Troubleshooting KMP Setup (Optional)
If you encounter issues with KMP setup, you can use the KMP doctor tool:
```bash
# Install Kotlin (includes KMP doctor)
curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
sdk install kotlin
# Run KMP doctor to diagnose issues
kotlin doctor
```
*Note: This is only needed if you experience setup problems - Android Studio handles KMP setup automatically in most cases.*
### 1. Clone and Setup API Key
```bash
git clone
cd DailyPulse
```
Create `local.properties` in the project root:
```properties
NEWS_API_KEY=your_news_api_key_here
```
**Get your free API key at**: [NewsAPI.org](https://newsapi.org/register)
### 2. Build and Run
#### Android
1. Open the project in **Android Studio**
2. Wait for project sync
3. Run the app
#### iOS
1. Open in **Xcode**:
```bash
open iosApp/iosApp.xcodeproj
```
2. Add Swift packages if prompted (see **iOS packages** below)
3. Wait for indexing
4. Run the app
## π± Platform Implementation
### Android
- **Jetpack Compose**: UI with Material Design 3
- **Navigation Compose**: Simple navigation with enum-based routes
- **KMP-ObservableViewModel**: Shared `ViewModel` with `viewModelScope` (same pattern as AndroidX `ViewModel`)
- **Koin**: Dependency injection with `koinViewModel()`
- **Pull-to-refresh**: Material 3 `pullToRefresh` components
### iOS
- **SwiftUI**: Native iOS UI with TabView
- **NavigationStack**: Coordinator pattern with NavigationPath
- **Async/Await**: Structured concurrency with Task
- **@MainActor**: Main thread safety for UI updates
- **KMP-ObservableViewModel + SwiftUI**: `@StateViewModel` / `@ObservedViewModel` instead of wrapping the Kotlin VM in a separate `ObservableObject`
- **KMP-NativeCoroutines**: `asyncFunction` / `asyncSequence` for `suspend` and `Flow` from Kotlin
- **Pull-to-refresh**: SwiftUI `.refreshable` modifier
### Swift: ViewModel observable library (brief)
The iOS app depends on **[KMP-ObservableViewModel](https://github.com/rickclephas/KMP-ObservableViewModel)** via Swift Package Manager (`KMPObservableViewModelSwiftUI`, `KMPObservableViewModelCore`). A small bridge file declares that the Kotlin base class conforms to the libraryβs `ViewModel` protocol so SwiftUI property wrappers work.
On the Kotlin side, feature view models extend `com.rickclephas.kmp.observableviewmodel.ViewModel` and use **`MutableStateFlow(viewModelScope, initial)`** from that library so state updates propagate to SwiftUI. **`@NativeCoroutinesState`** on public `StateFlow` properties (from **KMP-NativeCoroutines**) exposes them as regular Swift properties (e.g. `viewModel.contentState`) instead of raw Obj-C `StateFlow` types.
Together: **ObservableViewModel** owns lifecycle and observation wiring; **NativeCoroutines** shapes the generated Swift API for flows and suspend functions.
## ποΈ Architecture Patterns
### Dependency Injection with Koin
```kotlin
val articlesModule = module {
single { ArticlesRemoteDataService(get()) }
single { ArticlesDataSource(get()) }
single { ArticlesRepository(get(), get()) }
single { ListArticleUseCase(get()) }
single { ArticlesViewModel(get()) }
}
```
### Repository Pattern
```kotlin
class ArticlesRepository(
private val dataSource: ArticlesDataSource,
private val remoteDataService: ArticlesRemoteDataService
) {
suspend fun getArticles(forceFetch: Boolean): List {
// Implements caching strategy
}
}
```
### State Management (shared ViewModel)
Kotlin view models subclass **KMP-ObservableViewModel**βs `ViewModel` and use its `viewModelScope` and `MutableStateFlow(viewModelScope, β¦)`. **`@NativeCoroutinesState`** marks `StateFlow`s that Swift should consume as generated properties.
```kotlin
class ArticlesViewModel(
private val listArticleUseCase: ListArticleUseCase
) : ViewModel() {
private val _contentState = MutableStateFlow(viewModelScope, ArticlesState(loading = true))
@NativeCoroutinesState
val contentState: StateFlow = _contentState
}
```
```swift
import SwiftUI
import shared
import KMPObservableViewModelSwiftUI
import KMPNativeCoroutinesAsync
struct ArticlesScreen: View {
@StateViewModel var viewModel = ArticlesInjector().viewModel
var body: some View {
// viewModel.contentState is driven by Kotlin StateFlow + NativeCoroutines
Text("\(viewModel.contentState.articles.count)")
}
}
```
## ποΈ Database
### SQLDelight Configuration
```kotlin
sqldelight {
databases {
create(name = "DailyPulseDatabase") {
packageName.set("com.eli.examples.dailypulse.db")
}
}
}
```
## π Network Layer
### Ktor HTTP Client
```kotlin
class ArticlesRemoteDataService(
private val httpClient: HttpClient,
private val configuration: ArticlesConfiguration = ArticlesConfiguration.DEFAULT_CONFIG
) {
suspend fun fetchArticles(): List {
val response: ArticlesResponse = httpClient.get(allArticlesURL).body()
return response.articles
}
}
```
## π¦ Dependencies
Versions are defined in **`gradle/libs.versions.toml`** and applied from the version catalog in Gradle.
### Core (KMP `shared` module)
- **Kotlin**: 2.3.20
- **Kotlinx Coroutines**: 1.10.1
- **KMP-NativeCoroutines** (plugin + `kmp-nativecoroutines-core`): 1.0.2
- **KMP-ObservableViewModel** (`kmp-observableviewmodel-core`): 1.0.3
- **Kotlinx Serialization JSON**: 1.8.1
- **Ktor**: 2.3.5
- **Koin**: 4.0.4
- **SQLDelight**: 2.0.2
### iOS (Swift Package Manager, Xcode)
- **[KMP-ObservableViewModel](https://github.com/rickclephas/KMP-ObservableViewModel)** β e.g. `1.0.3` (`KMPObservableViewModelSwiftUI`, `KMPObservableViewModelCore`)
- **[KMP-NativeCoroutines](https://github.com/rickclephas/KMP-NativeCoroutines)** β Async / Combine helpers that match the Gradle plugin (e.g. `KMPNativeCoroutinesAsync`)
### Android
- **Jetpack Compose**: 1.5.4
- **Material Design 3**: 1.3.0
- **Navigation Compose**: 2.8.9
- **Coil**: 2.5.0
---
**Built with β€οΈ using Kotlin Multiplatform**