와챠의 우당탕탕 개발 기록장
[Android] 사용자 위치 정보 + 지역 이름 알아오기(play-services-location) 구현기 본문
[Android] 사용자 위치 정보 + 지역 이름 알아오기(play-services-location) 구현기
minWachya 2025. 7. 4. 22:55아래 라이브러리를 사용해 사용자 위치를 받아오고 + 지역명까지 받아오려고 한다.
com.google.android.gms:play-services-location
받아온 위치 정보로는 기상청 날씨 api에 전달해 해당 지역의 날씨를 받아오는 데에 사용되고,
지역명은 사진과 같이 하단에 적어주려고 한다!
(참고로 위치 관련 권한 받는 내용은 이전 포스트에 있으니 생략함)
관련 정보나 버전 정보는 아래 참고!
위치 인식 앱 빌드 | Sensors and location | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 개발 리소스 추가 리소스 앱의 위치 정보에 관한 액세
developer.android.com
1. 라이브러리 추가
toml에 라이브러리명, 버전 추가하고 싱크
[versions]
//...
location = "21.3.0"
[libraries]
//...
play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "location" }
앱 단위 gradle에 이렇게 추가해주고 싱크~
dependencies {
// Google Location Service
implementation(libs.play.services.location)
}
2. 위치 가져오기 + 지역명 가져오기 코드
HomewViewModel은 Home 화면(맨 위에 있는 화면)에 있는 데이터들을 관리하는ViewModel 클래스이다.
여기서 관리하는 데이터가 날씨 데이터, 지역명 등이 있기에 위치와 지역명을 가져오는 코드도 이 클래스에 추가했다.
라이브러리를 사용해 위치와 주소를 가져올 때 Context가 필요한데, ViewModel 내에서 이 함수를 사용하고 있기 때문에 context를 ViewModel로 넣어줘야하는 상황이 생겼다. 하지만 이 경우 메모리 누수가 발생할 수 있어서 AndroidViewModel을 상속받아서 application을 받아와 application의 context를 사용하는 방법으로 구현했다.
주석 설명은 아래에
@HiltViewModel
class HomeViewModel @Inject constructor(
application: Application,
private val homeRepository: HomeRepositoryImpl,
) : AndroidViewModel(application) {
//...
private val _address = MutableStateFlow<String>("주소 가져오는 중...")
val address: StateFlow<String> = _address.asStateFlow()
private var baseDate: String
private var baseTime: String
// 1
init {
getLocation()
}
// 2
@SuppressLint("MissingPermission")
private fun getLocation() {
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(application.applicationContext)
fusedLocationProviderClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken = CancellationTokenSource().token
override fun isCancellationRequested(): Boolean = false
}).addOnSuccessListener { location ->
location?.let {
val place = CoordinateConverter().convertToXy(lat = it.latitude, lon = it.longitude)
getAddress(it.latitude, it.longitude)
}
}
.addOnFailureListener { exception ->
//...
}
}
// 3
private fun getAddress(lat: Double, lng: Double) = try {
val geocoder = Geocoder(application.applicationContext, Locale.KOREA)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
geocoder.getFromLocation(lat, lng, 1 ,object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: MutableList<Address>) {
_address.value = addresses[0].thoroughfare
}
override fun onError(errorMessage: String?) {
super.onError(errorMessage)
}
})
}
else {
val address = geocoder.getFromLocation(lat, lng, 1) as List<Address>
_address.value = address[0].thoroughfare
}
} catch (e: IOException) {
"주소 다시 불러오기"
}
}
// 1: 초기화 때 날짜 정보 생성
날씨 정보 api에 GET 요청을 할 때 날짜 정보와 위치 정보가 필요하다.
따라서 ViewModel이 처음 생성될 때 날짜 정보를 생성하도록 했다.
위치 정보는 미리 생성할 수 없어서... 미리 만들 수 있는 날짜 정보를 먼저 만들고, 이후에 위치 정보를 받아서(getLocation) 날짜+위치 정보를 날씨 정보 api(getHome)로 함께 보내고자 했다.
// 2: 위치 정보 가져오기
위치 권한에 동의한 사람만 접근할 수 있도록 코드를 짜야한다.
권한 체크 로직은 여기서가 아니라 다른 곳에서 구현했기 때문에 여기서는 MissingPermission 어노테이션으로 또 권한 체크를 하지 않도록 했다.(코드가 길어지기도 하고...)
lastLocation를 사용하려다가 정확한 날씨 위치를 위해서는 정확한 위치 정보가 필요한데, lastLocation은 배터리 사용량은 최소지만 사용자가 적극적으로 위치 정보를 사용하지 않은 경우 위치가 최신 정보가 아닐 수도 있어서 currentLocation을 사용했다.
배터리 사용량은 위보다는 더 쓰겠지만 나는 실시간 위치 추적이 아니라 한 번만 추적하는 거기 때문에 ㄱㅊ다고 생각...
암튼 위치 정보를 받으면 이 정보를 토대로 지역명도 받아오기 위해 getAddress함수에 위치 정보를 보냈다.
@SuppressLint("MissingPermission")
private fun getLocation() {
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(application.applicationContext)
fusedLocationProviderClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, object : CancellationToken() {
override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken = CancellationTokenSource().token
override fun isCancellationRequested(): Boolean = false
}).addOnSuccessListener { location ->
location?.let {
val place = CoordinateConverter().convertToXy(lat = it.latitude, lon = it.longitude)
getAddress(it.latitude, it.longitude)
}
}
.addOnFailureListener { exception ->
//...
}
}
// 3: 지역명 받아오기
geoCoder.getFromLocation이 deprecated되어서 알아보니까 버전에 따라 다르게 처리해주는 방식으로 해결하는 거 같아 아래와 같이 구현했다.
관련 링크: https://stackoverflow.com/questions/73456748/geocoder-getfromlocation-deprecated
private fun getAddress(lat: Double, lng: Double) = try {
val geocoder = Geocoder(application.applicationContext, Locale.KOREA)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
geocoder.getFromLocation(lat, lng, 1 ,object : Geocoder.GeocodeListener {
override fun onGeocode(addresses: MutableList<Address>) {
_address.value = addresses[0].thoroughfare
}
override fun onError(errorMessage: String?) {
super.onError(errorMessage)
}
})
}
else {
val address = geocoder.getFromLocation(lat, lng, 1) as List<Address>
_address.value = address[0].thoroughfare
}
} catch (e: IOException) {
"주소 다시 불러오기"
}
일케 하면 구현은 끝...!!!
근데 졸라 큰 문제가 있다.
위치 가져오는 건 ㄱㅊ은데 지역명 가져오는 데에 꽤 시간이 걸린다.
1~2초 정도?
꽤나 기다리기 지루해서... 기상청에서 제공해주는 엑셀을 앱에 넣어서 위치 정보 토대로 엑셀에서 지역명을 가져오는 방법도 구현해보려고 한다. 앱 괜히 무겁게 하고싶지 않아서 라이브러리 쓰고자 했던 건데... 이렇게 느릴 줄은 몰랐다...
엑셀 읽는 방법은 빨랐으면 좋겠다...!
참고
https://krrong.tistory.com/295
안드로이드 위치정보 가져오기 - FusedLocationProviderClient
💡 Intro 이전 안드로이드 위치정보 가져오기 글에서는 LocationManager에 대한 내용만 있다. 사실 LocationManager 보다는 더 정확한 위치 정보를 제공해주는 FusedLocationProviderClient를 사용하고, 이전 글에
krrong.tistory.com
https://tekken5953.tistory.com/17
[안드로이드] 내 GPS 위치정보 + 상세주소 불러오기 - Google's Location Service
안녕하세요. 이번 포스팅에서는 스마트폰에 내장된 GPS 센서가 측정한 데이터를 불러오고, 상세 데이터로 변환하는 예제를 진행 해보겠습니다. 특정 기술에 대한 이해가 목적이므로 추가적인 학
tekken5953.tistory.com