gradle 설정이고
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
def flutterVersionCode = localProperties.getProperty("flutter.versionCode", "1").toInteger()
def flutterVersionName = localProperties.getProperty("flutter.versionName", "1.0")
android {
namespace = "com.example.easytrip"
compileSdkVersion 34
defaultConfig {
applicationId = "com.example.easytrip"
minSdkVersion 23
targetSdkVersion 34
versionCode flutterVersionCode
versionName flutterVersionName
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
repositories {
flatDir {
dirs 'libs'
}
}
repositories {
google()
mavenCentral()
flatDir {
dirs 'libs'
}
}
buildFeatures {
dataBinding true
}
}
flutter {
source = "../.."
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'org.json:json:20210307'
implementation 'com.kakao.maps.open:android:2.11.9'
}
buildscript {
ext.kotlin_version = '1.8.0'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// 다른 필요한 플러그인 추가
}
}
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://devrepo.kakao.com/nexus/repository/kakaomap-releases/' }
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
다음과 같은 문제가 발생하고 있습니다.
전체코드는 아래와 같습니다.
package com.example.easytrip
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Base64
import android.util.Log
import android.view.View
import android.view.ViewGroup
import com.kakao.vectormap.LatLng
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
import net.daum.mf.map.api.*
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import java.io.IOException
import java.security.MessageDigest
import com.kakao.vectormap.route.RouteLine
import com.kakao.vectormap.route.RouteLineLayer
import com.kakao.vectormap.route.RouteLineOptions
import com.kakao.vectormap.route.RouteLineSegment
import com.kakao.vectormap.route.RouteLineStyle
class MainActivity : FlutterActivity() {
var mapView: MapView? = null
private var startMarker: MapPOIItem? = null
private var endMarker: MapPOIItem? = null
private var routeLineLayer: RouteLineLayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
getAppKeyHash()
mapView = MapView(this)
routeLineLayer = mapView?.routeLineManager?.layer // RouteLineLayer 초기화
MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, "com.example.easytrip/search")
.setMethodCallHandler { call, result ->
when (call.method) {
"search" -> {
val keyword = call.arguments as String
searchPlaces(keyword, result)
}
else -> result.notImplemented()
}
}
MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, "com.example.easytrip/map")
.setMethodCallHandler { call, result ->
when (call.method) {
"zoomIn" -> {
mapView?.zoomIn(true)
result.success(null)
}
"zoomOut" -> {
mapView?.zoomOut(true)
result.success(null)
}
"moveToCurrentLocation" -> {
mapView?.setMapCenterPoint(MapPoint.mapPointWithGeoCoord(37.5665, 126.9780), true)
result.success(null)
}
"moveToLocation" -> {
val latitude = (call.arguments as Map<*, *>?)?.get("latitude") as? Double
val longitude = (call.arguments as Map<*, *>?)?.get("longitude") as? Double
val isStartPoint = (call.arguments as Map<*, *>?)?.get("isStartPoint") as? Boolean
if (latitude != null && longitude != null) {
mapView?.setMapCenterPoint(MapPoint.mapPointWithGeoCoord(latitude, longitude), true)
if (isStartPoint != null) {
addMarker(latitude, longitude, isStartPoint)
} else {
addMarker(latitude, longitude)
}
}
result.success(null)
}
"addMarker" -> {
val latitude = (call.arguments as Map<*, *>?)?.get("latitude") as? Double
val longitude = (call.arguments as Map<*, *>?)?.get("longitude") as? Double
val isStartPoint = (call.arguments as Map<*, *>?)?.get("isStartPoint") as? Boolean
if (latitude != null && longitude != null) {
if (isStartPoint != null) {
addMarker(latitude, longitude, isStartPoint)
} else {
addMarker(latitude, longitude)
}
}
result.success(null)
}
"removeMapView" -> {
removeMapView()
result.success(null)
}
"removeAllMarkers" -> {
removeAllMarkers()
result.success(null)
}
"drawRouteLine" -> {
val startLat = (call.arguments as Map<*, *>)["startLatLng"] as Map<*, *>
val endLat = (call.arguments as Map<*, *>)["endLatLng"] as Map<*, *>
drawRouteLine(
startLat["latitude"] as Double,
startLat["longitude"] as Double,
endLat["latitude"] as Double,
endLat["longitude"] as Double
)
result.success(null)
}
else -> result.notImplemented()
}
}
}
private fun removeMapView() {
if (mapView != null) {
val parent = mapView!!.parent as? ViewGroup
parent?.removeView(mapView)
mapView = null
}
}
private fun removeAllMarkers() {
mapView?.removeAllPOIItems()
}
override fun onDestroy() {
super.onDestroy()
mapView = null
}
fun addMarker(latitude: Double, longitude: Double) {
val marker = MapPOIItem().apply {
itemName = "Selected Location"
mapPoint = MapPoint.mapPointWithGeoCoord(latitude, longitude)
markerType = MapPOIItem.MarkerType.CustomImage
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.custom_marker)
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, 150, 150, false)
customImageBitmap = resizedBitmap
isCustomImageAutoscale = false
setCustomImageAnchor(0.5f, 1.0f)
}
mapView?.addPOIItem(marker)
}
fun addMarker(latitude: Double, longitude: Double, isStartPoint: Boolean) {
val marker = MapPOIItem().apply {
itemName = if (isStartPoint) "출발지" else "도착지"
mapPoint = MapPoint.mapPointWithGeoCoord(latitude, longitude)
markerType = MapPOIItem.MarkerType.CustomImage
val bitmapResource = if (isStartPoint) R.drawable.start_marker else R.drawable.end_marker
val bitmap = BitmapFactory.decodeResource(resources, bitmapResource)
val resizedBitmap = Bitmap.createScaledBitmap(bitmap, 150, 150, false)
customImageBitmap = resizedBitmap
isCustomImageAutoscale = false
setCustomImageAnchor(0.5f, 1.0f)
}
if (isStartPoint) {
startMarker?.let { mapView?.removePOIItem(it) }
startMarker = marker
} else {
endMarker?.let { mapView?.removePOIItem(it) }
endMarker = marker
}
mapView?.addPOIItem(marker)
}
fun drawRouteLine(startLatitude: Double, startLongitude: Double, endLatitude: Double, endLongitude: Double) {
val client = OkHttpClient()
val url = "https://apis-navi.kakaomobility.com/v1/directions?origin=$startLongitude,$startLatitude&destination=$endLongitude,$endLatitude"
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "KakaoAK api") // REST API 키 입력
.build()
client.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
runOnUiThread {
Log.e("RouteLine", "Failed to get route: ${e.message}")
}
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
if (!response.isSuccessful) {
Log.e("RouteLine", "Failed to get route: ${response.message}")
return
}
val responseData = response.body?.string()
Log.d("RouteLine", "Response Data: $responseData") // 응답 데이터를 로그로 출력
val jsonResponse = JSONObject(responseData)
val routes = jsonResponse.getJSONArray("routes")
if (routes.length() == 0) {
Log.e("RouteLine", "No routes found in the response")
return
}
// 경로 데이터에서 vertexes를 추출
val routePoints = routes.getJSONObject(0).getJSONArray("sections").getJSONObject(0).getJSONArray("roads")
val style = RouteLineStyle.from(this@MainActivity, R.style.SimpleRouteLineStyle) // 라인 스타일 설정
val segments = mutableListOf<RouteLineSegment>()
// 각 도로 구간의 vertexes를 사용하여 경로 점을 추가
for (i in 0 until routePoints.length()) {
val road = routePoints.getJSONObject(i)
val vertexes = road.getJSONArray("vertexes")
val points = mutableListOf<LatLng>()
for (j in 0 until vertexes.length() step 2) {
val lng = vertexes.getDouble(j)
val lat = vertexes.getDouble(j + 1)
points.add(LatLng.from(lat, lng))
}
segments.add(RouteLineSegment.from(points, style))
}
runOnUiThread {
// RouteLine 추가
val options = RouteLineOptions.from(segments)
routeLineLayer?.addRouteLine(options)
// 경로가 잘 보이도록 화면을 경로 전체를 포함하는 위치로 이동
val firstPoint = segments[0].points.first()
mapView?.moveCamera(CameraUpdateFactory.newMapPoint(firstPoint.toMapPoint(), 10))
}
}
})
}
private fun searchPlaces(keyword: String, result: MethodChannel.Result) {
val client = OkHttpClient()
val url = "https://dapi.kakao.com/v2/local/search/keyword.json?query=$keyword"
val apiKey = "api" // REST API 키 입력
val request = Request.Builder()
.url(url)
.addHeader("Authorization", "KakaoAK $apiKey")
.build()
Log.d("searchPlaces", "Request URL: $url")
Log.d("searchPlaces", "Authorization: KakaoAK $apiKey")
client.newCall(request).enqueue(object : okhttp3.Callback {
override fun onFailure(call: okhttp3.Call, e: IOException) {
runOnUiThread {
Log.e("searchPlaces", "Request failed: ${e.message}")
result.error("ERROR", e.message, null)
}
}
override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
val responseData = response.body?.string()
runOnUiThread {
Log.d("searchPlaces", "Response code: ${response.code}")
if (response.isSuccessful && responseData != null) {
Log.d("searchPlaces", "Response successful: $responseData")
result.success(responseData)
} else {
Log.e("searchPlaces", "Response failed: ${response.message}")
result.error("ERROR", "Failed to get response", null)
}
}
}
})
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory("KakaoMapView", KakaoMapFactory(this))
}
private fun getAppKeyHash() {
try {
val info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
for (signature in info.signatures) {
val md = MessageDigest.getInstance("SHA")
md.update(signature.toByteArray())
val something = String(Base64.encode(md.digest(), 0))
Log.e("Hash key", something)
}
} catch (e: Exception) {
Log.e("name not found", e.toString())
}
}
}
class KakaoMapFactory(private val activity: Activity) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
val mapView = MapView(activity)
(activity as MainActivity).mapView = mapView
val creationParams = args as Map<String, Any>
val startLatitude = creationParams["startLatitude"] as? Double
val startLongitude = creationParams["startLongitude"] as? Double
val endLatitude = creationParams["endLatitude"] as? Double
val endLongitude = creationParams["endLongitude"] as? Double
if (startLatitude != null && startLongitude != null) {
activity.addMarker(startLatitude, startLongitude, true) // 출발지
}
if (endLatitude != null && endLongitude != null) {
activity.addMarker(endLatitude, endLongitude, false) // 도착지
}
return KakaoMapView(mapView)
}
}
class KakaoMapView(private val mapView: MapView) : PlatformView {
override fun getView(): View {
return mapView
}
override fun dispose() {}
}
왜 안되는지…