[Android] kakao map ์ฌ์ฉํ๊ธฐ
๐โโ๏ธ kakao sdk ์ฌ์ฉ ์ด๊ธฐ ์ค์
์ฐ์ ๊ธฐ๋ณธ์ ์ผ๋ก kakao sdk ์ฌ์ฉ์ ์ํด ํ๋ก์ ํธ๋ฅผ ๋ฑ๋กํ๊ณ app key๋ฅผ ๋ฐ์์ค๋ ๊ฒ๊น์ง ๋ง๋ฌด๋ฆฌํ๋ค๋ ๊ฐ์ ํ์ ๊ทธ ๋ค์ ๋จ๊ณ๋ถํฐ ์ฐจ๊ทผ์ฐจ๊ทผ ์ ๋ฆฌํด๋ณด๊ณ ์ ํ๋ค.
๐โโ๏ธ kakao map sdk ๋ค์ด
์ฐ์ kakao map์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ํ์ํ sdk๋ฅผ ๋ค์ด๋ฐ์์ผ ํ๋ค. ๋ค์ ๊ณต์ ๋ฌธ์์์ sdk๋ฅผ ๋ค์ด๋ฐ์ ์์ถ์ ํ์ด์ฃผ๋ฉด ๋ค์์ ํดํฐ, ํ์ผ์ ์ป์ ์ ์๋ค.
- libDaumMapAndroid.jar
- ์ํคํ ์ณ๋ณ๋ก libMapEngineApi.so ์ธ ๊ฐ์ ๋ฐ์ด๋๋ฆฌ ํ์ผ
์ฐ์ libDaumMapAndroid.jar ํ์ผ์ /app/libs/์ ๋ณต์ฌ๋ฅผ ํ๊ณ , libMapEngineApi.so ํ์ผ์ /app/source/main/jniLibs ํด๋๋ฅผ ๋ง๋ค์ด ๊ทธ ์์ ๋ณต์ฌํด์ฃผ๋ฉด ๋๋ค.
์ด์ ๋ฑ๋กํ jar ํ์ผ์ dependency๋ฅผ ์ถ๊ฐํด์ค์ผ ํ๋ค! ์ฐ์ File -> Project Structure๋ก ๋ค์ด๊ฐ ์ผ์ชฝ ๋ฉ๋ด์ Dependencies๋ฅผ ํด๋ฆฐํ๋ค. Modules ๋ถ๋ถ์ app์ ์ ํํ๊ณ Declared Dependencies์ +๋ฅผ ๋๋ฌ jar dependency๋ฅผ ์ถ๊ฐํ๋ค.
์ฌ๊ธฐ์ step1์๋ src\main\jniLibs๋ฅผ ์ ๋ ฅํ๊ณ , step2์๋ implementation์ ์ ํํ ํ Apply, Ok ๋ฒํผ์ ํด๋ฆญํด ์ ์ฉ์ ํ๋ค.
์ฌ๊ธฐ๊น์ง ์งํํ๋ค๋ฉด build ํ์ผ์ ๋ค์์ dependency๊ฐ ์ถ๊ฐ๋์ด ์๋์ง ํ์ธํ์! ๊ทธ๋ฆฌ๊ณ ๋ง์ฝ ์ถ๊ฐ๋์ด ์์ง ์๋ค๋ฉด ์๋์ ์ฝ๋๋ฅผ ์ถ๊ฐํด๋ผ!
//kakao map
implementation files('libs/libDaumMapAndroid.jar')
์ด์ ๋ง์ง๋ง์ผ๋ก manifest์์ permission ๋ฐ app key๋ฅผ ์ถ๊ฐํด์ฃผ์ด์ผ ํ๋ค.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
// internet ๋ฐ location ๊ถํโญ๏ธ
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application
android:usesCleartextTraffic="true // ์ํธํ ๋์ง ์์ ์๋ฒ์ ํต์ ์ด ํ์ํจโญ๏ธ
android:name=".presentation.base.GlobalApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/ic_sopar_logo"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Sopar.Splash"
tools:targetApi="31">
<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="oauth"
android:scheme="kakao${NATIVE_APP_KEY}" />
</intent-filter>
</activity>
...
// app key ์ถ๊ฐโญ๏ธ
<meta-data android:name="com.kakao.sdk.AppKey" android:value="${NATIVE_APP_KEY}"/>
</application>
</manifest>
- ์นด์นด์ค ๋งต api ํธ์ถ์์๋ http๊ฐ ์ฌ์ฉ๋๋ฏ๋ก ์์ usesCleartextTraffic๋ฅผ true๋ก ์ค์ ํด์ผ ํ๋ค.
- ๊ณต์๋ฌธ์์์๋ ACCESS_FINE_LOCATION ๊ถํ๋ง์ ์ค์ ํ์ง๋ง ํด๋น ๊ถํ๋ง์ ์ค์ ํ๋ฉด ๋ค์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.
- If you need access to FINE location, you must request both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION
- ๋ฐ๋ผ์ wifi๋ ๊ธฐ์ง๊ตญ ์ ํธ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์น๋ฅผ ๊ฐ์ ธ์ค๋ ACCESS_COARSE_LOCATION ๊ถํ๋ ํจ๊ป ์ค์ ํด์ค๋ค.
๊ทธ๋ ๋ค๋ฉด ์ด์ ์นด์นด์ค ๋งต์ ํ๋ฉด์ ๋์ด๋ณด์!
๐โโ๏ธ MapView ์ฌ์ฉํ๊ธฐ
โ ์นด์นด์ค๋งต ํ๋ฉด์ ๋์ฐ๊ธฐ
ํ๋ฉด์ ์ง๋๋ฅผ ๋์ฐ๊ธฐ ์ํด์๋ net.daum.mf.map.api.MapView๋ฅผ ํ์ฉํ ์ ์๋ค.
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presentation.map.MapFragment">
<net.daum.mf.map.api.MapView
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
MapView๋ฅผ ์ฌ์ฉํ ๋๋ net.daum.android.map.MapView์ ํท๊ฐ๋ฆฌ์ง ์๋๋ก ํ๋ค!
์ฌ๊ธฐ๊น์ง ์งํํ๋ฉด ์๋ ์ฌ์ง๊ฐ์ด ๋งต์ด ์ ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ ๊ฒ์ด๋ค.
๋ณธ๊ฒฉ์ ์ผ๋ก ๋งต์ ์ฌ์ฉํ๊ธฐ ์ ์ ํ์ํ ๊ถํ ์น์ธ ์์ ์ด ํ์ํ๋ค!
โ ๊ถํ ์น์ธ ์งํํ๊ธฐ
๊ถํ ์น์ธ์ registerForActivityResult์ ActivityResultContracts๋ฅผ ์ฌ์ฉํด ์งํํ๊ณ ์ ํ๋ค.
์ฐ์ ๊ถํ์ด ์น์ธ๋์๋์ง๋ฅผ ๋จผ์ ContextCompat.checkSelfPermission๋ฅผ ํตํด ํ์ธํด์ค๋ค.
private fun isAllPermissionsGranted(): Boolean = REQUIRED_PERMISSIONS.all { permission ->
ContextCompat.checkSelfPermission(requireContext(), permission) ==
PackageManager.PERMISSION_GRANTED
}
๋ง์ฝ ์น์ธ์ด ํ์ํ๋ค๋ฉด ๊ถํ์ ๋ํ ์น์ธ ์์ฒญ์ ์งํํ๋ฉด ๋๋ค. ์ฐ์ ๊ทธ ๊ณผ์ ์์ ์ฌ์ฉํ requestPermissionLauncher๋ฅผ ์ ์ํด์ค๋ค.
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.entries.forEach { permission ->
when {
//๊ถํ์ ์น์ธํ์์ ๋
permission.value -> {
Toast.makeText(requireContext(), "ํ์ํ ๊ถํ์ด ์น์ธ๋์์ต๋๋ค!", Toast.LENGTH_SHORT).show()
}
//๊ถํ์ ์์ ํ ๊ฑฐ๋ถํ์ ๊ฒฝ์ฐ
shouldShowRequestPermissionRationale(permission.key) -> {
Toast.makeText(requireContext(), "์ค์ ์์ ๊ถํ์ ์น์ธํด์ฃผ์ธ์!", Toast.LENGTH_SHORT).show()
}
//๊ถํ์ ์น์ธํ์ง ์์ ๊ฒฝ์ฐ
else -> {
Toast.makeText(requireContext(), "์ค์ ์์ ๊ถํ์ ์น์ธํด์ฃผ์ธ์!", Toast.LENGTH_SHORT).show()
}
}
}
}
์ด์ ์ด๋ฅผ ์ด์ฉํด ํ์ํ ๊ถํ์ ์์ฒญํ๋ค.
if (!isAllPermissionsGranted()) {
//ํ์ํ ๊ถํ์ด ์น์ธ๋์ด ์์ง ์์ ๊ฒฝ์ฐ
requestPermissionLauncher.launch(REQUIRED_PERMISSIONS)
}
์ฌ๊ธฐ์ ํ์ํ ๊ถํ์ companion object ์ ๋ฐฐ์ด ํํ๋ก ๋ฏธ๋ฆฌ ์ ์ํด์ฃผ์๋ค.
companion object {
private val REQUIRED_PERMISSIONS = arrayOf(
android.Manifest.permission.ACCESS_COARSE_LOCATION,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.INTERNET
)
}
์ด๋ ๊ฒ ํ๋ค๋ฉด ์ด์ ํ์ํ ๊ถํ์ด ๋ชจ๋ ์น์ธ๋์๋ค.
์ด์ ์นด์นด์ค๋งต์ ํ์ฉํ๋ ๋จ๊ณ๋ง ๋จ์๋ค! ํด๋น ๋ด์ฉ์ ๋ํด์๋ ์ถํ ๊ฒ์๊ธ์ ํตํด ์ ๋ฆฌํด๋ณด๊ฒ ๋ค!
(2023.05.12) ์ถ๊ฐ ์ ๋ฆฌ ๋ถ๋ถ
hilt์ ํจ๊ป kakao map์ ์ฌ์ฉํ ๊ฒฝ์ฐ, xml์์ ๋ฐ๋ก MapView๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ
Caused by: android.view.InflateException: Binary XML file line #12 in org.sopar:layout/fragment_map: Error inflating class net.daum.mf.map.api.MapView
์์ ์ค๋๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค. ๋ฐ๋ผ์ addView๋ฅผ ํตํด MapView๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ ์ ์ฉํด์ผ ํ๋ค.
<RelativeLayout
android:id="@+id/map_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapView = MapView(requireActivity())
binding.mapView.addView(mapView)
...
๐โโ๏ธ ๋ถ์กฑํ ๋ถ๋ถ์ด ์๋ค๋ฉด ๋ง์ํด์ฃผ์ธ์! ๊ฐ์ฌํฉ๋๋ค!
๋๊ธ๋จ๊ธฐ๊ธฐ