Route 클래스 사용 관련하여 질문있습니다

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() {}
}

왜 안되는지…

polyline은 사용 가능한데… routeline은 왜… 안될까요…

올려주신 코드를 확인해봤는데, KakaoMap Android V2 에서는 사용하지 않는 코드들 입니다.

  1. 우선, mapView?.routeLineManager 는 존재하지 않는 API 입니다. RouteLineManager 는 KakaoMap 을 통해서만 접근할 수 있습니다.

  2. 또한, mapView?.setMapCenterPoint(MapPoint.mapPointWithGeoCoord 이나 MapPOIItem 같은 것도 V2 SDK 에서는 없는 API 들 입니다.

다른 지도 SDK 와 같이 사용되고 있지 않은지 또는 프로젝트를 Clean/Build 등 통해 다시 확인해 보셔야 할 것 같습니다.

gradle 설정은 확실히 문제 없는게 맞고 mainactivity 코드 수정이 필요한건가요?

MainActivity 에 import net.daum.mf.map.api.* 코드가 import 되어 있는걸로 보아 KakaoMap Android V2 이 사용되고 있는게 아니라, 예전 구버전 V1 지도 SDK 가 빌드된 걸로 사용되고 있는걸로 보여집니다.

구 버전 지도 SDK 관련 설정을 모두 삭제 하시고 V2 지도 SDK 만 살려서 다시 프로젝트를 설정해 보시기 바랍니다.

네. MainActivity 코드도 수정하셔야합니다. 현재 올려주신 MainActivity 코드는 V2 버전에는 없는 코드들 입니다. 구 버전 V1 코드를 사용 중인 걸로 보여집니다.