Naver Map을 사용하여 현 위치로 이동하는 Compose 코드를 작성하겠습니다.
먼저 아래 소스를 작성합니다.
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.gms.common.api.GoogleApiClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
import com.naver.maps.map.LocationSource
import com.naver.maps.map.compose.ExperimentalNaverMapApi
@ExperimentalNaverMapApi
@OptIn(ExperimentalPermissionsApi::class)
@Composable
public fun rememberFusedLocationSource(): LocationSource {
val permissionsState = rememberMultiplePermissionsState(
listOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
)
)
val context = LocalContext.current
val locationSource = remember {
object : FusedLocationSource(context) {
override fun hasPermissions(): Boolean {
return permissionsState.allPermissionsGranted
}
override fun onPermissionRequest() {
permissionsState.launchMultiplePermissionRequest()
}
}
}
val allGranted = permissionsState.allPermissionsGranted
LaunchedEffect(allGranted) {
if (allGranted) {
locationSource.onPermissionGranted()
}
}
return locationSource
}
private abstract class FusedLocationSource(context: Context) : LocationSource {
private val callback = object : FusedLocationCallback(context.applicationContext) {
override fun onLocationChanged(location: Location?) {
lastLocation = location
}
}
private var listener: LocationSource.OnLocationChangedListener? = null
private var isListening: Boolean = false
private var lastLocation: Location? = null
set(value) {
field = value
if (listener != null && value != null) {
listener?.onLocationChanged(value)
}
}
abstract fun hasPermissions(): Boolean
abstract fun onPermissionRequest()
fun onPermissionGranted() {
setListening(true)
}
override fun activate(listener: LocationSource.OnLocationChangedListener) {
this.listener = listener
if (isListening.not()) {
if (hasPermissions()) {
setListening(true)
} else {
onPermissionRequest()
}
}
}
override fun deactivate() {
if (isListening) {
setListening(false)
}
this.listener = null
}
private fun setListening(listening: Boolean) {
if (listening) {
callback.startListening()
} else {
callback.stopListening()
}
isListening = listening
}
private abstract class FusedLocationCallback(private val context: Context) {
private val locationCallback: LocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
onLocationChanged(locationResult.lastLocation)
}
}
fun startListening() {
GoogleApiClient.Builder(context)
.addConnectionCallbacks(object : GoogleApiClient.ConnectionCallbacks {
@SuppressLint("MissingPermission")
override fun onConnected(bundle: Bundle?) {
val request = LocationRequest()
request.priority = 100
request.interval = 1000L
request.fastestInterval = 1000L
LocationServices.getFusedLocationProviderClient(context)
.requestLocationUpdates(request, locationCallback, null)
}
override fun onConnectionSuspended(i: Int) {}
})
.addApi(LocationServices.API)
.build()
.connect()
}
fun stopListening() {
LocationServices
.getFusedLocationProviderClient(context)
.removeLocationUpdates(locationCallback)
}
abstract fun onLocationChanged(location: Location?)
}
}
네이버가 제공하는 FusedLocationSource 코드를 사용했습니다.
* 먼저 rememberMultiplePermissionsState()로 위치 권한을 받습니다.
* FusedLocationSource 콜백을 호출합니다.
* activate() / deactivate() (위치추적기능 활성화 / 비활성화)가 호출되었을 때 FusedLocationSourceCallback의 onLocationChanged() 콜백 메서드를 호출하여 위치 정보를 갱신합니다.
FusedLocationSourceCallback의 코드 중 GoogleApiClient 빌더가 deprecated 되었습니다. 이는 추후에 새로운 코드로 작성해 보겠습니다.
다음으로 Naver Map을 호출합니다.
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.common.location.rememberFusedLocationSource
import com.naver.maps.map.compose.*
@ExperimentalNaverMapApi
@Composable
fun MainScreen(modifier: Modifier = Modifier) {
Box(
modifier = modifier
) {
val cameraPositionState = rememberCameraPositionState()
NaverMap(
modifier = Modifier.fillMaxSize(),
cameraPositionState = cameraPositionState,
locationSource = rememberFusedLocationSource(),
properties = MapProperties(
locationTrackingMode = LocationTrackingMode.Follow
),
uiSettings = MapUiSettings(
isLocationButtonEnabled = true,
),
) {
}
}
}
* 먼저 cametaPositionState를 remember 합니다. 이는 현재 카메라 position을 기억하고, recomposition 되면 저장한 position을 restore 하는 구문입니다. 아래는 해당 라이브러리의 rememberCameraPositionState 코드 중 일부입니다.
@Composable
public inline fun rememberCameraPositionState(
key: String? = null,
crossinline init: CameraPositionState.() -> Unit = {},
): CameraPositionState = rememberSaveable(key = key, saver = CameraPositionState.Saver) {
CameraPositionState().apply(init)
}
/* 중략 */
public val Saver: Saver<CameraPositionState, CameraPosition> = Saver(
save = { it.position },
restore = { CameraPositionState(it) }
)
* 다음으로 locationSource 파라미터에 아까 작성한 rememberFusedLocationSource() 메서드를 넣어줍니다.
* properties 파라미터에 locationTrackingMode를 LocationTrackingMode.Follow로 설정해서 Naver Map이 로드될 때 현재 위치로 바로 이동할 수 있도록 지정합니다.
* uiSettings 파라미터로 locationButton을 활성화합니다.
다음과 같이 현재 위치가 정상적으로 출력됩니다. (현위치는 에뮬레이터 임의로 조정했습니다.)
감사합니다.
'Android > Android' 카테고리의 다른 글
Type com.example.domain.BuildConfig is defined multiple times (0) | 2023.06.14 |
---|---|
[Compose] Navigation으로 URL 넘길 때 주의사항 (0) | 2023.05.16 |
[Android] Compose로 Naver Map 띄우기 (0) | 2023.04.05 |
[Android] Clean Architecture 모듈화 해보기 - 2 (0) | 2023.03.22 |
[Android] Clean Architecture 모듈화 해보기 - 1 (0) | 2023.03.22 |