안드로이드 학습(Compose)/Compose로 날씨 앱 재구성하기
현 위치 표시하기
리저브콜드브루
2025. 3. 10. 16:47
728x90
반응형
5. 위치 정보 연동하기
앱의 메인홈화면에 사용될 현 위치 정보를 가져오고 표시하도록 작업을 진행하려고 한다.아래와 같이 단계별로 진행할 예정위치 정보 연동을 위한 권한 설정FusedLocationProvider를 이용한 위치 정
joyshu93.tistory.com
이전 프로젝트에서는 LocationProvider와, LocationDataStore 클래스를 만들어 사용했었다
Compose 프로젝트에 사용할 수 있도록 수정한다
LocationProvider.kt
package com.example.composeweatherapp
import ...
/***
* Activity 의존성 제거
* 권한 요청 방식 변경
*/
class LocationProvider(private val context: Context, private val locationDataStore: LocationDataStore) {
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
// MutableStateFlow<Location?>로 변경하여 Compose UI와 연동 가능
private val _locationState = MutableStateFlow<Location?>(null)
val locationState: StateFlow<Location?> = _locationState
private var locationCallback: LocationCallback? = null
// 위치를 가져오는 함수 (콜백을 통해 결과를 전달)
fun fetchLocation() {
if (ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 0L)
.setMaxUpdates(1)
.build()
// 기존 콜백이 있으면 제거 후 새로 등록
locationCallback?.let {
fusedLocationClient.removeLocationUpdates(it)
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val location = locationResult.lastLocation
if (location != null) {
_locationState.value = location // UI에서 즉시 반영되도록 상태 업데이트
// 위치 정보를 DataStore에 저장
CoroutineScope(Dispatchers.IO).launch {
locationDataStore.saveCurLatitude(location.latitude)
locationDataStore.saveCurLongitude(location.longitude)
locationDataStore.updateLocationKey(location.latitude, location.longitude)
}
}else {
Log.e("LocationProvider", "새로 요청한 위치도 null입니다.")
}
// 요청이 완료되었으므로 콜백 제거
locationCallback?.let {
fusedLocationClient.removeLocationUpdates(it)
}
}
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback!!,
Looper.getMainLooper()
)
}
else {
Log.e("LocationProvider", "위치 권한이 없습니다.")
}
}
}
// Composition에서 LocationProvider를 유지
@Composable
fun rememberLocationProvider(locationDataStore: LocationDataStore): LocationProvider {
val context = LocalContext.current
return remember { LocationProvider(context, locationDataStore) }
}
@Composable
fun LocationPermissionHandler(locationProvider: LocationProvider) {
val context = LocalContext.current
val permissionGranted = remember {
mutableStateOf(
ContextCompat.checkSelfPermission(
context, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
)
}
// 권한 요청을 rememberLauncherForActivityResult()로 처리하여 Compose 환경에 맞춤
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
) { isGranted ->
permissionGranted.value = isGranted
if (isGranted) {
locationProvider.fetchLocation()
}
}
// 권한 변경 감지를 LaunchedEffect(permissionGranted.value)로 최적화
// LaunchedEffect(Unit)을 활용해 앱 실행 시 자동 권한 요청
// Unit 대신 permissionGranted.value를 감지하도록 변경
// 권한이 이미 허용된 경우 불필요한 실행을 방지
LaunchedEffect(permissionGranted.value) {
if (!permissionGranted.value) {
permissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
}
}
MainScreen.kt
@Composable
fun MainScreen() {
val context = LocalContext.current
val locationDataStore = remember { LocationDataStore(context) }
val locationProvider = rememberLocationProvider(locationDataStore)
// 위치 권한 요청 및 위치 업데이트
LocationPermissionHandler(locationProvider)
// 앱 실행 시마다 최신 위치 요청
LaunchedEffect(Unit) { // Unit을 Key로 사용하면 앱이 실행될 때마다 fetchLocation()이 호출
locationProvider.fetchLocation()
}
// 위치 데이터를 State로 관리
var cityName by remember { mutableStateOf("위치 불명") }
val coroutineScope = rememberCoroutineScope()
val location = locationProvider.locationState.collectAsState().value
// 위치가 변경될 때마다 지역명을 업데이트
LaunchedEffect(location) {
location?.let {
coroutineScope.launch {
val geocoder = Geocoder(context, Locale.getDefault())
val addresses = geocoder.getFromLocation(it.latitude, it.longitude, 1)
if (addresses != null) {
if (addresses.isNotEmpty()) {
cityName = addresses[0].locality ?: addresses[0].adminArea ?: "위치 불명"
} else {
Log.e("MainScreen", "Geocoder가 주소를 찾지 못했습니다.")
}
}
}
}
}
TopWeatherInfo(cityName)
}
@Composable
fun TopWeatherInfo(cityName: String)
{
Column(modifier = Modifier.fillMaxSize().padding(64.dp)) {
Text(
text = cityName
)
TopWeatherInfoSection(weatherIcon = R.drawable.sunny_128, temperature = "10")
}
}
결과
- 앱 실행 시 최신 위치 요청 (LaunchedEffect(Unit)
- 위치가 변경될 때마다 Geocoder를 사용하여 지역명 업데이트 (LauncedEffect(location))
- Compose의 StateFlow와 collectAsState()를 사용하여 UI 자동 반영
- 권한 요청도 rememberLauncherForActivityResult()로 처리하여 더 간결하고 안전한 코드 구성
728x90
반응형