feat: Implement data layer and basic quiz fetching logic

This commit introduces the data layer, including the `QuizRepository` and its implementation, and integrates it with the domain layer's `GetQuizUseCase`. It also sets up Hilt for dependency injection in the app module and makes preliminary UI changes to display fetched quiz data.

Key changes include:

- **Data Layer (`model:data` module):**
    - Added `QuizRepositoryImpl` which implements `QuizRepository` from the domain layer.
    - Implemented `getQuiz()` in `QuizRepositoryImpl` to fetch quiz data from `QuizApi` and map it to the domain `Quiz` model using `QuizMapper`.
    - Created `QuizMapper` to convert `QuizResponseDto` to the domain `Quiz` model.
    - Added `RepositoryModule` for Hilt to provide `QuizRepository` bindings.
- **Domain Layer (`domain` module):**
    - Created `Quiz` domain model.
    - Defined `QuizRepository` interface.
    - Implemented `GetQuizUseCase` to interact with `QuizRepository`.
- **App Module (`app` module):**
    - Added `MyApplication` class annotated with `@HiltAndroidApp`.
    - Updated `AndroidManifest.xml` to use `MyApplication` and add internet permission.
    - In `MainActivity`:
        - Injected `GetQuizUseCase`.
        - Used `LaunchedEffect` to call the use case and update a mutable state `quizId`.
        - Modified the `Greeting` composable to display the fetched `quizId`.
    - Added dependency on the `domain` module in `app/build.gradle.kts`.
- **Network Layer (`core:network` module):**
    - Moved DTOs from `core.network.model` package to `core.network.models`.
    - Made `NetworkModule` internal.
    - Removed unused `QuizService` interface and `QuizServiceImpl` class.
- **Testing (`core:network` test):**
    - Updated import path for `QuizResponseDto` in `QuizResponseDtoParsingTest`.
This commit is contained in:
GitHub Actions Bot
2025-09-02 22:29:15 +02:00
parent f12ac0826e
commit 293b7f75b9
22 changed files with 112 additions and 22 deletions

View File

@@ -28,6 +28,7 @@ android {
dependencies { dependencies {
implementation(projects.ui.quiz) implementation(projects.ui.quiz)
implementation(projects.domain)
implementation(projects.model.data) implementation(projects.model.data)

View File

@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@@ -14,7 +16,6 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.KahootQuiz"> android:theme="@style/Theme.KahootQuiz">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -9,19 +9,39 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import dagger.hilt.android.AndroidEntryPoint
import dev.adriankuta.kahootquiz.domain.usecases.GetQuizUseCase
import dev.adriankuta.kahootquiz.ui.theme.KahootQuizTheme import dev.adriankuta.kahootquiz.ui.theme.KahootQuizTheme
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@Inject
lateinit var getQuizUseCase: GetQuizUseCase
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
KahootQuizTheme { KahootQuizTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
var quizId by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
quizId = getQuizUseCase().id
}
Greeting( Greeting(
name = "Android", name = quizId ?: "Android",
modifier = Modifier.padding(innerPadding) modifier = Modifier.padding(innerPadding)
) )
} }

View File

@@ -0,0 +1,7 @@
package dev.adriankuta.kahootquiz
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class MyApplication: Application()

View File

@@ -1,4 +0,0 @@
package dev.adriankuta.kahootquiz.core.network
interface QuizService {
}

View File

@@ -1,4 +0,0 @@
package dev.adriankuta.kahootquiz.core.network
internal class QuizServiceImpl: QuizService {
}

View File

@@ -12,7 +12,7 @@ import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object NetworkModule { internal object NetworkModule {
@Provides @Provides
@Singleton @Singleton

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
// Commonly reused DTOs // Commonly reused DTOs

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
// Content tags DTOs // Content tags DTOs

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
// Cover metadata and related DTOs // Cover metadata and related DTOs

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
// Metadata section DTOs // Metadata section DTOs

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
// Question and choice related DTOs // Question and choice related DTOs

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
// This file used to contain all DTOs in one place. // This file used to contain all DTOs in one place.
// The DTOs have been split into separate files for maintainability: // The DTOs have been split into separate files for maintainability:

View File

@@ -1,4 +1,4 @@
package dev.adriankuta.kahootquiz.core.network.model package dev.adriankuta.kahootquiz.core.network.models
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName

View File

@@ -1,6 +1,6 @@
package dev.adriankuta.kahootquiz.core.network.retrofit package dev.adriankuta.kahootquiz.core.network.retrofit
import dev.adriankuta.kahootquiz.core.network.model.QuizResponseDto import dev.adriankuta.kahootquiz.core.network.models.QuizResponseDto
import retrofit2.http.GET import retrofit2.http.GET
interface QuizApi { interface QuizApi {

View File

@@ -2,7 +2,7 @@ package dev.adriankuta.kahootquiz.core.network
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.google.gson.Gson import com.google.gson.Gson
import dev.adriankuta.kahootquiz.core.network.model.QuizResponseDto import dev.adriankuta.kahootquiz.core.network.models.QuizResponseDto
import org.junit.Test import org.junit.Test
import java.io.InputStreamReader import java.io.InputStreamReader

View File

@@ -0,0 +1,5 @@
package dev.adriankuta.kahootquiz.domain.models
data class Quiz(
val id: String
)

View File

@@ -0,0 +1,7 @@
package dev.adriankuta.kahootquiz.domain.repositories
import dev.adriankuta.kahootquiz.domain.models.Quiz
interface QuizRepository {
suspend fun getQuiz(): Quiz
}

View File

@@ -0,0 +1,14 @@
package dev.adriankuta.kahootquiz.domain.usecases
import dev.adriankuta.kahootquiz.domain.models.Quiz
import dev.adriankuta.kahootquiz.domain.repositories.QuizRepository
import javax.inject.Inject
class GetQuizUseCase @Inject constructor(
private val quizRepository: QuizRepository
) {
suspend operator fun invoke(): Quiz {
return quizRepository.getQuiz()
}
}

View File

@@ -0,0 +1,16 @@
package dev.adriankuta.kahootquiz.model.data
import dev.adriankuta.kahootquiz.core.network.retrofit.QuizApi
import dev.adriankuta.kahootquiz.domain.models.Quiz
import dev.adriankuta.kahootquiz.domain.repositories.QuizRepository
import dev.adriankuta.kahootquiz.model.data.mappers.toDomainModel
import javax.inject.Inject
internal class QuizRepositoryImpl @Inject constructor(
private val quizApi: QuizApi
) : QuizRepository {
override suspend fun getQuiz(): Quiz {
return quizApi.getQuiz().toDomainModel()
}
}

View File

@@ -0,0 +1,20 @@
package dev.adriankuta.kahootquiz.model.data.di
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dev.adriankuta.kahootquiz.domain.repositories.QuizRepository
import dev.adriankuta.kahootquiz.model.data.QuizRepositoryImpl
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindsQuizRepository(
quizRepositoryImpl: QuizRepositoryImpl
): QuizRepository
}

View File

@@ -0,0 +1,7 @@
package dev.adriankuta.kahootquiz.model.data.mappers
import dev.adriankuta.kahootquiz.core.network.models.QuizResponseDto
import dev.adriankuta.kahootquiz.domain.models.Quiz
internal fun QuizResponseDto.toDomainModel(): Quiz =
Quiz(this.uuid ?: "")