Skip to content

feat(agenda): Implement Part 1 - DTOs and Dependencies#7

Open
DemisChan wants to merge 6 commits intomainfrom
featureAgenda
Open

feat(agenda): Implement Part 1 - DTOs and Dependencies#7
DemisChan wants to merge 6 commits intomainfrom
featureAgenda

Conversation

@DemisChan
Copy link
Copy Markdown
Owner

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:

  1. Data Transfer Objects (DTOs):

    • Creates all necessary DTOs for Events, Tasks, Reminders, and related entities (Photos,
      Attendees).
    • This establishes the data contract with the remote API as defined in the requirements.
  2. Dependencies:

    • Adds io.coil-kt:coil-compose to support future image loading.
    • Adds com.google.accompanist:accompanist-permissions to prepare for handling photo
      selection and other runtime permissions.

Demis Lavrentidis added 2 commits January 11, 2026 08:34
- 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.
Comment on lines +5 to +7
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally got away from using mocks as much as possible. They're good in two scenarios:

  1. You're testing legacy code which isn't written with testability in mind.
  2. 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.

Comment on lines +3 to +15
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,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
)

Demis Lavrentidis added 4 commits January 22, 2026 10:00
- 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants