본문 바로가기
  • 철은 두드릴수록 강해지고 사람은 굴릴수록 강해진다.
IT/프로젝트

[캡스톤디자인(4)] Android Studio에서 Kakaomap API 구현 2편

by jangddu 2024. 2. 6.

 

 

지난 캡스톤디자인(3)과 이어진다.

https://tnqls18513.tistory.com/30

 

[캡스톤디자인(3)] Android Studio에서 Kakaomap API 구현 1편

서문 이전 글에서 언급했던 것처럼 여러 작업들 중에서 가장 고통과 성취를 느낀 부분은 Android Studio에서 Kakao API를구현하는 부분이다. 보통 블로그 글들을 찾아보면 지도를 화면에 띄우는 작업

tnqls18513.tistory.com

 

 

지난 글에서 설명했던 것과 같이 진행은 다음과 같이 했다.

1. 파이썬(내게 익숙한 언어)으로 Kakaomap API를 사용하여 얻은 정보를 csv로 저장

2. 위 작업을 Android Studio의 Kotlin으로 구현 (로그에 뜨게 만들기)

3. Android Studio에서의 세부 작업들

(1) 하나의 검색어에서 상위 15개만 출력

(2) 다수의 검색어도 되는지 확인

(3) 30개 카테고리, 각 상위 5개 값만 리스트로 저장, 출력

(4) 각 카테고리의 상위 5개 값 중에 1개씩 랜덤으로 뽑아서 또다른 리스트로 저장

(5) 카테고리 랜덤으로 10개 값만 뽑기

(6) 보기 좋게 원하는 정보만 출력되게 만들기

 

이 중에서 2번에 대해 설명한 것이 너무 부실한 것 같아서 보충하려고 한다.

 

진행 순서

[1] 카카오에서 REST KEY 받아오기

[2] build.gradle (:app)에 코드 추가

[3] ResultSearchKeyword.kt 새로 만들고 코드 추가

[4] KakaoAPI.kt 새로 만들고 코드 추가

[5] MainActivity.kt에 코드 넣기!

 

[1] 카카오에서 REST KEY 받아오기

(a) 카카오개발자 사이트에서 sdk를 다운받고 이 그림대로 파일들을 넣어줘야함

     (libs 디렉토리에 libDaumMapAndorid.jar을 넣고 jniLibs 디렉토리에 arm64-v8a, armeabi, armeabi-v7a를 넣는다)

(b) HashKey 얻기

(아래 코드를 적고 실행시킨 후 Logcat 탭에서 ctrl+F를 이용하면 KEY_HASH를 찾을 수 있다.)

import android.content.pm.PackageManager
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Base64
import android.util.Log
import androidx.annotation.RequiresApi
import java.security.MessageDigest
import java.util.*
 
class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.P)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        try {
            val info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES)
            val signatures = info.signingInfo.apkContentsSigners
            val md = MessageDigest.getInstance("SHA")
            for (signature in signatures) {
                val md: MessageDigest
                md = MessageDigest.getInstance("SHA")
                md.update(signature.toByteArray())
                val key = String(Base64.encode(md.digest(), 0))
                Log.d("Hash key:", "!!!!!!!$key!!!!!!")
            }
        } catch (e: Exception) {
            Log.e("name not found", e.toString())
        }
    }
}

-> 여기서 오류가 계속 떴는데 이때는 Andoid가 최신버전인지 확인한다. 2023년 기준으로는 34가 최신이었다.

 

(c) Kakao Developers에 플랫폼 등록

  • 사업자명, 어플이름, 패키지명, 해쉬키 등록해야함
  • 패키지명은 Manifest.xml 상단의 package 명을 적으면 된다.
    (내 경우엔 com.jangddu.Algorithm, com.jangddu.JJJJ였다. 2개로 이런저런 실험을 하다가 후자는 나중에 버림)

 

[2] build.gradle (:app)에 코드 추가

dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}

 

 

[3] ResultSearchKeyword.kt 새로 만들고 코드 추가

// 검색 결과를 담는 클래스
data class ResultSearchKeyword(
var meta: PlaceMeta, // 장소 메타데이터
var documents: List<Place> // 검색 결과
)

data class PlaceMeta(
var total_count: Int, // 검색어에 검색된 문서 수
var pageable_count: Int, // total_count 중 노출 가능 문서 수, 최대 45 (API에서 최대 45개 정보만 제공)
var is_end: Boolean, // 현재 페이지가 마지막 페이지인지 여부, 값이 false면 page를 증가시켜 다음 페이지를 요청할 수 있음
var same_name: RegionInfo // 질의어의 지역 및 키워드 분석 정보
)

