-
Notifications
You must be signed in to change notification settings - Fork 0
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
[#3] 지역코드 조회 API Service 추가 #4
Changes from all commits
4f2c365
07d3ac6
f028279
270908e
5f6c352
ab83389
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package kr.ksw.visitkorea.app | ||
|
||
import android.app.Application | ||
import dagger.hilt.android.HiltAndroidApp | ||
|
||
@HiltAndroidApp | ||
class VisitKoreaApp : Application() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package kr.ksw.visitkorea.data.di | ||
|
||
import dagger.Module | ||
import dagger.Provides | ||
import dagger.hilt.InstallIn | ||
import dagger.hilt.components.SingletonComponent | ||
import kr.ksw.visitkorea.data.remote.api.AreaCodeApi | ||
import kr.ksw.visitkorea.data.remote.api.RetrofitInterceptor | ||
import kr.ksw.visitkorea.data.repository.AreaCodeRepository | ||
import kr.ksw.visitkorea.data.repository.AreaCodeRepositoryImpl | ||
import okhttp3.OkHttpClient | ||
import retrofit2.Retrofit | ||
import retrofit2.converter.gson.GsonConverterFactory | ||
import javax.inject.Singleton | ||
|
||
@Module | ||
@InstallIn(SingletonComponent::class) | ||
object DataModule { | ||
|
||
private const val BASE_URL = "https://apis.data.go.kr/B551011/KorService1/" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BASE_URL 과 같은 상수는 DataModule 에 둬도 좋지만, 상수가 많아진다면 상수를 관리하는 object 를 하나 둬도 괜찮을 거 같아요. |
||
|
||
@Provides | ||
@Singleton | ||
fun provideOkhttpClient(interceptor: RetrofitInterceptor): OkHttpClient { | ||
return OkHttpClient.Builder() | ||
.addInterceptor(interceptor) | ||
.build() | ||
} | ||
|
||
@Provides | ||
@Singleton | ||
fun provideRetrofit(client: OkHttpClient): Retrofit { | ||
return Retrofit.Builder() | ||
.addConverterFactory(GsonConverterFactory.create()) | ||
.baseUrl(BASE_URL) | ||
.client(client) | ||
.build() | ||
} | ||
|
||
@Provides | ||
@Singleton | ||
fun provideAreaCodeApi(retrofit: Retrofit): AreaCodeApi = retrofit.create(AreaCodeApi::class.java) | ||
|
||
@Provides | ||
@Singleton | ||
fun provideAreaCodeRepository(areaCodeApi: AreaCodeApi): AreaCodeRepository { | ||
return AreaCodeRepositoryImpl(areaCodeApi) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package kr.ksw.visitkorea.data.mapper | ||
|
||
import kr.ksw.visitkorea.data.remote.model.ApiResponse | ||
|
||
fun <T> ApiResponse<T>.toItems(): List<T> = response.body.items.item |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package kr.ksw.visitkorea.data.remote.api | ||
|
||
import kr.ksw.visitkorea.data.remote.dto.AreaCodeDTO | ||
import kr.ksw.visitkorea.data.remote.model.ApiResponse | ||
import retrofit2.http.GET | ||
import retrofit2.http.Query | ||
|
||
interface AreaCodeApi { | ||
@GET("areaCode1") | ||
suspend fun getAreaCode( | ||
@Query("numOfRows") numOfRows: Int = 20 | ||
): ApiResponse<AreaCodeDTO> | ||
|
||
@GET("areaCode1") | ||
suspend fun getSigunguCode( | ||
@Query("numOfRows") numOfRows: Int = 40, | ||
@Query("areaCode") areaCode: String | ||
): ApiResponse<AreaCodeDTO> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package kr.ksw.visitkorea.data.remote.api | ||
|
||
import kr.ksw.visitkorea.BuildConfig | ||
import okhttp3.Interceptor | ||
import okhttp3.Response | ||
import javax.inject.Inject | ||
|
||
|
||
|
||
class RetrofitInterceptor @Inject constructor() : Interceptor { | ||
override fun intercept(chain: Interceptor.Chain): Response { | ||
val request = chain.request() | ||
val requestUrl = request.url | ||
.newBuilder() | ||
.apply { | ||
baseQueryMap.forEach { (key, value) -> | ||
addQueryParameter(key, value) | ||
} | ||
} | ||
.build() | ||
|
||
return chain.proceed(request | ||
.newBuilder() | ||
.url(requestUrl) | ||
.build() | ||
) | ||
} | ||
|
||
companion object { | ||
private val baseQueryMap = mapOf( | ||
"MobileOS" to "AND", | ||
"MobileApp" to "visitkorea", | ||
"_type" to "json", | ||
"serviceKey" to BuildConfig.API_KEY | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package kr.ksw.visitkorea.data.remote.dto | ||
|
||
data class AreaCodeDTO( | ||
val code: String, | ||
val name: String, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package kr.ksw.visitkorea.data.remote.model | ||
|
||
data class ApiResponse<T>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. data class 의 마지막 item 들에는 trailing comma 를 넣어 주는 것이 좋습니다. 아래의 다른 data class 들도 마찬가지로요 :) |
||
val response: CommonResponse<T>, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package kr.ksw.visitkorea.data.remote.model | ||
|
||
data class CommonBody<T>( | ||
val numOfRows: Int, | ||
val pageNo: Int, | ||
val totalCount: Int, | ||
val items: CommonItem<T>, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package kr.ksw.visitkorea.data.remote.model | ||
|
||
data class CommonHeader( | ||
val resultCode: String, | ||
val resultMsg: String, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package kr.ksw.visitkorea.data.remote.model | ||
|
||
data class CommonItem<T>( | ||
val item: List<T>, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package kr.ksw.visitkorea.data.remote.model | ||
|
||
data class CommonResponse<T>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 제네릭을 이용한 Response class 를 만드는 건 좋습니다. 그리고 이런 형태가 반복적으로 사용된다면 data/remote 가 아닌 common 과 같은 패키지를 하나 만들어서 관리해도 좋을 거 같아요. |
||
val header: CommonHeader, | ||
val body: CommonBody<T>, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package kr.ksw.visitkorea.data.repository | ||
|
||
import kr.ksw.visitkorea.data.remote.dto.AreaCodeDTO | ||
|
||
interface AreaCodeRepository { | ||
suspend fun getAreaCode(): Result<List<AreaCodeDTO>> | ||
|
||
suspend fun getSigunguCode(areaCode: String): Result<List<AreaCodeDTO>> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package kr.ksw.visitkorea.data.repository | ||
|
||
import kr.ksw.visitkorea.data.mapper.toItems | ||
import kr.ksw.visitkorea.data.remote.api.AreaCodeApi | ||
import kr.ksw.visitkorea.data.remote.dto.AreaCodeDTO | ||
import javax.inject.Inject | ||
|
||
class AreaCodeRepositoryImpl @Inject constructor( | ||
private val areaCodeApi: AreaCodeApi | ||
): AreaCodeRepository { | ||
override suspend fun getAreaCode(): Result<List<AreaCodeDTO>> = runCatching { | ||
areaCodeApi.getAreaCode().toItems() | ||
} | ||
|
||
override suspend fun getSigunguCode(areaCode: String): Result<List<AreaCodeDTO>> = runCatching { | ||
areaCodeApi.getSigunguCode(areaCode = areaCode).toItems() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,11 @@ | ||
package kr.ksw.visitkorea.presentation.splash | ||
|
||
import android.Manifest | ||
import android.content.Intent | ||
import android.content.pm.PackageManager | ||
import android.os.Bundle | ||
import android.widget.Toast | ||
import androidx.activity.ComponentActivity | ||
import androidx.activity.compose.setContent | ||
import androidx.activity.result.contract.ActivityResultContracts | ||
import androidx.activity.viewModels | ||
import androidx.compose.foundation.layout.Arrangement | ||
import androidx.compose.foundation.layout.Column | ||
import androidx.compose.foundation.layout.fillMaxSize | ||
|
@@ -16,26 +14,18 @@ import androidx.compose.material3.Text | |
import androidx.compose.ui.Alignment | ||
import androidx.compose.ui.Modifier | ||
import androidx.compose.ui.unit.sp | ||
import androidx.core.content.ContextCompat | ||
import androidx.lifecycle.lifecycleScope | ||
import dagger.hilt.android.AndroidEntryPoint | ||
import kotlinx.coroutines.flow.collectLatest | ||
import kotlinx.coroutines.launch | ||
import kr.ksw.visitkorea.R | ||
import kr.ksw.visitkorea.presentation.main.MainActivity | ||
import kr.ksw.visitkorea.presentation.ui.theme.VisitKoreaTheme | ||
|
||
@AndroidEntryPoint | ||
class SplashActivity : ComponentActivity() { | ||
|
||
private val locationPermissionLauncher = registerForActivityResult( | ||
ActivityResultContracts.RequestMultiplePermissions() | ||
) { permissionMap -> | ||
if(permissionMap[Manifest.permission.ACCESS_COARSE_LOCATION] != true && | ||
permissionMap[Manifest.permission.ACCESS_FINE_LOCATION] != true) { | ||
Toast.makeText( | ||
this@SplashActivity, | ||
getString(R.string.location_permission_denied_toast), | ||
Toast.LENGTH_SHORT | ||
).show() | ||
} | ||
startMainActivity() | ||
} | ||
private val viewModel: SplashViewModel by viewModels() | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
|
@@ -55,20 +45,26 @@ class SplashActivity : ComponentActivity() { | |
} | ||
} | ||
} | ||
checkPermission() | ||
observeSideEffect() | ||
viewModel.checkPermission(this) | ||
} | ||
|
||
private fun checkPermission() { | ||
if(ContextCompat.checkSelfPermission( | ||
this, | ||
Manifest.permission.ACCESS_FINE_LOCATION | ||
) == PackageManager.PERMISSION_GRANTED) { | ||
startMainActivity() | ||
} else { | ||
locationPermissionLauncher.launch(arrayOf( | ||
Manifest.permission.ACCESS_COARSE_LOCATION, | ||
Manifest.permission.ACCESS_FINE_LOCATION | ||
)) | ||
private fun observeSideEffect() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네 알겠습니다! |
||
lifecycleScope.launch { | ||
viewModel.sideEffect.collectLatest { effect -> | ||
when(effect) { | ||
SplashSideEffect.PermissionDenied -> { | ||
Toast.makeText( | ||
this@SplashActivity, | ||
getString(R.string.location_permission_denied_toast), | ||
Toast.LENGTH_SHORT | ||
).show() | ||
} | ||
SplashSideEffect.StartMainActivity -> { | ||
startMainActivity() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
여담인데, 35가 곧 배포될 예정이라서 compile / target sdk version up 을 34로 유지하는 건 앞으로 1년 정도만 허용될 거예요. (지금 당장은 하지 않아도 됨)