Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#5] 지역코드/시군구코드 로컬 저장 #6

Merged
merged 7 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ android {
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner = "kr.ksw.visitkorea.HiltTestRunner"
vectorDrawables {
useSupportLibrary = true
}
Expand Down Expand Up @@ -74,11 +74,6 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)

// hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
ksp(libs.androidx.hilt.compiler)

// room
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
Expand All @@ -88,6 +83,17 @@ dependencies {
implementation(libs.retrofit.converter.gson)
implementation(libs.okhttp)

// hilt
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
ksp(libs.androidx.hilt.compiler)
androidTestImplementation(libs.androidx.core.testing)

// workmanager
implementation(libs.androidx.hilt.work)
implementation(libs.androidx.work)

// test
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand All @@ -100,4 +106,5 @@ dependencies {

androidTestImplementation(libs.hilt.android.testing)
kspAndroidTest(libs.hilt.compiler)
kspAndroidTest(libs.androidx.hilt.compiler)
}
16 changes: 16 additions & 0 deletions app/src/androidTest/java/kr/ksw/visitkorea/HiltTestRunner.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kr.ksw.visitkorea

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class HiltTestRunner: AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package kr.ksw.visitkorea.data.local.dao

import androidx.test.filters.SmallTest
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import kotlinx.coroutines.test.runTest
import kr.ksw.visitkorea.data.local.databases.AreaCodeDatabase
import kr.ksw.visitkorea.data.local.entity.AreaCodeEntity
import kr.ksw.visitkorea.data.local.entity.SigunguCodeEntity
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject

@HiltAndroidTest
@SmallTest
class AreaCodeDaoTest {

@get:Rule
val hiltRule = HiltAndroidRule(this)

@Inject
lateinit var areaCodeDatabase: AreaCodeDatabase

private lateinit var dao: AreaCodeDao

@Before
fun setUp() {
hiltRule.inject()
dao = areaCodeDatabase.areaCodeDao
}

@After
fun tearDown() {
areaCodeDatabase.close()
}

@Test
fun getAllAreaCodeEntities_areaCodeListIsEmpty() = runTest {
assert(dao.getAllAreaCodeEntities().isEmpty())
}

@Test
fun upsertAreaCodeEntity_areaCodeIsUpserted() = runTest {
val areaCodeEntity = AreaCodeEntity(
code = "1", name = "서울"
)
dao.upsertAreaCodeEntity(areaCodeEntity)
assert(dao.getAllAreaCodeEntities().isNotEmpty())
}

@Test
fun upsertSigunguCodeEntity_sigunguCodeIsUpserted() = runTest {
val sigunguCodeEntity = SigunguCodeEntity(
areaCode = "1", code = "1", name = "강남구"
)
dao.upsertSigunguCodeEntity(sigunguCodeEntity)
assert(dao.getSigunguCodeByAreaCode("1").isNotEmpty())
}

@Test
fun `getSigunguCodeByAreaCode_sigunguCodeListByAreaCode1`() = runTest {
for(i in 1..4) {
dao.upsertSigunguCodeEntity(SigunguCodeEntity(
id = i,
areaCode = "1",
code = "$i",
name = "시군구 $i"
))
}
dao.upsertSigunguCodeEntity(SigunguCodeEntity(
id = 5,
areaCode = "2",
code = "1",
name = "가평군"
))

val sigunguCodeEntities = dao.getSigunguCodeByAreaCode("1")
assert(sigunguCodeEntities.isNotEmpty())
assert(sigunguCodeEntities.size == 4)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package kr.ksw.visitkorea.data.repository

import kr.ksw.visitkorea.data.mapper.toItems
import kr.ksw.visitkorea.data.remote.dto.AreaCodeDTO
import kr.ksw.visitkorea.data.remote.model.ApiResponse
import kr.ksw.visitkorea.data.remote.model.CommonBody
import kr.ksw.visitkorea.data.remote.model.CommonHeader
import kr.ksw.visitkorea.data.remote.model.CommonItem
import kr.ksw.visitkorea.data.remote.model.CommonResponse

class FakeAndroidAreaCodeRepository : AreaCodeRepository {
private var areaCodeResponse = ApiResponse(
CommonResponse(
CommonHeader("0000", "OK"),
CommonBody(
10,
1,
17,
CommonItem(listOf(
AreaCodeDTO("1","서울"),
AreaCodeDTO("2","인천"),
AreaCodeDTO("3","대전"),
AreaCodeDTO("4","대구"),
AreaCodeDTO("5","광주"),
AreaCodeDTO("6","부산"),
AreaCodeDTO("7","울산"),
AreaCodeDTO("8","세종특별자치시"),
AreaCodeDTO("31","경기도"),
AreaCodeDTO("32","강원특별자치도")
))
)
)
)

private var sigunguCodeResponse1 = ApiResponse(
CommonResponse(
CommonHeader("0000", "OK"),
CommonBody(
5,
1,
25,
CommonItem(listOf(
AreaCodeDTO("1","강남구"),
AreaCodeDTO("2","강동구"),
AreaCodeDTO("3","강북구"),
AreaCodeDTO("4","강서구"),
AreaCodeDTO("5","관악구")
))
)
)
)
private var sigunguCodeResponse2 = ApiResponse(
CommonResponse(
CommonHeader("0000", "OK"),
CommonBody(
5,
1,
31,
CommonItem(listOf(
AreaCodeDTO("1","가평군"),
AreaCodeDTO("2","고양시"),
AreaCodeDTO("3","과천시"),
AreaCodeDTO("4","광명시"),
AreaCodeDTO("5","광주시")
))
)
)
)

private var sigunguRequest = mapOf(
"1" to sigunguCodeResponse1,
"31" to sigunguCodeResponse2
)

override suspend fun getAreaCode(): Result<List<AreaCodeDTO>> = runCatching {
areaCodeResponse.toItems()
}

override suspend fun getSigunguCode(areaCode: String): Result<List<AreaCodeDTO>> = runCatching {
sigunguRequest[areaCode]!!.toItems()
}
}
38 changes: 38 additions & 0 deletions app/src/androidTest/java/kr/ksw/visitkorea/di/TestAppModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kr.ksw.visitkorea.di

import android.app.Application
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import kr.ksw.visitkorea.data.di.DataModule
import kr.ksw.visitkorea.data.local.databases.AreaCodeDatabase
import kr.ksw.visitkorea.data.repository.AreaCodeRepository
import kr.ksw.visitkorea.data.repository.FakeAndroidAreaCodeRepository
import javax.inject.Singleton

@Module
@TestInstallIn(
components = [SingletonComponent::class],
replaces = [DataModule::class]
)
object TestAppModule {

@Provides
@Singleton
fun provideAreaCodeDatabase(application: Application): AreaCodeDatabase {
return Room.inMemoryDatabaseBuilder(
application,
AreaCodeDatabase::class.java
).build()
}

@Provides
@Singleton
fun provideAreaCodeRepository(): AreaCodeRepository {
return FakeAndroidAreaCodeRepository()
}

}
12 changes: 12 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.VisitKorea">

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="{applicationId}.androidx-startup"
android:exported="false"
tools:replace="android:authorities">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove"/>
</provider>

<activity
android:name=".presentation.splash.SplashActivity"
android:exported="true"
Expand Down
13 changes: 12 additions & 1 deletion app/src/main/java/kr/ksw/visitkorea/app/VisitKoreaApp.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
package kr.ksw.visitkorea.app

import android.app.Application
import androidx.hilt.work.HiltWorkerFactory
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject

@HiltAndroidApp
class VisitKoreaApp : Application()
class VisitKoreaApp : Application(), Configuration.Provider {
@Inject
lateinit var hiltWorkerFactory: HiltWorkerFactory

override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(hiltWorkerFactory)
.build()
}
13 changes: 13 additions & 0 deletions app/src/main/java/kr/ksw/visitkorea/data/di/DataModule.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package kr.ksw.visitkorea.data.di

import android.app.Application
import androidx.room.Room
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kr.ksw.visitkorea.data.local.databases.AreaCodeDatabase
import kr.ksw.visitkorea.data.remote.api.AreaCodeApi
import kr.ksw.visitkorea.data.remote.api.RetrofitInterceptor
import kr.ksw.visitkorea.data.repository.AreaCodeRepository
Expand Down Expand Up @@ -37,6 +40,16 @@ object DataModule {
.build()
}

@Provides
@Singleton
fun provideAreaCodeDatabase(application: Application): AreaCodeDatabase {

Choose a reason for hiding this comment

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

fun provideAreaCodeDatabase(@ApplicationContext context: Context): AreaCodeDatabase {
    ...
}

로 해도 좋을 것 같아요. predefined binding 에 대한 qualifier 입니다.

Copy link
Collaborator Author

@ksw4015 ksw4015 Oct 5, 2024

Choose a reason for hiding this comment

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

@ApplicationContext의 경우 생성자 주입으로 context를 제공하는 것이 아니라서 그런지 추가하면 Missing Binds 에러가 발생하네요

return Room.databaseBuilder(
application,
AreaCodeDatabase::class.java,
"area_code.db"
).build()
}

@Provides
@Singleton
fun provideAreaCodeApi(retrofit: Retrofit): AreaCodeApi = retrofit.create(AreaCodeApi::class.java)
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/kr/ksw/visitkorea/data/local/dao/AreaCodeDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.ksw.visitkorea.data.local.dao

import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import kr.ksw.visitkorea.data.local.entity.AreaCodeEntity
import kr.ksw.visitkorea.data.local.entity.SigunguCodeEntity

@Dao
interface AreaCodeDao {
@Upsert
suspend fun upsertAreaCodeEntity(areaCodeEntity: AreaCodeEntity)

@Upsert
suspend fun upsertSigunguCodeEntity(sigunguCodeEntity: SigunguCodeEntity)

@Query("SELECT * FROM area_code")
suspend fun getAllAreaCodeEntities(): List<AreaCodeEntity>

@Query("SELECT * FROM sigungu_code WHERE areaCode = :areaCode")
suspend fun getSigunguCodeByAreaCode(areaCode: String): List<SigunguCodeEntity>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package kr.ksw.visitkorea.data.local.databases

import androidx.room.Database
import androidx.room.RoomDatabase
import kr.ksw.visitkorea.data.local.dao.AreaCodeDao
import kr.ksw.visitkorea.data.local.entity.AreaCodeEntity
import kr.ksw.visitkorea.data.local.entity.SigunguCodeEntity

@Database(
entities = [AreaCodeEntity::class, SigunguCodeEntity::class],
version = 1
)
abstract class AreaCodeDatabase: RoomDatabase() {
abstract val areaCodeDao: AreaCodeDao
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package kr.ksw.visitkorea.data.local.entity

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity("area_code")
data class AreaCodeEntity(
@PrimaryKey(autoGenerate = true)
val id: Int? = null,
val code: String,
val name: String,
)
Loading
Loading