05
03

내 위치를 좌표로 알기위해 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://developers.google.com/android/reference/com/google/android/gms/location/LocationRequest.Builder?source=post_page-----e4f814138764--------------------------------

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

 

도움이 됐다면 댓글이나 공감 버튼 한 번씩 누르고 가주세요!

 

반응형
COMMENT