feat(agenda): Implement Part 1 - DTOs and Dependencies#7
feat(agenda): Implement Part 1 - DTOs and Dependencies#7
Conversation
- Introduce domain models for the agenda feature using an `AgendaItem` sealed interface with `Event`, `Task`, and `Reminder` types. - Create DTOs (`AgendaResponseDto`, `EventDto`, `TaskDto`, `ReminderDto`) for deserializing the API response. - Add mapper functions to convert network DTOs to their corresponding domain models. - Define the `AgendaApi` interface with a `getAgenda` endpoint to fetch agenda items for a specific time. - Implement `GetAgendaUseCase` to orchestrate fetching agenda data from the repository for a given date. - Define a custom `AgendaError` enum for tailored error handling within the agenda feature. - Add an `AgendaEvent` sealed interface to model user interactions for the upcoming UI layer.
- Creates all required Data Transfer Objects (DTOs) for the agenda feature, including for events, tasks, reminders, photos, and attendees. This establishes the data contract for the remote API. - Adds Coil and Accompanist as project dependencies to prepare for future UI work involving image loading and permissions.
| import io.mockk.coEvery | ||
| import io.mockk.coVerify | ||
| import io.mockk.mockk |
There was a problem hiding this comment.
I personally got away from using mocks as much as possible. They're good in two scenarios:
- You're testing legacy code which isn't written with testability in mind.
- You're testing code you don't own (e.g. you need to make a library's function return something specific and it's not extendable)
In all other cases, I found fakes/stubs much more reliable and less error prone. I can't count how often my tests broke at some point, not because there was a bug, but because the mock itself broke (for example when the mocked functionality changed/was extended, there were unmocked function calls, etc.).
Having a fake instead will make you implement new functionality before you can even run your tests and also doesn't rely on that much 3rd party library magic as it's just a plain Kotlin class.
| sealed interface AgendaItem { | ||
| val id: String | ||
| val title: String | ||
| val description: String? | ||
| val time: String? | ||
| val remindAt: String | ||
|
|
||
| data class Event( | ||
| override val id: String, | ||
| override val title: String, | ||
| override val description: String?, | ||
| override val time: String? = null, | ||
| override val remindAt: String, |
There was a problem hiding this comment.
Alternatively, you could think about creating a sealed hierarchy just for the differences of the agenda items. This is what I mean:
sealed interface AgendaItemDetails {
data class Event(
val toTime: LocalDateTime,
val attendees: List<Attendee>,
val photos: List<Photo>
): AgendaItemDetails
data class Task(
val isDone: Boolean
): AgendaItemDetails
data object Reminder: AgendaItemDetails
}That way, you just need type-checks when updating properties that are individual to one of the agenda item types, but you don't need this for updating shared properties anymore like the title:
data class AgendaDetailState(
val title: String = "",
val description: String = "",
val time: LocalDateTime = LocalDateTime.now(),
val details: AgendaItemDetails? = null
)Updating then works like this:
// Updating a shared property
state = state.copy(title = "new title")
// Updating an individual property
state = state.copy(
details = detailsAsEvent()?.copy(attendees = newAttendees)
)- Replace AgendaItem sealed interface with data class using composition - Create AgendaItemDetails sealed interface for type-specific properties only - Event's 'from' time becomes the common 'time' property; 'to' is Event-specific - Update Mappers to return unified AgendaItem with appropriate details - Add LocalDateTime conversion helpers for API timestamp parsing - Update AgendaRepository interface with unified AgendaItemResult type alias - Implement DefaultAgendaRepository with runtime type validation - Fix AgendaViewModel to properly handle Result<List<AgendaItem>, Error> - Add error field to AgendaState for error handling Benefits of composition over inheritance: - Common properties defined once (no repeated 'override val') - 95% of UI code uses common properties without type checks - data object Reminder elegantly expresses "no unique properties" - Sorting/filtering works directly on common 'time' property
# Conflicts: # features/agenda/build.gradle.kts
- Refactor Gradle scripts to source `API_KEY` and `BASE_URL` from root project extension properties (`project.ext`). - Unify the way build configuration fields are declared across the `core:data`, `features:auth`, and `features:agenda` modules. - Fix an issue where the `API_KEY` string value was not properly quoted in the generated `BuildConfig` file, causing build failures.
This MR introduces the foundational data structures and required dependencies for the new Agenda
feature. It constitutes Part 1 of the feature implementation, keeping the change set small and
focused for easier review.
Key Changes:
Data Transfer Objects (DTOs):
Attendees).
Dependencies:
selection and other runtime permissions.