data class RegionInfo(
var region: List<String>, // 질의어에서 인식된 지역의 리스트, ex) '중앙로 맛집' 에서 중앙로에 해당하는 지역 리스트
var keyword: String, // 질의어에서 지역 정보를 제외한 키워드, ex) '중앙로 맛집' 에서 '맛집'
var selected_region: String // 인식된 지역 리스트 중, 현재 검색에 사용된 지역 정보
)

data class Place(
var id: String, // 장소 ID
var place_name: String, // 장소명, 업체명
var category_name: String, // 카테고리 이름
var category_group_code: String, // 중요 카테고리만 그룹핑한 카테고리 그룹 코드
var category_group_name: String, // 중요 카테고리만 그룹핑한 카테고리 그룹명
var phone: String, // 전화번호
var address_name: String, // 전체 지번 주소
var road_address_name: String, // 전체 도로명 주소
var x: String, // X 좌표값 혹은 longitude
var y: String, // Y 좌표값 혹은 latitude
var place_url: String, // 장소 상세페이지 URL
var distanc: String // 중심좌표까지의 거리. 단, x,y 파라미터를 준 경우에만 존재. 단위는 meter
)

 

[4] KakaoAPI.kt 새로 만들고 코드 추가

import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query

interface KakaoAPI {
@GET("v2/local/search/keyword.json") // Keyword.json의 정보를 받아옴
fun getSearchKeyword(
@Header("Authorization") key: String, // 카카오 API 인증키 [필수]
@Query("query") query: String // 검색을 원하는 질의어 [필수]
// 매개변수 추가 가능
// @Query("category_group_code") category: String

): Call<ResultSearchKeyword> // 받아온 정보가 ResultSearchKeyword 클래스의 구조로 담김
}

 

[5] MainActivity.kt에 코드 넣기!

  • import 부분의 example엔 내 package넣기
  • xxxxxx부분은 내 REST KEY 넣기
  • searchKeyword("은행")에 난 은행을 넣었는데 검색하고자 하는 키워드 넣기
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.mechacat.databinding.ActivityMainBinding
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding

companion object {
const val BASE_URL = "https://dapi.kakao.com/"
const val API_KEY = "KakaoAK XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" // REST API 키
}

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

searchKeyword("은행")

}

// 키워드 검색 함수
    private fun searchKeyword(keyword: String) {
    val retrofit = Retrofit.Builder() // Retrofit 구성
    .baseUrl(BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build()
    val api = retrofit.create(KakaoAPI::class.java) // 통신 인터페이스를 객체로 생성
    val call = api.getSearchKeyword(API_KEY, keyword) // 검색 조건 입력

    // API 서버에 요청
    call.enqueue(object: Callback<ResultSearchKeyword> {
    override fun onResponse(
    call: Call<ResultSearchKeyword>,
    response: Response<ResultSearchKeyword>
    ) {
    // 통신 성공 (검색 결과는 response.body()에 담겨있음)
    Log.d("Test", "Raw: ${response.raw()}")
    Log.d("Test", "Body: ${response.body()}")
    }

    override fun onFailure(call: Call<ResultSearchKeyword>, t: Throwable) {
    // 통신 실패
    Log.w("MainActivity", "통신 실패: ${t.message}")
    }
	})
}

}

-> 이때 오류가 떴는데 gradle(:app)에 밑 코드를 넣으면 해결된다.

buildFeatures{
  viewBinding true
}

 

이제 결과를 보면 밑의 접은 글 처럼 로그에 뜬다. 

 

더보기

