This commit refactors the `QuizScreen` to use a `Column` instead of a `LazyColumn` for its main layout, improving the positioning and sizing of elements. It also introduces an `EvenGrid` composable for displaying choices, ensuring they are evenly distributed. Key changes: - **QuizScreen.kt:** - Changed the main layout from `LazyColumn` to `Column`. - Toolbar, QuestionContent, Choices, Timer, and ContinueButton are now direct children of the `Column`. - `QuestionContent` now uses `fillMaxHeight(0.5f)` to take up half the available height. - `Choices` composable now uses `weight(1f)` to fill remaining space. - Removed `animateContentSize` and `animateItem` modifiers. - Refactored how individual UI sections (toolbar, question, choices, timer, continue button) are structured within the main `Column`. - **components/EvenGrid.kt:** - Added a new reusable `EvenGrid` composable that arranges items into a grid with a specified number of columns, ensuring even distribution. - **components/Choices.kt:** - Replaced `FlowRow` with the new `EvenGrid` composable to display choices. - `ChoiceItem` now uses `fillMaxHeight()` within the `EvenGrid` cell. - Removed explicit height setting for `ChoiceItem`'s `Box`. - **components/QuestionContent.kt:** - `AsyncImage` within `QuestionContent` now uses `weight(1f)` instead of `heightIn(min = 200.dp)`. - **components/TimerBar.kt:** - Removed `coerceIn(0f, 1f)` for `progress` in `fillMaxWidth` as progress should already be within this range. - **QuizScreenViewModel.kt & domain/models/Question.kt:** - Made `Question.choices` non-nullable (`List<Choice>`) and updated the mapper and ViewModel to reflect this. This simplifies null checks for `currentQuestion.choices`. - Accessing `quizState.quiz.questions[currentQuestionIndex]` directly instead of `getOrNull` as the index should always be valid. - **data/mappers/QuizMapper.kt:** - Updated `QuestionDto.toDomain()` to return `orEmpty()` for choices, ensuring a non-null list.
KahootQuiz — Interview Challenge
This project is an implementation for an interview-style challenge. It demonstrates a clean, modular Android architecture with a focus on separation of concerns, convention plugins for Gradle, and pragmatic Kotlin usage.
TL;DR
- Only image media is supported right now.
- Slider question type is not supported.
- There is no end/completion screen yet.
- Errors in ViewModels are caught but not yet handled (no user-facing error states/actions).
Project Overview
- Multi-module, clean architecture:
core/
— common utilities (e.g., networking).domain/
— pure domain models and repository abstractions, domain models.data/
— repository implementations, mappers.ui/
— feature UI modules (e.g.,ui/quiz
).
- Convention plugins are used to centralize and reuse Gradle configuration across modules (see
build-logic/
). - Kotlin-first approach using language features to keep code concise and readable.
How to Build & Run
- Requirements:
- Android Studio
- JDK 21
- Gradle wrapper included
- Steps:
- Open the project in Android Studio.
- Sync Gradle.
- Run the
app
configuration on a device/emulator.
If you prefer the command line: ./gradlew assembleDebug
and then install the generated APK.
Architecture Details
- Data flow follows a standard clean pattern:
domain.repositories.QuizRepository
defines the contract.data.QuizRepositoryImpl
usescore.network.retrofit.QuizApi
+ mappers to producedomain.models.Quiz
.- UI consumes domain via ViewModels and exposes a
UiState
.
- The code emphasizes separation of concerns and testability.
Current Limitations & Known Issues
- Media support:
- Only
image
media is supported in the quiz content. - Other media types are not supported.
- Only
- Question types:
- Slider answers are not supported yet.
- UX flow:
- There is no end/completion screen after the quiz finishes.
- Error handling:
- Exceptions are caught in ViewModels but not handled (no retry, no error UI, no telemetry hooks yet).
Suggested Improvements
-
Introduce a UI-specific model for the Quiz screen
- The domain model
Quiz
is relatively complex and currently used directly inUiState
. - Add a dedicated, lean UI data class that contains only the data relevant to the quiz screen.
- Benefits: Improved clarity for UI developers, simpler previews, easier testing/mocking, and better forward-compatibility when domain evolves.
- The domain model
-
Expand Unit Test Coverage
- Currently there is only one unit test for parsing a sample JSON API response.
- Add tests for:
- ViewModel state transitions (loading/success/error).
- Mapping edge cases (e.g., missing fields, unsupported media types).
- Navigation/flow for various question types.
-
Error Handling Strategy
- Map exceptions to user-friendly UI states with retry actions.
- Add telemetry/logging hooks for observability.
-
Feature Completeness
- Implement slider answer type.
- Add an end/completion screen with score summary and restart/share options.
- Consider support for additional media types (video/audio), with graceful fallbacks.
-
Transitions between questions could be more smooth.
What I’m Happy About
- I created and used convention plugins to reuse modules configuration.
- The architecture is clean with multi-modularity and separation of concerns.
- I leaned into Kotlin ‘sugar’ where it helps readability and conciseness — I love it.
- Configured
Detekt
for static code analysis
Extra: Related Work I Can Share
I can share more complex code from my private app that is published on the Google Play Store. Additionally, I have a secondary project — an AI Agent implemented in TypeScript using Google’s GenKit framework — that prepares content for that app. It leverages multiple models, vector stores, and embeddings to orchestrate cooperative behaviors.
If you’re interested, I can provide a deeper walkthrough, architectural diagrams, or selected code excerpts.
License
This repository is provided as-is for interview and demonstration purposes.