This project is a proof of concept written entirely with recent models focused on efficiency like Google's Gemini 3.5 Flash and MAI-Code-1-Flash. The main goal was to evaluate the models and different ways to work with them (Antigravity, Gemini CLI, GitHub Copilot and so on).
A modern, fluid, and high-performance Kotlin Multiplatform (KMP) application utilizing the public Unsplash API to deliver a rich, responsive photo discovery experience on Android, iOS, and macOS.
The Unsplash Image Feed App leverages native platform UI toolkits (Jetpack Compose on Android and SwiftUI on iOS & macOS) bound to a shared multiplatform business/presentation layer. The application fully implements the public (unauthenticated) features of the Unsplash API across 5 key phases:
- Photo Details, EXIF & Statistics: Detailed inspect views featuring camera configuration data (shutter speed, aperture, ISO), geographical tags mapped natively, and trend line charts showing photo popularity.
- Advanced Search & Filters: Universal search bar supporting debounced text queries, visual category picker tabs, and sheet filter controls (orientation, color, content filter).
- Curated Collections: Browse, explore, and navigate composite mosaic collection grids, infinite photogrid detail feeds, and related carousels.
- Topics Directory & Category Tabs: Interactive horizontal sliding capsule/pill indicators with elastic animation transitions to toggle the main feed between general editorial and dedicated active topics.
- Photographer Profiles & Insights: Detailed public photographer details containing a collapsing folding header, portfolios segmented across three dynamic feeds, and dual interactive timeline statistics charts (Views and Downloads) supporting drag-scrubbing gestures.
- macOS Native Target: Native desktop application compiling fully with native macOS architectural targets and window interfaces, reusing 100% of the iOS views, presenters, and models under-the-hood.
The codebase is built on the Shared Presenter Pattern, maximizing cross-platform code reuse while retaining completely native rendering and scrolling performance.
┌─────────────────────────────────────────────────────────────┐
│ App View Shells │
│ │
│ ┌───────────────────────────┐ ┌─────────────────────┐ │
│ │ androidApp (Jetpack Comp) │ │ iosApp (SwiftUI) │ │
│ └─────────────┬─────────────┘ └──────────┬──────────┘ │
└─────────────────┼────────────────────────────┼──────────────┘
│ (Collects StateFlow) │ (Exposed CommonFlow)
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ shared Module (Kotlin Multiplatform) │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Presenters │ │
│ │ (FeedPresenter, UserProfilePresenter, etc.) │ │
│ └──────────────────────────┬──────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ UnsplashRepository │ │
│ └──────────────────────────┬──────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ UnsplashApiClient │ │
│ │ (Ktor HTTP Engine, ContentNegotiation) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
sharedmodule (commonMain): Exposes highly-optimized, stateful presenters mapping Ktor API models into declarative KotlinStateobjects. Uses Metro for compile-time Dependency Injection.androidApp: Thin visual representation shell rendered entirely using Jetpack Compose.iosApp: SwiftUI visual shell that binds directly to Shared flows mapped into native Swift variables.
To comply with security best practices, the application loads your API Access Key from a local, git-ignored configuration file at compile-time and injects it into the shared core via BuildKonfig.
- In the root directory of this project, create a file named
local.properties:touch local.properties
- Insert your Unsplash API Client Access Key inside
local.propertiesusing the property name shown below:unsplash.api.key=YOUR_UNSPLASH_ACCESS_KEY_HERE
- macOS (Required to compile the iOS app target)
- Android Studio Koala+ with the Kotlin Multiplatform Mobile (KMM) plugin installed
- Xcode 15+
- JDK 17 or newer
- Open the project folder in Android Studio.
- Wait for Gradle sync to complete.
- Select
androidAppfrom the Run Configurations dropdown. - Choose an Android Emulator or a connected physical device.
- Click the green Run (Play) button.
Alternatively, compile from the command line:
./gradlew :androidApp:assembleDebugThe Shared library is fully configured to compile for iOS Simulator and Device architectures (iosX64, iosArm64, iosSimulatorArm64).
To build the KMP library target directly:
# Compile for iOS Simulator
./gradlew :shared:compileKotlinIosSimulatorArm64
# Generate full debug framework
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64To run the SwiftUI App:
- Ensure
xcodegenis installed. Runcd iosApp && xcodegento generate the Xcode project workspace dynamically. - Open
iosApp/iosApp.xcodeprojin Xcode. - Select either the
iosApp(for iPhone/iPad) ormacosApp(for macOS) scheme in the top toolbar. - Hit Run (⌘R).
If you are on macOS, you can build and run the native macOS application directly using Gradle:
./gradlew :shared:assemble
cd iosApp && xcodegen
xcodebuild -scheme macosApp -destination 'platform=macOS' build
open -a "$(xcodebuild -scheme macosApp -destination 'platform=macOS' -showBuildSettings | awk -F' = ' '/CODESIGNING_FOLDER_PATH/ {print $2}')"To integrate with Android Studio:
- Create a Shell Script run configuration.
- Set Execute:
Script text. - Paste the build/run commands listed above. You can now compile and run macOS app alongside your Android/iOS work directly within Android Studio!
When modifying user feeds or detail interfaces:
- No Local Caching: Never cache Unsplash image files on external servers. Always fetch and display assets utilizing the native URLs returned by the API.
- ixid Preservation: Retain all
ixidtracking parameters in image request strings. - Attribution: Display photographer names and profiles prominently with a UTM backlink pointing to Unsplash (
utm_source=ImageFeedApp&utm_medium=referral). - Download Tracking: Ensure downloading or saving a photo triggers the designated
download_locationcallback to increment the photographer's download stats.