Body: ResultSearchKeyword(meta=PlaceMeta(total_count=51493, pageable_count=45, is_end=false, same_name=RegionInfo(region=[], keyword=은행, selected_region=)), documents=[Place(id=8124674, place_name=하나은행 본점, category_name=금융,보험 > 금융서비스 > 은행 > 하나은행, category_group_code=BK9, category_group_name=은행, phone=1599-1111, address_name=서울 중구 을지로1가 101-1, road_address_name=서울 중구 을지로 35, x=126.981866951611, y=37.566491371702, place_url=http://place.map.kakao.com/8124674, distanc=null), Place(id=8202771, place_name=KB국민은행 여의도본점, category_name=금융,보험 > 금융서비스 > 은행 > KB국민은행, category_group_code=BK9, category_group_name=은행, phone=02-2073-7114, address_name=서울 영등포구 여의도동 36-3, road_address_name=서울 영등포구 국제금융로8길 26, x=126.927905661537, y=37.5208657732053, place_url=http://place.map.kakao.com/8202771, distanc=null), Place(id=146711523, place_name=KB국민은행 범일동종합금융센터, category_name=금융,보험 > 금융서비스 > 은행 > KB국민은행, category_group_code=BK9, category_group_name=은행, phone=051-647-6713, address_name=부산 동구 범일동 830-58, road_address_name=부산 동구 범일로102번길 8, x=129.060378063338, y=35.1388665447651, place_url=http://place.map.kakao.com/146711523, distanc=null), Place(id=8201410, place_name=KB국민은행 서교동종합금융센터, category_name=금융,보험 > 금융서비스 > 은행 > KB국민은행, category_group_code=BK9, category_group_name=은행, phone=02-335-2103, address_name=서울 마포구 동교동 160-4, road_address_name=서울 마포구 양화로 147, x=126.9220546249462, y=37.556047909888946, place_url=http://place.map.kakao.com/8201410, distanc=null), Place(id=11658267, place_name=KB국민은행 수완지점, category_name=금융,보험 > 금융서비스 > 은행 > KB국민은행, category_group_code=BK9, category_group_name=은행, phone=062-962-3074, address_name=광주 광산구 수완동 1080, road_address_name=광주 광산구 임방울대로 348, x=126.82430857684307, y=35.190995447869476, place_url=http://place.map.kakao.com/11658267, distanc=null), Place(id=1310395223, place_name=우리은행 본점, category_name=금융,보험 > 금융서비스 > 은행 > 우리은행, category_group_code=BK9, category_group_name=은행, phone=02-2002-3000, address_name=서울 중구 회현동1가 203, road_address_name=서울 중구 소공로 51, x=126.98175547982937, y=37.55944556203258, place_url=http://place.map.kakao.com/1310395223, distanc=null), Place(id=8698423, place_name=KB국민은행 순천종합금융센터, category_name=금융,보험 > 금융서비스 > 은행 > KB국민은행, category_group_code=BK9, category_group_name=은행, phone=061-721-0404, address_name=전남 순천시 연향동 1324-2, road_address_name=전남 순천시 연향번영길 149, x=127.518745142281, y=34.9533138264752, place_url=http://place.map.kakao.com/8698423, distanc=null), Place(id=797662949, place_name=하나은행 수원지점, category_name=금융,보험 > 금융서비스 > 은행 > 하나은행, category_group_code=BK9, category_group_name=은행, phone=031-259-9200, address_name=경기 수원시 팔달구 인계동 763-8, road_address_name=경기 수원시 팔달구 중부대로 32, x=127.020340482771, y=37.2749647641254, place_url=http://place.map.kakao.com/797662949, distanc=null), Place(id=793156041, place_name=IBK기업은행 영업부본점, category_name=금융,보험 > 금융서비스 > 은행 > IBK기업은행, category_group_code=BK9, category_group_name=은행, phone=02-1588-2588, address_name=서울 중구 을지로2가 50, road_address_name=서울 중구 을지로 79, x=126.9865643391636, y=37.56649199972894, place_url=http://place.map.kakao.com/793156041, distanc=null), Place(id=328946284, place_name=KB국민은행 부평종합금융센터, category_name=금융,보험 > 금융서비스 > 은행 > KB국민은행, category_group_code=BK9, category_ 202

이를 화면에 보이게 수정하고 "제주 카페"라는 키워드를 넣으면 아래 사진처럼 중구난방으로 뜬다.

 


 

3. Android Studio에서의 세부 작업들

(1) 하나의 검색어에서 상위 15개만 출력

(2) 다수의 검색어도 되는지 확인

(3) 30개 카테고리, 각 상위 5개 값만 리스트로 저장, 출력

(4) 각 카테고리의 상위 5개 값 중에 1개씩 랜덤으로 뽑아서 또다른 리스트로 저장

(5) 카테고리 랜덤으로 10개 값만 뽑기

(6) 보기 좋게 원하는 정보만 출력되게 만들기

 

3번의 작업들은 chatgpt한테 자세히 물어보며 단계별로 실험하고 테스트를 진행했다.

(간단한 작업이라 생각되어서 생략했지만 혹시 궁금하다면 댓글을 달아주세요)

 

다음 글은 캡스톤 디자인 시리즈의 하이라이트 마무리(느낀점)이다. 

배운점, 아쉬운점 등등 내 심정들이 많이 담길 예정이다.