위치 검색 지도 앱
주요 기능
- POI 기능 사용해 retrofit을 통해 가져온 response 바디를 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()
}
}
}
TmapPOI 데이터 뿌리기
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 Key를 AndroidManifest.xml 파일의 application 태그 안에 아래와 같이 추가해준다.
<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을 추가할 뷰에 fragment를 넣어준다.
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)
}
}
}
결과화면
Author And Source
이 문제에 관하여(위치 검색 지도 앱), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jieun0915/위치-검색-지도-앱저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)