A modern, server-driven quiz application built with Jetpack Compose
QuizzyBee is an Android quiz application that leverages Server-Driven UI (SDUI) architecture with Firebase Remote Config to deliver a dynamic, customizable quiz experience. The app allows for UI updates without requiring app releases, making it perfect for A/B testing and rapid iteration.
- π¨ Dynamic UI - Screens configured via Firebase Remote Config
- π Multiple Categories - Various quiz topics with custom icons
- π Difficulty Levels - Easy, Medium, and Hard challenges
- β±οΈ Timed Questions - Countdown timer for each question
- π Score Tracking - Track your progress and results
- π― Type-Safe SDUI - Screen-specific event types for clean, maintainable code
- π Custom Theme - Vibrant color palette with Material 3 design
Primary: #F0985A (Orange)
Secondary: #F7D14D (Yellow)
Tertiary: #7A532C (Brown)
Surface: #CAE9ED (Light Blue)
Accent: #EFAB31 (Gold)QuizzyBee uses a robust SDUI architecture that allows the entire UI to be configured remotely:
βββββββββββββββββββββββββββββββββββββββ
β Firebase Remote Config (JSON) β
β - Screen Layouts β
β - Component Configuration β
β - Theme Overrides β
ββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββ
β RemoteConfigService β
β - Fetches & Caches Config β
β - Parses JSON to Data Models β
ββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββ
β SduiRenderer<T : SduiEvent> β
β - Type-safe event handling β
β - Dynamic component rendering β
ββββββββββββββββ¬βββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββ
β ComponentFactory β
β - Creates components dynamically β
β - Handles screen-specific events β
βββββββββββββββββββββββββββββββββββββββ
Each screen has its own sealed event interface for type safety:
// Category Screen
sealed interface CategorySduiEvent : SduiEvent {
data class CategorySelected(val categoryId: Int)
data class DifficultySelected(val difficulty: String)
}
// Quiz Screen
sealed interface QuizSduiEvent : SduiEvent {
data class AnswerSelected(val answer: String)
data object TimeUp
}Uses sealed interfaces to prevent impossible states:
sealed interface ConfigState {
data object Loading : ConfigState
data class Loaded(val config: ScreenConfig) : ConfigState
data class Error(val message: String) : ConfigState
}- UI Framework: Jetpack Compose with Material 3
- Architecture: MVVM with Circuit (Presenter pattern)
- Dependency Injection: Hilt
- Networking: Ktor Client
- Database: Room
- Remote Config: Firebase Remote Config
- Serialization: Kotlinx Serialization
- Navigation: Circuit Navigation
- Language: Kotlin
app/
βββ data/
β βββ local/ # Room database, DAOs
β βββ remote/ # Ktor API clients
β βββ sdui/ # SDUI models & RemoteConfigService
βββ domain/
β βββ models/ # Domain entities (Category, Question)
β βββ repository/ # Data repositories
βββ ui/
βββ screens/ # Screen composables & presenters
β βββ category/ # Category selection screen
β βββ quiz/ # Quiz screen
β βββ result/ # Results screen
βββ sdui/ # SDUI system
β βββ components/ # Reusable SDUI components
β βββ ComponentFactory.kt
β βββ ComponentTypes.kt
β βββ SduiRenderer.kt
βββ theme/ # App theming
- Android Studio Hedgehog or later
- JDK 11 or later
- Android SDK 24+
-
Clone the repository
git clone https://github.com/yourusername/quizzybee.git cd quizzybee -
Add Firebase Configuration
- Create a Firebase project
- Download
google-services.json - Place it in
app/directory
-
Configure Remote Config
Add these keys to Firebase Remote Config:
category_screen_configquiz_screen_configresult_screen_config
See Firebase Remote Config Setup below.
-
Build and Run
./gradlew assembleDebug
{
"version": "1.0",
"screen_id": "category_screen",
"layout": {
"type": "column",
"padding": 16,
"background_color": "#FFFFFF",
"components": [
{
"id": "logo",
"type": "image_display",
"enabled": true,
"props": {
"image_resource": "bee_only",
"size": "120",
"shape": "circle",
"background_color": "#EFAB31",
"elevation": "4",
"inner_padding": "8"
}
},
{
"id": "difficulty_selector",
"type": "chip_group",
"enabled": true,
"props": {
"options": "easy,medium,hard",
"spacing": "8",
"capitalize": "true"
}
},
{
"id": "category_list",
"type": "category_grid",
"enabled": true,
"props": {
"columns": "2",
"show_icons": "true",
"card_style": "filled"
}
}
]
}
}| Component | Type | Description |
|---|---|---|
image_display |
Display | Shows images with optional circular background |
text_display |
Display | Renders text with customizable styles |
chip_group |
Input | Selection chips for multiple options |
category_grid |
Display | Grid layout for quiz categories |
question_display |
Display | Shows quiz questions |
answer_options |
Input | Multiple choice answer buttons |
countdown_timer |
Display | Timer with warning threshold |
progress_indicator |
Display | Shows quiz progress |
score_card |
Display | Displays quiz results |
action_button |
Input | Configurable action buttons |
No more else -> {} in event handlers! Each screen only handles its own events.
Clean state handling with sealed interfaces prevents impossible states.
Layout types use enums instead of strings for compile-time safety:
enum class LayoutType {
@SerialName("column") COLUMN,
@SerialName("row") ROW,
@SerialName("box") BOX
}Configs are cached locally with 1-hour expiration for offline support.
# Run unit tests
./gradlew test
# Run instrumentation tests
./gradlew connectedAndroidTestThis project is open source and available under the MIT License.
Contributions are welcome! Please feel free to submit a Pull Request.
For questions or feedback, please open an issue on GitHub.
Made with β€οΈ and Jetpack Compose
