Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d24e703
feat: Kakao API로 변경 및 검색 기능 추가
GwonDongHyeon21 Dec 31, 2025
af25151
feat: Kakao API 의존성 주입
GwonDongHyeon21 Dec 31, 2025
0d102bb
feat: 변경된 data class 적용
GwonDongHyeon21 Dec 31, 2025
c2c1383
feat: Paging 설정 추가
GwonDongHyeon21 Dec 31, 2025
c975ebb
feat: Kakao 검색 기능 페이징 적용
GwonDongHyeon21 Dec 31, 2025
b31e617
feat: SearchLocation 페이징 적용
GwonDongHyeon21 Dec 31, 2025
06cc7e2
feat: Error 화면 컴포넌트 수정
GwonDongHyeon21 Dec 31, 2025
3668914
feat: LoadingIndicator 화면 컴포넌트 추가
GwonDongHyeon21 Dec 31, 2025
3c9804c
feat: EmptyText 컴포넌트 추가
GwonDongHyeon21 Dec 31, 2025
63067cd
feat: MemoripPagingList 컴포넌트 추가 및 적용
GwonDongHyeon21 Dec 31, 2025
67ecb85
feat: Serialization Json 설정 추가
GwonDongHyeon21 Jan 1, 2026
20277ef
feat: Serialization Json을 사용하여 의존성 주입 방식 수정
GwonDongHyeon21 Jan 1, 2026
6225d21
feat: Serialization 형태로 data class 수정
GwonDongHyeon21 Jan 1, 2026
961397a
Merge branch 'dev' into feature/50-search-paging
GwonDongHyeon21 Jan 1, 2026
353dbf6
rename: 파일 이름 수정
GwonDongHyeon21 Jan 1, 2026
470b7c8
feat: MemoripPagingList 컴포넌트 Paging AppendState 부분 확장 함수로 분리
GwonDongHyeon21 Jan 1, 2026
4a55b20
feat: MemoripPagingList 컴포넌트 LazyVerticalStaggeredGrid UI 추가
GwonDongHyeon21 Jan 1, 2026
6298369
feat: 네트워크 Json 설정 수정
GwonDongHyeon21 Jan 1, 2026
020c5ad
Merge branch 'dev' into feature/50-search-paging
GwonDongHyeon21 Jan 5, 2026
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
24 changes: 16 additions & 8 deletions client/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
alias(libs.plugins.kotlin.android)
alias(libs.plugins.dagger.hilt.root)
alias(libs.plugins.ksp)
alias(libs.plugins.jetbrains.kotlin.serialization)
}

android {
Expand All @@ -17,13 +18,11 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")

// Naver Search
val naverOpenApi = getLocalProperty("NAVER_OPEN_API")
val naverClientId = getLocalProperty("NAVER_SEARCH_CLIENT_ID")
val naverClientSecret = getLocalProperty("NAVER_SEARCH_CLIENT_SECRET")
buildConfigField("String", "NAVER_OPEN_API", "\"$naverOpenApi\"")
buildConfigField("String", "NAVER_SEARCH_CLIENT_ID", "\"$naverClientId\"")
buildConfigField("String", "NAVER_SEARCH_CLIENT_SECRET", "\"$naverClientSecret\"")
// Kakao Search
val kakaoBaseUrl = getLocalProperty("KAKAO_BASE_URL")
val kakaoRestApiKey = getLocalProperty("KAKAO_REST_API_KEY")
buildConfigField("String", "KAKAO_BASE_URL", "\"$kakaoBaseUrl\"")
buildConfigField("String", "KAKAO_REST_API_KEY", "\"$kakaoRestApiKey\"")
}

