Schedrix is a simple calendar scheduling app made with Compose Multiplatform (Android + iOS).
Users can choose a date, pick a time, and confirm the meeting by filling in their name and email. Then, Google Calendar opens in the browser with everything pre-filled.
- Kotlin Multiplatform plugin installed in your Android Studio (for iOS support)
- Xcode installed and configured
- Dark mode support
- Calendar navigation by month
- Validations and error feedback
- Opens Google Calendar with pre-filled info
- API call simulation (ktorfit + ktor)
- Topic animation
- Lottie animations for loading and error
Error + try again
Android
android.error.mov
iOS
Screen.Recording.2025-04-20.at.17.39.05.mov
Dark mode
AndroidWhatsApp.Video.2025-04-20.at.17.23.29.mp4
iOS
Screen.Recording.2025-04-20.at.17.30.47.mov
Redirect to Google Calendar
AndroidWhatsApp.Video.2025-04-20.at.17.16.08.mp4
- MVVM: All screens share the same logic class. I call it
ScreenModelbecause of Voyager, but it's the same idea as a ViewModel. - Abstraction instead of implementations: Interfaces like
AppointmentRepositoryandAppointmentRemoteDataSourceseparate contracts from logic. This keeps the UI decoupled and makes testing easier with fake implementations. - No business logic in UI: The UI just observes state. All logic lives in the ScreenModel.
- Expect/actual: Some features need different platform logic. For example, opening Google Calendar uses an
Intenton Android andUIApplication.sharedApplication.openURLon iOS. I useexpectin common code and implement it withactualon each platform. - ScreenModel tied to screen: Voyager ensures the ScreenModel stays alive with the screen. Using only Koin could cause unwanted recreation on recomposition.
- Visibility control: I used
internalin most files to prevent leaking logic across modules and keep the codebase clean. - No magic numbers/hard-coded text: Colors, strings, content descriptions, and dimensions are declared as constants to avoid hardcoding (which is considered bad practice and... ugly =P).
- Componentization: Components and code are isolated for reuse, following the DRY principle.
- Content description text to non-text components: It's important to guarantee that VoiceOver/Talkback will be able to provide the screen context to user
- BDD-style (
GIVEN,WHEN,THEN) - Uses a fake repository to simulate success, failure and alternative scenarios without relying on a real API
Cases:
- Month navigation
- Fetching and parsing appointments
- Validations (email, name)
- Error handling
- Date/time selection
See: AppointmentScreenModelTest.kt
| Scenario | Description |
|---|---|
| Initial state | Validates current month and timezone |
| Fetch appointments | Updates calendar and disables loading |
| Change to previous month | Navigates to previous month and updates state |
| Change to next month | Navigates to next month and updates state |
| Select date/time | Sets state for confirmation |
| No appointments on date | Keeps selectedDateTimes empty |
| Mixed valid and invalid dates | Only valid dates are parsed |
| Multiple appointments on same day | Displays all available times |
| SelectAppointmentTime | Updates finalSelectedDateTime |
| Retry after error | Resets error state and refetches data |
| Invalid API response | Shows error screen |
| Valid confirmation | Enables "open Google Calendar" option |
| Invalid email format | Blocks confirmation and shows feedback |
| Invalid name input | Blocks confirmation and shows feedback |
| Valid name and email | Triggers Google Calendar integration |
Coverage:
⚠️ I know test coverage matters, but for now I focused on hitting 100% in the presentation layer, mainly in theScreenModel, since that’s where most of the logic lives
To generate this report: ./gradlew :composeApp:koverHtmlReport
| Library | Purpose |
|---|---|
| Voyager | Navigation and screen-scoped ViewModel |
| Koin | Dependency injection |
| Ktor | HTTP client (Android + iOS) |
| Ktorfit | Retrofit-style HTTP with annotations |
| Mockk | Mocks for unit tests |
| Kover | Code coverage report |
| Compottie | Lottie animation in Compose Multiplatform |
| kotlinx-datetime | Multiplatform date and time handling |
- Add local caching + offline support
- Deeplink support
- UI tests
- Previews (it’s tricky in CMP because the preview is incompatible with the commonMain source set, so I had to implement a workaround by creating the preview in the androidMain source set)






