내 위치를 좌표로 알기위해 LocationRequest를 사용하려는데, deprecated 된 부분이 있었다. 이 부분을 개량하면서 최종적으로 정리한 코드를 천천히 기술해보겠다.
LocationRequest.create().apply {
priority = Priority.PRIORITY_HIGH_ACCURACY
interval = UPDATE_INTERVAL.toLong()
smallestDisplacement = 10.0f
fastestInterval = FASTEST_UPDATE_INTERVAL.toLong()
}
원래 위치정보를 받을 때, 바로 위 코드처럼 LocationRequest.create()를 사용했었다. 이 부분이 deprecated되면서 Builder로 다 바뀌었고 바뀐 코드를 정리하면 아래와 같다.
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, timeInterval).apply {
setMinUpdateDistanceMeters(10.0f)
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
setWaitForAccurateLocation(true)
}.build()
이렇게 만든 LocationRequest를 LocationClient에 넣어줘야한다. LocationClient로는 GPS, Network, Passive, Fused 이렇게 4가지가 있는데, Fused가 나머지 세 방법 중 최적의 값을 찾아주는 역할을 하기 때문에 FusedLocationClient를 사용했다.
LocationServices.getFusedLocationProviderClient(context)
LocationClient를 사용해 위치를 받아올때, 들어가는 인자로 LocationCallBack이 필요하다.
@NonNull
Task<Void> requestLocationUpdates(
@NonNull LocationRequest var1,
@NonNull LocationCallback var2,
@Nullable Looper var3
);
이 메서드를 이용해서 내 위치를 계속 업데이트 받는다. 세번째 인자로 null을 넣어주면 MainLooper가 default로 들어가기 때문에 신경써야 할 부분은 아까 생성한 request, 그리고 지금 만들어줄 LocationCallBack이다.
inner class LocationCallBack() : LocationCallback() {
override fun onLocationResult(location: LocationResult) {
super.onLocationResult(location)
val locationList = location.locations
if (locationList.size > 0) {
currentLocation = locationList[locationList.size - 1]
currentLocation?.let {
listener(it)
Log.d(
TAG,
"onLocationResult: lng: ${it.longitude} lat: ${it.latitude}"
)
}
}
}
override fun onLocationAvailability(availability: LocationAvailability) {
super.onLocationAvailability(availability)
}
}
응집도를 낮추기 위해 inner class를 사용하지않는게 좋지만, 위치서비스를 위한 클래스를 분리하면서 딱히 아이디어가 떠오르지않아서 inner로 선언했다.
onLocationResult의 location에서는, location값들이 list로 들어오는데, 가장 마지막 값을 꺼내면 그게 가장 최신값이라고 볼 수 있다. 여기서 사용한 listener는 익명함수로 전달받은 것으로 이따가 살펴보겠다. 이렇게 만든 콜백 클래스를 인스턴스로 만들어서 아까 메서드의 두번째 인자로 넣어주면 된다.
이 과정에서 PermissionCheck가 필수인데, 나는 TedPermission을 사용했다.
https://github.com/ParkSangGwon/TedPermission
GitHub - ParkSangGwon/TedPermission: Easy check permission library for Android Marshmallow
Easy check permission library for Android Marshmallow - ParkSangGwon/TedPermission
github.com
이제 이걸 다 담은 클래스의 인스턴스를 만들건데, 매번 만들이유가 없으니 싱글톤으로 만들어줄 것이다. 이 인스턴스 생성 코드는 Room Codelab에서 얻었다.
companion object {
private var instance: LocationHelper? = null
fun getInstance(context: Context): LocationHelper {
if (instance == null) {
synchronized(LocationHelper::class.java) {
if (instance == null) {
instance = LocationHelper(context)
Log.d(TAG, "getInstance: create with context")
}
}
}
return instance!!
}
fun getInstance(): LocationHelper {
return instance!!
}
}
ApplicationClass단위처럼 전체 생명주기를 아우르는 곳에서 instance를 생성해주면 그 다음에 getInstance를 호출할때는 이미 만들어진 걸 가져오게 된다.
이 클래스를 어떤 레이어에 넣어야하나 고민해봤는데, 코드 중에 MainLooper에 접근하는 코드가 있기때문에 ui 활동에 관여한다고 판단해서 presentaion(UI) 레이어에 배치하기로 했다.
이제 전체 코드를 보자.
class LocationHelper private constructor(context: Context) {
private var timeInterval: Long = FASTEST_UPDATE_INTERVAL.toLong() // 500으로 설정했다
private var request: LocationRequest
private var locationClient: FusedLocationProviderClient
var currentLocation: Location? = null
lateinit var listener: (Location) -> Unit
init {
locationClient = LocationServices.getFusedLocationProviderClient(context)
request = createRequest()
}
fun updateTimeInterval(timeInterval: Long) {
// 배터리를 조금이라도 효율적으로 쓰기위해 만든 메서드
// 일단 내 위치를 잡고 난 뒤에는 갱신시간을 조절한다
// 갱신 후 startLocationTracking을 다시 실행해줘야한다
this.timeInterval = timeInterval
request = createRequest()
}
fun getClient():FusedLocationProviderClient{
return locationClient
}
private fun createRequest(): LocationRequest =
LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, timeInterval).apply {
setMinUpdateDistanceMeters(10.0f)
setGranularity(Granularity.GRANULARITY_PERMISSION_LEVEL)
setWaitForAccurateLocation(true)
}.build()
fun startLocationTracking() {
// 이걸 실행하기 전에 LOCATION관련 Permission 확인을 해야된다.
TedPermission.create()
.setPermissionListener(object : PermissionListener {
@SuppressLint("MissingPermission")
override fun onPermissionGranted() {
locationClient.requestLocationUpdates(
request,
this@LocationHelper.LocationCallBack(),
null
)
}
override fun onPermissionDenied(deniedPermissions: MutableList<String>?) {
}
})
.setDeniedMessage("위치 권한을 허용해주세요")
.setPermissions(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
)
.check();
}
fun stopLocationTracking() {
locationClient.flushLocations()
locationClient.removeLocationUpdates(this.LocationCallBack())
}
inner class LocationCallBack() : LocationCallback() {
override fun onLocationResult(location: LocationResult) {
super.onLocationResult(location)
val locationList = location.locations
if (locationList.size > 0) {
currentLocation = locationList.last()
currentLocation?.let {
listener(it)
}
}
}
override fun onLocationAvailability(availability: LocationAvailability) {
super.onLocationAvailability(availability)
}
}
companion object {
private var instance: LocationHelper? = null
fun getInstance(context: Context): LocationHelper {
if (instance == null) {
synchronized(LocationHelper::class.java) {
if (instance == null) {
instance = LocationHelper(context)
}
}
}
return instance!!
}
fun getInstance(): LocationHelper {
return instance!!
}
}
}
listener는 새 위치를 받아올 때 마다 값을 관측하기 위해 익명함수를 설정하려고 넣어둔 프로퍼티다.
locationHelperInstance.listener = {
viewModel.setUserLocation(LatLng(it))
}
viewModel에 만들어둔 메서드를 람다로 넣어줬는데
private val _userLocation = MutableStateFlow<LatLng?>(null)
val userLocation get() = _userLocation.asStateFlow()
fun setUserLocation(currentLocation: LatLng?) {
viewModelScope.launch(Dispatchers.IO) {
_userLocation.emit(currentLocation)
}
}
StateFlow를 사용하여 이 메서드를 호출하면 emit시키고, 화면에 보여주는 곳에서 collect해서 데이터를 받았다.
하는 김에 collect하는 곳까지 코드를 올려두겠다.
lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userLocation.collectLatest { currentLocation ->
currentLocation?.let {
if (::naverMap.isInitialized) {
// 네이버 맵에 파란색 점으로 내 위치 보여주는 코드. default가 false라 true로
// 바꿔줘야 된다.
naverMap.locationOverlay.run {
isVisible = true
position = LatLng(currentLocation.latitude, currentLocation.longitude)
}
}
}
}
}
}
repeatOnLifecycle도 launchWhenStarted가 deprecated되면서 사용하게된 메서드다.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED)
이렇게 정의하면 lifecycle이 STARED일때 -> 백그라운드에 넘어가지 않았을 때 collect하고 있게되고, 백그라운드에 들어가 있으면 emit되더라도 collect하지않는다.
:: 참고한 글::
https://tomas-repcik.medium.com/locationrequest-create-got-deprecated-how-to-fix-it-e4f814138764
LocationRequest.create() got deprecated. How to fix it?
How to properly ask for location in location services 21.0.0
tomas-repcik.medium.com
도움이 됐다면 댓글이나 공감 버튼 한 번씩 누르고 가주세요!
'Android 🖥️ > 삽질⛏️' 카테고리의 다른 글
서버없이 FCM 보내기 - CloudFunction + FireStore + FCM(2) (0) | 2024.05.19 |
---|---|
구글 로그인 CredentialManager 마이그레이션 + Firebase Auth (4) | 2024.05.11 |
Firebase CloudFunction 삽질 + FCM(1) (0) | 2024.04.21 |
Firebase Cloud Messaging(FCM) 삽질하기 (0) | 2024.04.16 |
LiveData 알아보기 (1) | 2024.04.10 |