位置検索マップアプリケーション


主な機能
  • POI機能を用いて,改造により得られた応答主体をGsonに分割し,
  • を用いる.
  • 検索画面から受信データはintentに移動し、GoogleMapを介して地図位置
  • をマークする.
  • あなたの位置データをPOI APIを通じて現在の私の位置情報を取得し、GSONに分割し、タグを通じて再びデータを送信します.
  • テクノロジーの使用
  • GoogleMap poi
  • Coroutine
  • Retrofit
  • OkHttp
  • アプリケーションレベルbuild.gradle
    plugins {
        id 'kotlin-android-extensions'
    }
    
    dependencies {
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
        implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
    
        implementation "com.squareup.okhttp3:okhttp:4.8.0"
        implementation "com.squareup.okhttp3:logging-interceptor:4.8.0"
    
        implementation "com.squareup.retrofit2:retrofit:2.9.0"
        implementation "com.squareup.retrofit2:converter-gson:2.9.0"
        
        implementation 'com.google.android.gms:play-services-maps:18.0.2'
        implementation 'com.google.android.gms:play-services-location:19.0.1'
    }
    UI
    activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <EditText
            android:id="@+id/searchBarInputView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/searchButton"
            app:layout_constraintTop_toTopOf="parent"
            android:hint="@string/please_input_search_keyword"/>
    
        <Button
            android:id="@+id/searchButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_marginEnd="5dp"
            android:text="@string/search"/>
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/searchBarInputView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
    
        <TextView
            android:id="@+id/emptyResultTextView"
            tools:visibility="visible"
            android:visibility="gone"
            android:text="@string/empty_result_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    RecyclerViewを実装してMockingデータを解放
    model/LocationLatLngEntity.kt
    @Parcelize
    data class LocationLatLngEntity(
        val latitude: Float,
        val longitude: Float
    ): Parcelable
    model/SearchResultEntity.kt
    @Parcelize
    data class SearchResultEntity(
        val fullAddress: String,
        val buildingName: String,
        val locationLatLng: LocationLatLngEntity
    ): Parcelable
    SearchRecyclerAdapter.kt
    import android.view.LayoutInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.recyclerview.widget.RecyclerView
    import com.example.aop_part3_chapter04.databinding.ViewholderSearchResultItemBinding
    import com.example.aop_part3_chapter04.model.SearchResultEntity
    
    class SearchRecyclerAdapter: RecyclerView.Adapter<SearchRecyclerAdapter.SearchResultItemViewHolder>() {
    
        private var searchResultList: List<SearchResultEntity> = listOf()
        private lateinit var searchResultClickListener: (SearchResultEntity) -> Unit
    
        class SearchResultItemViewHolder(val binding: ViewholderSearchResultItemBinding, val searchResultClickListener: (SearchResultEntity) -> Unit) : RecyclerView.ViewHolder(binding.root) {
    
            fun bindData(data: SearchResultEntity) = with(binding) {
                titleTextView.text = data.buildingName
                subtitleTextView.text = data.fullAddress
            }
            fun bindViews(data: SearchResultEntity) {
                binding.root.setOnClickListener {
                    searchResultClickListener(data)
                }
            }
        }
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchResultItemViewHolder {  // SearchResultItemViewHolder 반환
            val view = ViewholderSearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return SearchResultItemViewHolder(view, searchResultClickListener)
        }
    
        override fun onBindViewHolder(holder: SearchResultItemViewHolder, position: Int) {
            holder.bindData(searchResultList[position])
            holder.bindViews(searchResultList[position])
        }
    
        override fun getItemCount(): Int = searchResultList.size
    
        // 데이터를 반영해줌과 동시에 리스너를 같이 등록
        fun setSearchResultList(searchResultList: List<SearchResultEntity>, searchResultClickListener: (SearchResultEntity) -> Unit) {
            this.searchResultList = searchResultList
            this.searchResultClickListener = searchResultClickListener
            notifyDataSetChanged()
        }
    }
    MainActivity.kt
    import android.annotation.SuppressLint
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import android.widget.Toast
    import androidx.core.view.isVisible
    import com.example.aop_part3_chapter04.databinding.ActivityMainBinding
    import com.example.aop_part3_chapter04.model.LocationLatLngEntity
    import com.example.aop_part3_chapter04.model.SearchResultEntity
    
    class MainActivity : AppCompatActivity() {
    
        private lateinit var binding: ActivityMainBinding
        private lateinit var adapter: SearchRecyclerAdapter
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            initAdapter()
            initViews()
            initData()
            setData()
        }
    
        private fun initViews() = with(binding) {
            emptyResultTextView.isVisible = false
            recyclerView.adapter = adapter
        }
    
        private fun initAdapter() {
            adapter = SearchRecyclerAdapter()
        }
    
        @SuppressLint("NotifyDataSetChanged")
        private fun initData() {
            adapter.notifyDataSetChanged()  // 처음엔 빈 상태로 호출
        }
    
        private fun setData() {
            val dataList = (0..10).map {
                SearchResultEntity(
                    buildingName = "빌딩 $it",
                    fullAddress = "주소 $it",
                    locationLatLng = LocationLatLngEntity(it.toFloat(), it.toFloat()
                    )
                )
            }
            // 만들어진 dataList를 어댑터에 반영
            adapter.setSearchResultList(dataList) {
                Toast.makeText(this, "빌딩이름 : ${it.buildingName} 주소 : ${it.fullAddress}", Toast.LENGTH_SHORT).show()
            }
        }
    }
    MAPPOIデータの分散
    https://openapi.sk.com/
    response/search/Poi.kt
    data class Poi(
        //POI 의  id
        val id: String? = null,
    
        //POI 의 name
        val name: String? = null,
    
        //POI 에 대한 전화번호
        val telNo: String? = null,
    
        //시설물 입구 위도 좌표
        val frontLat: Float = 0.0f,
    
        //시설물 입구 경도 좌표
        val frontLon: Float = 0.0f,
    
        //중심점 위도 좌표
        val noorLat: Float = 0.0f,
    
        //중심점 경도 좌표
        val noorLon: Float = 0.0f,
    
        //표출 주소 대분류명
        val upperAddrName: String? = null,
    
        //표출 주소 중분류명
        val middleAddrName: String? = null,
    
        //표출 주소 소분류명
        val lowerAddrName: String? = null,
    
        //표출 주소 세분류명
        val detailAddrName: String? = null,
    
        //본번
        val firstNo: String? = null,
    
        //부번
        val secondNo: String? = null,
    
        //도로명
        val roadName: String? = null,
    
        //건물번호 1
        val firstBuildNo: String? = null,
    
        //건물번호 2
        val secondBuildNo: String? = null,
    
        //업종 대분류명
        val mlClass: String? = null,
    
        //거리(km)
        val radius: String? = null,
    
        //업소명
        val bizName: String? = null,
    
        //시설목적
        val upperBizName: String? = null,
    
        //시설분류
        val middleBizName: String? = null,
    
        //시설이름 ex) 지하철역 병원 등
        val lowerBizName: String? = null,
    
        //상세 이름
        val detailBizName: String? = null,
    
        //길안내 요청 유무
        val rpFlag: String? = null,
    
        //주차 가능유무
        val parkFlag: String? = null,
    
        //POI 상세정보 유무
        val detailInfoFlag: String? = null,
    
        //소개 정보
        val desc: String? = null
    )
    response/search/Pois.kt
    data class Pois(
        val poi: List<Poi>
    )
    response/search/SearchPoiInfo.kt
    data class SearchPoiInfo(
        val totalCount: String,
        val count: String,
        val page: String,
        val pois: Pois
    )
    response/search/SearchResponse.kt
    data class SearchResponse(
        val searchPoiInfo: SearchPoiInfo
    )
    Key.kt
    object Key {
        const val TMAP_API = "l7xx980ff28df71646dab90dfb4f50429691"
    }
    Uri.kt
    object Url {
        // 도메인
        const val TMAP_URL = "https://apis.openapi.sk.com"
    
        // tmap 위치를 가져오는 pois
        const val GET_TMAP_LOCATION = "/tmap/pois"
    }
    utillity/ApiService.kt
    package com.example.aop_part3_chapter04.utillity
    
    import com.example.aop_part3_chapter04.Key
    import com.example.aop_part3_chapter04.Url
    import com.example.aop_part3_chapter04.response.search.SearchResponse
    import retrofit2.Response
    import retrofit2.http.GET
    import retrofit2.http.Header
    import retrofit2.http.Query
    
    interface ApiService {
    
        @GET(Url.GET_TMAP_LOCATION)
        suspend fun getSearchLocation(
            @Header("appKey") appKey: String = Key.TMAP_API,
            @Query("version") version: Int = 1,
            @Query("callback") callback: String? = null,
            @Query("count") count: Int = 20,
            @Query("searchKeyword") keyword: String,
            @Query("areaLLCode") areaLLCode: String? = null,
            @Query("areaLMCode") areaLMCode: String? = null,
            @Query("resCoordType") resCoordType: String? = null,
            @Query("searchType") searchType: String? = null,
            @Query("multiPoint") multiPoint: String? = null,
            @Query("searchtypCd") searchtypCd: String? = null,
            @Query("radius") radius: String? = null,
            @Query("reqCoordType") reqCoordType: String? = null,
            @Query("centerLon") centerLon: String? = null,
            @Query("centerLat") centerLat: String? = null
        ): Response<SearchResponse>
    }
    utillity/RetrofitUtil
    import com.example.aop_part3_chapter04.BuildConfig
    import com.example.aop_part3_chapter04.Url
    import okhttp3.OkHttpClient
    import okhttp3.logging.HttpLoggingInterceptor
    import retrofit2.Retrofit
    import retrofit2.converter.gson.GsonConverterFactory
    import java.util.concurrent.TimeUnit
    
    object RetrofitUtil {
    
        val apiService: ApiService by lazy { getRetrofit().create(ApiService::class.java) }
    
        private fun getRetrofit(): Retrofit {
    
            return Retrofit.Builder()
                .baseUrl(Url.TMAP_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(buildOkHttpClient())
                .build()
        }
    
        private fun buildOkHttpClient(): OkHttpClient {
            val interceptor = HttpLoggingInterceptor()
            if (BuildConfig.DEBUG) {
                interceptor.level = HttpLoggingInterceptor.Level.BODY
            } else {
                interceptor.level = HttpLoggingInterceptor.Level.NONE
            }
            return OkHttpClient.Builder()
                .connectTimeout(5, TimeUnit.SECONDS) // 5초동안 응답 없으면 에러 실행하도록 함
                .addInterceptor(interceptor)
                .build()
        }
    }
    MainActivity.kt
    class MainActivity : AppCompatActivity(), CoroutineScope {
    
        private lateinit var job: Job
    
        override val coroutineContext: CoroutineContext
            get() = Dispatchers.Main + job
            
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            job = Job()
    
            initAdapter()
            initViews()
            bindViews()
            initData()
        }
        
        private fun bindViews() = with(binding) {
            searchButton.setOnClickListener {
                searchKeyword(searchBarInputView.text.toString())
            }
        }
        
        private fun setData(pois: Pois) {
            val dataList = pois.poi.map {
                SearchResultEntity(
                    buildingName = it.name ?: "빌딩명 없음",
                    fullAddress = makeMainAdress(it),
                    locationLatLng = LocationLatLngEntity(it.noorLat, it.noorLon
                    )
                )
            }
            // 만들어진 dataList 를 어댑터에 반영
            adapter.setSearchResultList(dataList) {
                Toast.makeText(this, "빌딩이름 : ${it.buildingName} 주소 : ${it.fullAddress}, 위도/경도 : ${it.locationLatLng}", Toast.LENGTH_SHORT).show()
            }
        }
    
        private fun searchKeyword(keywordString: String) {
            launch(coroutineContext) { // 메인스레드에서 먼저 시작
                try {
                    withContext(Dispatchers.IO) {
                        val response = RetrofitUtil.apiService.getSearchLocation(
                            keyword = keywordString
                        )
                        if (response.isSuccessful) {
                            val body = response.body()
                            withContext(Dispatchers.Main) {
                                Log.e("response", body.toString())
                                body?.let { searchResponse ->
                                    setData(searchResponse.searchPoiInfo.pois)
                                }
                            }
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    Toast.makeText(this@MainActivity, "검색하는 과정에서 에러가 발생했습니다. : ${e.message}", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        private fun makeMainAdress(poi: Poi): String =
            if (poi.secondNo?.trim().isNullOrEmpty()) {
                (poi.upperAddrName?.trim() ?: "") + " " +
                        (poi.middleAddrName?.trim() ?: "") + " " +
                        (poi.lowerAddrName?.trim() ?: "") + " " +
                        (poi.detailAddrName?.trim() ?: "") + " " +
                        poi.firstNo?.trim()
            } else {
                (poi.upperAddrName?.trim() ?: "") + " " +
                        (poi.middleAddrName?.trim() ?: "") + " " +
                        (poi.lowerAddrName?.trim() ?: "") + " " +
                        (poi.detailAddrName?.trim() ?: "") + " " +
                        (poi.firstNo?.trim() ?: "") + " " +
                        poi.secondNo?.trim()
            }
    }
    <uses-permission android:name="android.permission.INTERNET"/>インターネットを許可する必要がありますが、できます.
    GoogleMapデータの表示
    https://console.developers.google.com/apis/dashboard
    APIキーは、新規プロジェクトの作成およびユーザ認証情報の作成プロジェクトで作成されます.
    アプリケーション制限から「Androidアプリケーション」を選択し、パッケージ名とSHA-1署名証明書のデジタル指紋を入力します.
    cmdウィンドウに"C:\Program Files\Android\Android Studio\jre\bin\keytool" -list -v -keystore "%USERPROFILE%\.android\debug.keystore" -alias androiddebugkey -storepass android -keypass androidのコマンドを入力すると、SHA-1コードが表示されます.
    生成されたAPIキーはAndroidManifestです.xmlファイルのアプリケーションラベルに追加します.次のようにします.
    <application
            <meta-data
                android:name="com.google.android.geo.API_KEY"
                android:value="@string/google_map" />
    </application>
    アプリケーションレベルbuild.Gradleライブラリを追加します.
    implementation 'com.google.android.gms:play-services-maps:18.0.2'
    implementation 'com.google.android.gms:play-services-location:19.0.1'
    クリップをmapを追加するビューに挿入します.
    activity_map.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <fragment
            android:id="@+id/mapFragment"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:name="com.google.android.gms.maps.SupportMapFragment"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    地図上のマークを設定します.
    MapActivity.kt
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import com.example.aop_part3_chapter04.databinding.ActivityMapBinding
    import com.example.aop_part3_chapter04.model.SearchResultEntity
    import com.google.android.gms.maps.CameraUpdateFactory
    import com.google.android.gms.maps.GoogleMap
    import com.google.android.gms.maps.OnMapReadyCallback
    import com.google.android.gms.maps.SupportMapFragment
    import com.google.android.gms.maps.model.LatLng
    import com.google.android.gms.maps.model.Marker
    import com.google.android.gms.maps.model.MarkerOptions
    import java.lang.Exception
    
    class MapActivity : AppCompatActivity(), OnMapReadyCallback{
    
        private lateinit var binding: ActivityMapBinding
        private lateinit var map: GoogleMap
        private var currentSelectMarker: Marker? = null
    
        private lateinit var searchResult: SearchResultEntity
    
        companion object {
            const val SEARCH_RESULT_EXTRA_KEY = "SEARCH_RESULT_EXTRA_KEY"
            const val CAMERA_ZOOM_LEVEL = 17f
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMapBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            if (::searchResult.isInitialized.not()) { 
            // searchResult 가 없으면 intent 값 가져옴
                intent?.let {
                    searchResult = it.getParcelableExtra(SEARCH_RESULT_EXTRA_KEY) ?: throw Exception("데이터가 존재하지 않습니다.")
                    setupGoogleMap()
                }
            }
        }
    
        private fun setupGoogleMap() {
            val mapFragment = supportFragmentManager.findFragmentById(R.id.mapFragment) as SupportMapFragment
            mapFragment.getMapAsync(this)
        }
    
        override fun onMapReady(map: GoogleMap) {
            this.map = map
            currentSelectMarker = setupMarker(searchResult)
    
            currentSelectMarker?.showInfoWindow()
        }
    
        private fun setupMarker(searchResult: SearchResultEntity): Marker {
            val positionLatLng = LatLng(
                searchResult.locationLatLng.latitude.toDouble(), searchResult.locationLatLng.longitude.toDouble()
            )
            val markerOptions = MarkerOptions().apply {
                position(positionLatLng)
                title(searchResult.buildingName)
                snippet(searchResult.fullAddress)
            }
            map.moveCamera(CameraUpdateFactory.newLatLngZoom(positionLatLng, CAMERA_ZOOM_LEVEL))
    
            return map.addMarker(markerOptions)!!
        }
    }
    intentで検索した位置をクリックすると、地図が表示されます.
    MainActivity.kt
    private fun setData(pois: Pois) {
            adapter.setSearchResultList(dataList) {
                startActivity(
                    Intent(this, MapActivity::class.java).apply {
                        putExtra(SEARCH_RESULT_EXTRA_KEY, it)
                    }
                )
            }
        }
    自分の場所情報を読み込む
    adress/AddressInfo.kt
    import com.google.gson.annotations.Expose
    import com.google.gson.annotations.SerializedName
    
    data class AddressInfo(
        @SerializedName("fullAddress")
        @Expose
        val fullAddress: String?,
        @SerializedName("addressType")
        @Expose
        val addressType: String?,
        @SerializedName("city_do")
        @Expose
        val cityDo: String?,
        @SerializedName("gu_gun")
        @Expose
        val guGun: String?,
        @SerializedName("eup_myun")
        @Expose
        val eupMyun: String?,
        @SerializedName("adminDong")
        @Expose
        val adminDong: String?,
        @SerializedName("adminDongCode")
        @Expose
        val adminDongCode: String?,
        @SerializedName("legalDong")
        @Expose
        val legalDong: String?,
        @SerializedName("legalDongCode")
        @Expose
        val legalDongCode: String?,
        @SerializedName("ri")
        @Expose
        val ri: String?,
        @SerializedName("bunji")
        @Expose
        val bunji: String?,
        @SerializedName("roadName")
        @Expose
        val roadName: String?,
        @SerializedName("buildingIndex")
        @Expose
        val buildingIndex: String?,
        @SerializedName("buildingName")
        @Expose
        val buildingName: String?,
        @SerializedName("mappingDistance")
        @Expose
        val mappingDistance: String?,
        @SerializedName("roadCode")
        @Expose
        val roadCode: String?
    )
    adress/AddressInfoResponse.kt
    data class AddressInfoResponse(
        val addressInfo: AddressInfo
    )
    utillity/ApiService.kt
    @GET(Url.GET_TMAP_REVERSE_GEO_CODE)
        suspend fun getReverseGeoCode(
            @Header("appKey") appKey: String = Key.TMAP_API,
            @Query("version") version: Int = 1,
            @Query("callback") callback: String? = null,
            @Query("lat") lat: Double,
            @Query("lon") lon: Double,
            @Query("coordType") coordType: String? = null,
            @Query("addressType") addressType: String? = null
        ): Response<AddressInfoResponse>
    Url.kt
    object Url {
        const val GET_TMAP_REVERSE_GEO_CODE = "/tmap/geo/reversegeocoding"
    }
    MapActivity.kt
    class MapActivity : AppCompatActivity(), OnMapReadyCallback, CoroutineScope{
    
        private lateinit var job: Job
    
        override val coroutineContext: CoroutineContext
            get() = Dispatchers.Main + job
    
        private lateinit var locationManager: LocationManager
    
        private lateinit var myLocationListener: MyLocationListener
    
        companion object {
            const val SEARCH_RESULT_EXTRA_KEY = "SEARCH_RESULT_EXTRA_KEY"
            const val CAMERA_ZOOM_LEVEL = 17f
            const val PERMISSION_REQUEST_CODE = 101
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMapBinding.inflate(layoutInflater)
            setContentView(binding.root)
            
            job = Job()
    
        private fun bindViews() = with(binding) {
            currentLocationButton.setOnClickListener {
                getMyLocation()
            }
        }
        
        private fun getMyLocation() {
            if (::locationManager.isInitialized.not()) {
                locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
            }
    
            // GPS 권한
            val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
            if (isGpsEnabled) {
                if (ContextCompat.checkSelfPermission(
                        this,
                        Manifest.permission.ACCESS_FINE_LOCATION
                    ) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(
                        this,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(
                            Manifest.permission.ACCESS_FINE_LOCATION,
                            Manifest.permission.ACCESS_COARSE_LOCATION
                        ),
                        PERMISSION_REQUEST_CODE
                    )
                } else {
                    setMyLocationListener()
                }
            }
        }
    
    	// 현재 내 위치에 대한 정보 불러오기
        @SuppressLint("MissingPermission")
        private fun setMyLocationListener() {
            val minTime = 1500L
            val minDistance = 100f
    
            if (::myLocationListener.isInitialized.not()) {
                myLocationListener = MyLocationListener()
            }
            with(locationManager) {
                requestLocationUpdates(
                    LocationManager.GPS_PROVIDER,
                    minTime, minDistance, myLocationListener
                )
                requestLocationUpdates(
                    LocationManager.GPS_PROVIDER,
                    minTime, minDistance, myLocationListener
                )
            }
        }
    
        private fun onCurrentLocationChanged(locationLatLngEntity: LocationLatLngEntity) {
            map.moveCamera(CameraUpdateFactory.newLatLngZoom(
                LatLng(
                    locationLatLngEntity.latitude.toDouble(),
                    locationLatLngEntity.longitude.toDouble()
                ), CAMERA_ZOOM_LEVEL))
            loadReverseGeoInformation(locationLatLngEntity)
            removeLocationListener()
        }
    
        // 내 위치 정보 불러오기
        private fun loadReverseGeoInformation(locationLatLngEntity: LocationLatLngEntity) {
            launch(coroutineContext) {
                try {
                    withContext(Dispatchers.IO) {
                        val response = RetrofitUtil.apiService.getReverseGeoCode(
                            lat = locationLatLngEntity.latitude.toDouble(),
                            lon = locationLatLngEntity.longitude.toDouble()
                        )
                        if (response.isSuccessful) {
                            val body = response.body()
                            withContext(Dispatchers.Main) {
                                Log.e("list", body.toString())
                                body?.let {
                                    currentSelectMarker = setupMarker(SearchResultEntity(
                                        fullAddress = it.addressInfo.fullAddress ?: "주소 정보 없음",
                                        buildingName = "내 위치",
                                        locationLatLng = locationLatLngEntity
                                    ))
                                    currentSelectMarker?.showInfoWindow()
                                }
                            }
                        }
                    }
                } catch (e: Exception) {
                    e.printStackTrace()
                    Toast.makeText(this@MapActivity, "검색하는 과정에서 에러가 발생했습니다.", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        private fun removeLocationListener() {
            if (::locationManager.isInitialized && ::myLocationListener.isInitialized) {
                locationManager.removeUpdates(myLocationListener)
            }
        }
    
        // 현재위치 권한
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
            if (requestCode == PERMISSION_REQUEST_CODE) {
                // ACCESS_FINE_LOCATION 과 ACCESS_COARSE_LOCATION 권한 체크
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED
                    && grantResults[1] == PackageManager.PERMISSION_GRANTED
                ) {
                    setMyLocationListener()
                } else {
                    Toast.makeText(this, "권한을 받지 못했습니다.", Toast.LENGTH_SHORT).show()
                }
            }
        }
    
        inner class MyLocationListener: LocationListener {
    
            override fun onLocationChanged(location: Location) {
                val locationLatLngEntity = LocationLatLngEntity(
                    location.latitude.toFloat(),
                    location.longitude.toFloat()
                )
                onCurrentLocationChanged(locationLatLngEntity)
            }
    
        }
    }
    結果画面