StarGazer is a modern Kotlin Multiplatform (KMM) application for Android and iOS that showcases space-related content using the spaceflightnewsapi.net API. Built with cutting-edge technologies and a clean architecture pattern, it demonstrates best practices for cross-platform mobile development.
- π Cross-platform: Native experience on both Android and iOS from a single codebase
- π¨ Modern UI: Clean, intuitive interface built entirely with Jetpack Compose Multiplatform
- π° Three Content Sections:
- Articles: Curated list of space-related articles
- Blogs: Space exploration blog posts
- Reports: In-depth reports on space missions and discoveries
- π Advanced Filtering: Filter content by news source and content type
- π± Detailed Views: Embedded WebView for full article content
- π Dark Mode: System-aware theme with manual toggle
- β‘ Reactive Architecture: Unidirectional Data Flow (UDF) with Molecule
- π§ͺ Comprehensive Testing: Unit and UI tests with Turbine and Mokkery
StarGazer implements a hybrid ViewModel + Presenter + Molecule architecture that combines the best of both worlds:
βββββββββββββββββββββββββββββββββββββββββββ
β UI Layer (Compose) β
β β’ Renders state only β
β β’ Emits events β
β β’ Observes StateFlow<State> β
β β’ Observes Flow<Effect> for navigation β
ββββββββββββββββ¬βββββββββββ²ββββββββββββββββ
β Events β State/Effects
ββββββββββββββββΌβββββββββββ΄ββββββββββββββββ
β ViewModel β
β β’ Manages lifecycle (viewModelScope) β
β β’ Channels events from UI β
β β’ Exposes StateFlow via launchMolecule β
β β’ Processes effects from Presenter β
ββββββββββββββββ¬βββββββββββ²ββββββββββββββββ
β Events β State/Effects
ββββββββββββββββΌβββββββββββ΄ββββββββββββββββ
β Presenter (@Composable) β
β β’ Business logic with Molecule β
β β’ Receives Flow<Event> β
β β’ Returns Pair<State, Flow<Effect>> β
β β’ Pure reactive logic (testable) β
ββββββββββββββββ¬βββββββββββ²ββββββββββββββββ
β Calls β Results
ββββββββββββββββΌβββββββββββ΄ββββββββββββββββ
β Domain/Data Layer β
β β’ Services, Repositories, DataStore β
βββββββββββββββββββββββββββββββββββββββββββ
- ViewModel provides
viewModelScopefor automatic lifecycle management - Presenter contains pure reactive logic with Molecule (testable without UI)
- Hybrid approach leverages the strengths of both without duplicating responsibilities
- Scalable and allows creating complex reusable Presenters
StarGazer/
βββ composeApp/ # Main application (Android/iOS)
β βββ App.kt # Entry point with Koin setup
β βββ navigation/ # Type-Safe Navigation Graph
β βββ ui/components/ # Shared components (TopBar with Presenter)
β
βββ posts/ # Feature module: Posts
β βββ data/ # Service implementations
β βββ domain/ # Interfaces and models
β βββ ui/
β β βββ posts/ # Posts screen (Presenter + ViewModel)
β β βββ settings/ # Settings dropdown (Presenter + ViewModel)
β β βββ webview/ # WebView for article details
β βββ di/ # Koin dependency injection
β
βββ ds/ # Design System (colors, typography, theme)
βββ network/ # Ktor HTTP client configuration
βββ storage/ # DataStore for preferences
βββ utils/ # Multiplatform utilities
- Kotlin 2.2.21 - Modern, concise, and safe programming language
- Kotlin Multiplatform - Share code between Android and iOS
- Jetpack Compose Multiplatform 1.9.2 - Declarative UI framework
- Molecule 2.2.0 - Build a StateFlow stream using Jetpack Compose
- Compose Navigation - Type-safe navigation between screens
- Unidirectional Data Flow (UDF) - Predictable state management
- Ktor 3.3.1 - Multiplatform HTTP client
- Kotlinx Serialization - JSON parsing
- DataStore 1.1.7 - Preferences and settings persistence
- Koin 4.1.1 - Lightweight DI framework with KMM support
- Coil 3.x - Image loading with caching
- Material3 - Modern Material Design components
- Kotlin Test - Multiplatform testing framework
- Turbine 1.2.1 - Testing library for Kotlin Flows
- Mokkery 2.10.2 - Modern mocking library (replaces unmaintained MockK)
- Compose UI Test - UI testing for Compose
StarGazer implements a comprehensive testing approach covering all architecture layers:
Tests for business logic using Molecule and Turbine to validate state emissions and effects:
@Test
fun emitsLoadingStateInitiallyAndThenContentWithPosts() = runTest {
// Given - PostService returns a list of posts
val mockResponse = Posts(count = 2, results = mockPosts)
every { suspend { postService.getArticles(...) } } returns Result.success(mockResponse)
val flow = moleculeFlow(RecompositionMode.Immediate) {
presenter.present(events)
}
// When - Collecting the state
flow.test {
// Then - First emission should be Loading
val loadingState = awaitItem().first
assertIs<PostState.Loading>(loadingState)
// Then - Second emission should be Content with posts
val contentState = awaitItem().first as PostState.Content
assertEquals(2, contentState.posts.size)
}
}Test Coverage:
- PostPresenterTest - Posts screen presenter logic
- SettingsPresenterTest - Settings presenter logic
- TopBarPresenterTest - TopBar presenter logic
Integration tests for API services using Mokkery for mocking:
- InfoServiceTest - Info API validation
- PostServiceTest - Posts API validation
Compose UI tests validating component behavior and state changes:
- TopBarTest - TopBar states and interactions
- DarkModeItemTest - Dark mode toggle
MockK was discarded because mockk-common (the multiplatform version) has not been maintained since 2022 and causes numerous compatibility issues. Mokkery is the modern, actively maintained alternative with similar API and full KMM support.
This app uses the free and open spaceflightnewsapi.net API to fetch space-related content.
- Android Studio Hedgehog | 2023.1.1 or newer
- Xcode 15+ (for iOS development)
- JDK 17 or higher
- Kotlin 2.2.21
- Open the project in Android Studio
- Select
composeAppconfiguration - Run on an Android device or emulator
- Open the project in Android Studio
- Select
iosAppconfiguration - Run on an iOS simulator or device
Alternatively, open iosApp/iosApp.xcodeproj in Xcode and run from there.
Each feature defines a Contract containing:
State: Immutable UI state (sealed interface)Event: User interactions (sealed interface)Effect: One-time side effects like navigation (sealed interface)
Example from PostContract.kt:
sealed interface PostState {
data object Loading : PostState
data object Error : PostState
data class Content(val posts: List<Post>) : PostState
}
sealed interface PostEvent {
data class OnPostClick(val url: String) : PostEvent
data object LoadNextPage : PostEvent
}
sealed interface PostEffect {
data class NavigateToWebView(val url: String) : PostEffect
}Presenters are @Composable functions that use Molecule to create reactive state:
@Composable
fun present(events: Flow<PostEvent>): Pair<PostState, Flow<PostEffect>> {
var state by remember { mutableStateOf<PostState>(PostState.Loading) }
val effects = remember { MutableSharedFlow<PostEffect>() }
// Reactive business logic here
return state to effects
}ViewModels delegate to Presenters but manage Android lifecycle:
class PostViewModel(private val presenter: PostPresenter) : ViewModel() {
private val events = MutableSharedFlow<PostEvent>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_LATEST
)
private val presentationResult = viewModelScope.launchMolecule(
mode = RecompositionMode.Immediate
) {
presenter.present(events) // β
Solo se llama UNA vez
}
val state: StateFlow<PostState> = presentationResult
.map { it.first }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = PostState.Loading
)
val effects: Flow<PostEffect> = presentationResult
.flatMapLatest { it.second }
}- Follow Kotlin Coding Conventions
- Use meaningful variable and function names
- Add KDoc comments for public APIs
- Keep functions small and focused
- Create feature module in appropriate package
- Define Contract (State, Event, Effect)
- Implement Presenter with business logic
- Create ViewModel as lifecycle bridge
- Build Composable UI
- Write tests (Presenter, Service, UI)
- Add to navigation graph
- Hybrid ViewModel + Presenter architecture
- Type-safe navigation
- Comprehensive testing with Turbine
- Dark mode support
- Offline caching
- Search functionality
- Favorites/Bookmarks
- Share articles
- Push notifications
- UI/UX improvements
- Performance optimizations
- Additional test coverage
- CI/CD pipeline
This project is available under the MIT License.
Contributions are welcome! Please feel free to submit a Pull Request.
Built with β€οΈ using Kotlin Multiplatform







