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

경북대Android_정수현_3주차 과제_Step2 #77

Open
wants to merge 14 commits into
base: jsh00325
Choose a base branch
from
Open
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
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# android-map-search

## 📄 프로그램 설명

2주차 어플리케이션에서 local DB를 사용하는 것을, 카카오 로컬 API를 사용하여 서버에서 데이터를 받아오는 것으로 변경한 어플리케이션입니다.

이후 지도 화면을 구현하여, 위치 검색과 지도를 볼 수 있는 어플리케이션을 제작합니다.

## 🎯 1단계(카카오 로컬 API) 구현할 기능

- [X] 카카오 로컬 API를 사용하여 검색 결과를 받아오는 기능 구현

- [X] API에 맞는 DTO 클래스를 생성하기

- [X] Retrofit을 사용하여 API를 호출하는 인터페이스 생성하기

- [X] 기존의 SQLite를 사용하는 부분을 Retrofit으로 변경하기

- [X] API를 호출할 때, 결과가 15개 이상 표시되도록 구현하기

## 🎯 2단계(카카오 지도 API) 구현할 기능

- 카카오 맵 SDK를 현재 프로젝트에 적용하기

- [`공식 문서`](https://apis.map.kakao.com/android_v2/docs/getting-started/)를 참고해서 수행하기

- 지도 위에 검색창이 있는 레이아웃 제작하기

- 검색창을 클릭하는 경우, 기존의 검색과 검색 결과를 확인할 수 있는 화면으로 전환하기

- 검색 화면에서 뒤로가기를 누르는 경우, 다시 지도 화면으로 전환하기
11 changes: 10 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
Expand All @@ -15,6 +17,8 @@ android {
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
resValue("string", "kakao_api_key", getApiKey("KAKAO_API_KEY"))
buildConfigField("String", "KAKAO_REST_API_KEY", getApiKey("KAKAO_REST_API_KEY"))
}

buildTypes {
Expand All @@ -36,6 +40,7 @@ android {

buildFeatures {
viewBinding = true
buildConfig = true
}
}

Expand All @@ -49,9 +54,13 @@ dependencies {
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
// implementation("com.kakao.maps.open:android:2.9.5")
implementation("androidx.lifecycle:lifecycle-viewmodel-android:2.8.3")
implementation("com.kakao.sdk:v2-all:2.20.3")
implementation("com.kakao.maps.open:android:2.11.1")
implementation("androidx.activity:activity:1.8.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}

fun getApiKey(key: String): String = gradleLocalProperties(rootDir, providers).getProperty(key)
8 changes: 6 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".KakaoMapApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -15,14 +16,17 @@
android:theme="@style/Theme.Map"
tools:targetApi="31">
<activity
android:name=".view.SearchLocationActivity"
android:name=".view.MapActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".view.SearchLocationActivity"
android:exported="true" />
</application>

</manifest>
</manifest>
11 changes: 11 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/KakaoMapApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package campus.tech.kakao.map

import android.app.Application
import com.kakao.vectormap.KakaoMapSdk

class KakaoMapApplication : Application() {
override fun onCreate() {
super.onCreate()
KakaoMapSdk.init(this, getString(R.string.kakao_api_key))
}
}
34 changes: 34 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/model/LocalSearchDTO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package campus.tech.kakao.map.model

data class LocalSearchResponse(
val documents: List<LocalSearchDocument>,
val meta: LocalSearchMeta
)

data class LocalSearchDocument(
val address_name: String,
val category_group_code: String,
val category_group_name: String,
val category_name: String,
val distance: String,
val id: String,
val phone: String,
val place_name: String,
val place_url: String,
val road_address_name: String,
val x: String,
val y: String
)

data class LocalSearchMeta(
val is_end: Boolean,
val pageable_count: Int,
val same_name: LocalSearchSameName,
val total_count: Int
)

data class LocalSearchSameName(
val keyword: String,
val region: List<String>,
val selected_region: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package campus.tech.kakao.map.model

import campus.tech.kakao.map.BuildConfig
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query

interface LocalSearchService {
@GET("search/keyword.json")
suspend fun requestLocalSearch(
@Header("Authorization") authorization: String = "KakaoAK ${BuildConfig.KAKAO_REST_API_KEY}",
@Query("query") query: String,
@Query("page") page: Int = 1,
@Query("size") size: Int = 15
): Response<LocalSearchResponse>
}
11 changes: 0 additions & 11 deletions app/src/main/java/campus/tech/kakao/map/model/LocationContract.kt

This file was deleted.

48 changes: 0 additions & 48 deletions app/src/main/java/campus/tech/kakao/map/model/LocationDbHelper.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,26 @@ package campus.tech.kakao.map.model

import android.content.ContentValues
import android.content.Context
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class SearchLocationRepository(context: Context) {
private val locationDbHelper: LocationDbHelper = LocationDbHelper(context)
private val historyDbHelper: HistoryDbHelper = HistoryDbHelper(context)
private val localSearchService = Retrofit.Builder()
.baseUrl("https://dapi.kakao.com/v2/local/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(LocalSearchService::class.java)

fun searchLocation(category: String): List<Location> {
val db = locationDbHelper.readableDatabase
val searchQuery = "SELECT * FROM ${LocationContract.TABLE_NAME} " +
"WHERE ${LocationContract.COLUMN_CATEGORY} = '$category'"
val cursor = db.rawQuery(searchQuery, null)

val result = mutableListOf<Location>()
while (cursor.moveToNext()) {
result.add(
Location(
name = cursor.getString(cursor.getColumnIndexOrThrow(LocationContract.COLUMN_NAME)),
address = cursor.getString(cursor.getColumnIndexOrThrow(LocationContract.COLUMN_ADDRESS)),
category = cursor.getString(cursor.getColumnIndexOrThrow(LocationContract.COLUMN_CATEGORY))
)
suspend fun searchLocation(category: String): List<Location> {
val response = localSearchService.requestLocalSearch(query = category)
return response.body()?.documents?.map {
Location(
name = it.place_name,
address = it.address_name,
category = it.category_group_name
)
}
cursor.close()
db.close()

return result.toList()
} ?: emptyList()
}

fun addHistory(locationName: String) {
Expand Down
50 changes: 50 additions & 0 deletions app/src/main/java/campus/tech/kakao/map/view/MapActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package campus.tech.kakao.map.view

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import campus.tech.kakao.map.databinding.ActivityMapBinding
import com.kakao.vectormap.KakaoMap
import com.kakao.vectormap.KakaoMapReadyCallback
import com.kakao.vectormap.MapAuthException
import com.kakao.vectormap.MapLifeCycleCallback

class MapActivity : AppCompatActivity() {
private lateinit var binding: ActivityMapBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapBinding.inflate(layoutInflater)
setContentView(binding.root)

binding.kakaoMapView.start(object : MapLifeCycleCallback() {
override fun onMapDestroy() {
Log.d("MapActivity", "onMapDestroy")
}

override fun onMapError(e: Exception?) {
Log.e("MapActivity", "onMapError", e)
Toast.makeText(
this@MapActivity,
when ((e as MapAuthException).errorCode) {
401 -> "API 인증에 실패했습니다.\n올바른 API 키를 설정해주세요."
499 -> "서버와의 통신에 실패했습니다.\n인터넷 연결을 확인해주세요."
else -> "오류가 발생했습니다. 다시 시도해주세요."
},
Toast.LENGTH_SHORT
).show()
Copy link

Choose a reason for hiding this comment

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

에러처리 잘 해주셨네요 🎉
케이스별로 깔끔하게 잘 구현 해주신것 같습니다!

추가 코멘트를 드리자면
현업에서는 어떤 에러가 발생했는지 감지 하기위해
에러가 발생하면 서버로 전송해 어떤 에러가 났었는지 모니터링합니다.
대표적인툴로 Firebase Crashlytics가 있습니다. 시간나실때 한번 보시는걸 추천드려요!

}
}, object : KakaoMapReadyCallback() {
override fun onMapReady(map: KakaoMap) {
Log.d("MapActivity", "onMapReady")
Copy link

Choose a reason for hiding this comment

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

(지금 대응하지 않으셔도 됩니다.)

엄밀히 따지면 맵과 관련된 서비스에서는
map이 ready된 후에 다른 작업을 시작하는게 맞긴 합니다.

}
})

binding.searchBackgroundView.setOnClickListener {
val intent = Intent(this@MapActivity, SearchLocationActivity::class.java)
startActivity(intent)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package campus.tech.kakao.map.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import campus.tech.kakao.map.model.Location
import campus.tech.kakao.map.model.SearchLocationRepository
import kotlinx.coroutines.launch

class SearchLocationViewModel : ViewModel() {
private lateinit var repository: SearchLocationRepository
Expand All @@ -20,7 +22,9 @@ class SearchLocationViewModel : ViewModel() {
val history: LiveData<List<String>> = _history

fun searchLocation(category: String) {
_location.value = repository.searchLocation(category)
viewModelScope.launch {
_location.value = repository.searchLocation(category)
}
}

fun addHistory(locationName: String) {
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/round_search_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>

</vector>
Loading