buildTypes {
Expand Down Expand Up @@ -63,7 +62,16 @@ dependencies {

// Retrofit
implementation(libs.retrofit)
implementation(libs.converter.gson)
implementation(libs.retrofit.kotlinx)

// Serialization
implementation(libs.kotlinx.serialization.json)

// Interceptor
implementation(libs.logging.interceptor)

// Paging
implementation(libs.androidx.paging.common)
}

fun getLocalProperty(propertyKey: String): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,64 @@
package com.andone.memorip.data.di

import com.andone.memorip.data.BuildConfig
import com.andone.memorip.data.naversearch.datasource.NaverSearchService
import com.andone.memorip.data.kakaosearch.datasource.KakaoSearchService
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

private const val BASE_URL = BuildConfig.NAVER_OPEN_API
private const val BASE_URL = BuildConfig.KAKAO_BASE_URL

val json = Json {
ignoreUnknownKeys = true
coerceInputValues = false
encodeDefaults = true
}

private val contentType = "application/json".toMediaType()

@Provides
@Singleton
fun provideNaverOkHttpClient(): OkHttpClient {
fun provideKakaoOkHttpClient(): OkHttpClient {
val logger = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
return OkHttpClient
.Builder()
.addInterceptor { chain ->
val newRequest = chain
.request()
.newBuilder()
.addHeader("X-Naver-Client-Id", BuildConfig.NAVER_SEARCH_CLIENT_ID)
.addHeader("X-Naver-Client-Secret", BuildConfig.NAVER_SEARCH_CLIENT_SECRET)
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", "KakaoAK ${BuildConfig.KAKAO_REST_API_KEY}")
.build()
chain.proceed(newRequest)
}
.addInterceptor(logger)
.build()
}

@Provides
@Singleton
fun provideNaverRetrofit(okHttpClient: OkHttpClient): Retrofit {
fun provideKakaoRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(json.asConverterFactory(contentType))
.build()
}

@Provides
@Singleton
fun provideNaverService(retrofit: Retrofit): NaverSearchService {
return retrofit.create(NaverSearchService::class.java)
fun provideKakaoSearchService(retrofit: Retrofit): KakaoSearchService {
return retrofit.create(KakaoSearchService::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.andone.memorip.data.di

import com.andone.memorip.data.naversearch.repositoryimpl.NaverSearchRepositoryImpl
import com.andone.memorip.domain.repository.NaverSearchRepository
import com.andone.memorip.data.kakaosearch.repositoryimpl.KakaoSearchRepositoryImpl
import com.andone.memorip.domain.repository.KakaoSearchRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -14,7 +14,7 @@ abstract class RepositoryModule {

@Binds
@Singleton
abstract fun bindNaverSearchRepository(
impl: NaverSearchRepositoryImpl
): NaverSearchRepository
abstract fun bindKaKaoSearchRepository(
impl: KakaoSearchRepositoryImpl
): KakaoSearchRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.andone.memorip.data.kakaosearch.datasource

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.andone.memorip.domain.model.response.KakaoLocation

class KakaoSearchPagingSource(
val apiService: KakaoSearchService,
val query: String
) : PagingSource<Int, KakaoLocation>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, KakaoLocation> {
val page = params.key ?: KAKAO_START_PAGE_INDEX
val size = params.loadSize

return try {
val result = apiService.searchLocations(
query = query,
page = page,
size = size
)

LoadResult.Page(
data = result.locations,
prevKey = if (page == KAKAO_START_PAGE_INDEX) null else page - 1,
nextKey = if (result.meta.isEnd) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, KakaoLocation>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}

companion object {
private const val KAKAO_START_PAGE_INDEX = 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.andone.memorip.data.kakaosearch.datasource

import com.andone.memorip.domain.model.response.KakaoSearchResponse
import retrofit2.http.GET
import retrofit2.http.Query

interface KakaoSearchService {
@GET("v2/local/search/keyword.json")
suspend fun searchLocations(
@Query("query") query: String,
@Query("page") page: Int,
@Query("size") size: Int,
): KakaoSearchResponse
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.andone.memorip.data.kakaosearch.repositoryimpl

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.andone.memorip.data.kakaosearch.datasource.KakaoSearchPagingSource
import com.andone.memorip.data.kakaosearch.datasource.KakaoSearchService
import com.andone.memorip.domain.model.response.KakaoLocation
import com.andone.memorip.domain.repository.KakaoSearchRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class KakaoSearchRepositoryImpl @Inject constructor(
private val searchApiService: KakaoSearchService
) : KakaoSearchRepository {

override suspend fun searchLocations(query: String): Flow<PagingData<KakaoLocation>> {
return Pager(
config = PagingConfig(
pageSize = KAKAO_SEARCH_SIZE,
enablePlaceholders = false,
initialLoadSize = KAKAO_SEARCH_SIZE
),
pagingSourceFactory = {
KakaoSearchPagingSource(
apiService = searchApiService,
query = query
)
}
).flow
}

companion object {
private const val KAKAO_SEARCH_SIZE = 15
}
}

This file was deleted.

This file was deleted.

11 changes: 11 additions & 0 deletions client/domain/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("java-library")
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.jetbrains.kotlin.serialization)
}
java {
sourceCompatibility = JavaVersion.VERSION_11
Expand All @@ -11,3 +12,13 @@ kotlin {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11
}
}
dependencies {
// Serialization
implementation(libs.kotlinx.serialization.json)

// Coroutine
implementation(libs.kotlinx.coroutines.core)

// Paging
implementation(libs.androidx.paging.common)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.andone.memorip.domain.model.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class KakaoSearchResponse(
@SerialName("documents") val locations: List<KakaoLocation>,
@SerialName("meta") val meta: KakaoMeta
)

@Serializable
data class KakaoMeta(
@SerialName("total_count") val totalCount: Int,
@SerialName("pageable_count") val pageableCount: Int,
@SerialName("is_end") val isEnd: Boolean
)

@Serializable
data class KakaoLocation(
@SerialName("id") val id: String,
@SerialName("place_name") val title: String,
@SerialName("category_name") val category: String,
@SerialName("address_name") val address: String,
@SerialName("road_address_name") val roadAddress: String,
@SerialName("x") val longitude: String,
@SerialName("y") val latitude: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.andone.memorip.domain.repository

import androidx.paging.PagingData
import com.andone.memorip.domain.model.response.KakaoLocation
import kotlinx.coroutines.flow.Flow

interface KakaoSearchRepository {
suspend fun searchLocations(query: String): Flow<PagingData<KakaoLocation>>
}

This file was deleted.

Loading