간단한 안드로이드 앱 만들기 (날씨 앱)

5. 위치 정보 연동하기

리저브콜드브루 2025. 2. 6. 13:21
728x90
반응형

앱의 메인홈화면에 사용될 현 위치 정보를 가져오고 표시하도록 작업을 진행하려고 한다.

아래와 같이 단계별로 진행할 예정

  • 위치 정보 연동을 위한 권한 설정
  • FusedLocationProvider를 이용한 위치 정보 가져오기, build.gradle 의존성 추가
  • 코드 수정

 

1. 권한 설정 (AndroidManifest.xml)

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> // 정밀 위치 정보 (GPS)
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> // 대략적인 위치 정보 (네트워크 기반)

권한을 AndroidManifest.xml에 추가

 

 

2. 위치 정보 가져오기 (FusedLocationProvider 사용)

의존성 추가 (build.gradle)

dependencies {
    implementation 'com.google.android.gms:play-services-location:21.0.1'
}

의존성 추가 후 Sync Now 클릭

 

 

3. 위치 정보 업데이트 추가 (LocationManager.kt)

  • 위치 권한을 요청하고 위치 정보를 가져와 업데이트를 시켜주는 코드
  • LocationManager는 MainAcivity와의 UI 업데이트 및 권한 요청을 상호작용한다.
  • activity 정보와 textView 정보를 받아 초기화할 수 있도록 해준다.
  • 위치 정보 관련 함수를 이곳에 작성하려고 한다.
class LocationManager(private val activity: AppCompatActivity, private val textView: TextView) {
}

 

 

4. 위치 권한 확인 및 요청 함수 (checkLocationPermission(), requestLocationPermission())

  • 위치 권한을 확인 및 권한이 없으면 요청하는 함수 생성
  • 각각 ActivityCompat.checkSelfPermission, ActivityCompat.requestPermissions 사용
private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(activity) //FusedLocationProviderClient: 위치 서비스 초기화

    //위치 권한 확인
    fun checkLocationPermission(): Boolean {
        return ActivityCompat.checkSelfPermission(
            activity,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    }

    //위치 권한 요청
    fun requestLocationPermission(requestCode: Int) {
        ActivityCompat.requestPermissions(
            activity,
            arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
            requestCode
        )
    }

 

 

5. 위치 정보 가져오기 (getCurrentLocation())

다음으로 위치 정보를 가져오는 함수를 만든다.

// 현재 위치 가져오기
    fun getCurrentLocation() {
        if (checkLocationPermission()) {
            val locationRequest = LocationRequest.Builder(0L) // 즉시 한 번만 업데이트
                .setMaxUpdates(1) // 위치를 한 번만 가져옴
                .build()

            val locationCallback = object : LocationCallback() {
                @RequiresApi(Build.VERSION_CODES.TIRAMISU)
                override fun onLocationResult(locationResult: LocationResult) {
                    val location = locationResult.lastLocation
                    if (location != null) {
                        //위치 정보 UI 업데이트
                        updateLocationUI(location.latitude, location.longitude)
                    } else {
                        textView.text = "위치 정보를 가져올 수 없습니다."
                        Log.e("Location", "새로 요청한 위치도 null입니다.")
                    }
                }
            }

            fusedLocationClient.requestLocationUpdates(
                locationRequest,
                locationCallback,
                Looper.getMainLooper()
            )
        }
    }
  • 주기적으로 업데이트할 필요가 없으므로 즉시 한 번만 요청하도록 한다.
  • locationResult.lastLocation을 통해 최신 위치를 가져옴
  • 설정한 요청정보를 가지고 fusedLocationClient.requestLocationUpdates()를 통해 호출한다.
  • 콜백 시 위치 정보 UI를 업데이트 하도록 한다.

 + LocationRequest 학습

 

LocationRequest란?

LocationRequestAndroid에서 위치 정보를 요청할 때 사용하는 설정 객체얼마나 자주, 어떤 방식으로 요청할지 설정할 수 있다.이전 방식 (Android 12 (API 31) 이하)현재의 LocationRequest.Builder()가 아닌 LocationRe

joyshu93.tistory.com

 

+ FusedLocationProviderClient 학습

 

FusedLocationProviderClient란?

Google Play 서비스의 위치 API를 사용해 사용자의 현재 위치를 가져올 수 있도록 도와주는 위치 서비스 객체GPS, Wi-Fi, 셀룰러 네트워크 등 다양한 수단을 조합해서 가장 효율적인 방식으로 사용자의

joyshu93.tistory.com

 

 

6. 위치 정보 UI 업데이트 함수 (updateLocationUI())

//위치 정보 UI 업데이트
    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
    private fun updateLocationUI(latitude: Double, longitude: Double) {
        Log.d("Location", "위도: $latitude, 경도: $longitude") // 디버그용 로그

        val geocoder = Geocoder(activity, Locale.getDefault())
        val geocoderCallback = object : Geocoder.GeocodeListener {
            override fun onGeocode(addresses: MutableList<android.location.Address>) {
                activity.runOnUiThread { // UI 업데이트를 메인 스레드에서 수행, 비동기 스레드에서 UI 업데이트를 시도하면 에러발생
                    if (addresses.isNotEmpty()) {
                        val address = addresses[0]

                        // 도시(locality) 정보가 null이면 광역시/도(adminArea)로 대체
                        val city = address.locality ?: address.adminArea ?: "위치 불명"

                        textView.text = city // 도시 이름 표시
                        Log.d("Location", "도시: $city")
                    } else {
                        textView.text = "위치 확인 불가"
                        Log.e("Location", "Geocoder가 주소를 찾지 못했습니다.")
                    }
                }
            }

            override fun onError(errorMessage: String?) {
                activity.runOnUiThread { // 오류 발생 시에도 메인 스레드에서 UI 업데이트
                    textView.text = "위치 정보 오류"
                    Log.e("Location", "Geocoder 오류: $errorMessage")
                }
            }
        }

        // 비동기 방식으로 Geocoder 사용
        geocoder.getFromLocation(latitude, longitude, 1, geocoderCallback)
    }
  • UI 업데이트는 메인 스레드에서 수행하지만 위치 정보를 가져오는 것은 비동기 스레드에서 작업한다.
  • UI 업데이트를 위해서는 runOnUiThread를 이용해 메인 스레드에서 작업할 수 있도록 처리가 필요하다.

+ Geocoder 학습

 

Geocoder란?

Geocoder란?Geocoder는 위도(latitude), 경도(longitude) 정보를 주소(지명, 도로명, 행정구역 등)로 변환해 주는 안드로이드 내장 클래스반대로 주소를 위조/경도로 변환(Geocoding)도 가능 기본 개념Geocoder는

joyshu93.tistory.com

 

 

7. SDK 설정 및 API 33 이상 지원하는 에뮬레이터로 교체

위치 정보를 가져오는 작업을 최신 권장 사양으로 작업하다 보니 API 33 이상을 요구하게 되었다. 

원활한 위치 정보를 받아오기 위해 코드에 맞춰 Android 13 Tiramisu SDK를 설치했다.
에뮬레이터도 API 33 이상 지원하는 기기로 만들었다.

SDK 설정
API 33 을 지원하는 에뮬레이터


8. 위치 정보 요청 호출 (MainActivity.kt)

작성된 LocationManager.kt 클래스를 사용해 MainActivity에서 동작하도록 코드를 추가해야한다.

// lateinit: 나중에 초기화 할 변수
private lateinit var locationManager: LocationManager //LocationManager.kt
private lateinit var textViewLocation: TextView // 위치를 표시할 텍스트뷰
  • locationManager, textViewLocation 타입에 맞에 선언한다.

 

// companion object: 클래스의 정적 멤버 선언
// 상수, 유틸리티 함수, 팩토리 메서드 등에 유용
companion object {
    private const val LOCATION_PERMISSION_REQUEST_CODE = 1000 //위치 권한 요청 코드
}
  • 위치 권한을 요청할 때 사용할 값을 미리 선언한다.

 

 override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    textViewLocation = findViewById(R.id.textView_location) //텍스트뷰 초기화
    locationManager = LocationManager(this, textViewLocation)

    if (locationManager.checkLocationPermission()) {
        locationManager.getCurrentLocation() // 권한이 있을 경우 현재 위치 가져오기
    } else {
        locationManager.requestLocationPermission(LOCATION_PERMISSION_REQUEST_CODE) //위치 권한 요청
    }
}
  • onCreate()에 Activity 생성 시 해야할 작업을 작성한다.
  • setContentView()를 이용해 main 레이아웃을 표시한다.
  • 선언해놓았던 textViewLocation, locationManager를 초기화 해준다.
  • 위치 권한이 있는지 확인 후 없다면 위치 권한을 요청하도록 함수를 호출하고 있다면 현재 위치를 가져오도록하는 함수를 호출한다.

 

// requestLocationPermission 콜백
// 권한 요청 결과 처리
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<out String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == LOCATION_PERMISSION_REQUEST_CODE &&
        grantResults.isNotEmpty() &&
        grantResults[0] == PackageManager.PERMISSION_GRANTED
    ) {
        locationManager.getCurrentLocation() //권한이 허용된 경우 위치 가져오기
    } else {
        textViewLocation.text = "위치 권한이 필요합니다."
    }
}
  • 위치 권한 요청 후 받은 콜백을 처리한다. 

 

locationManager.getCurrentLocation() 호출 2가지

  1. 앱이 실행될 때 궈한이 이미 있는 경우 > onCreate()에서 바로 호출
  2. 앱이 실행될 때 권한이 없는 경우 > 권한 요청 후, 허용 콜백되었을 때

결과 확인

결과물 확인을 위해 textView_location의 기본값을 'suwon'으로 수정해주었다.

위치 정보를 잘 가져왔다면 ' suwon'이 아닌 다른 지역명이 뜰 것이다.

실행 모습

city값을 필터링 해주었기 때문에 나의 현재 위치인 Seoul로 뜨는 모습을 확인할 수 있다.


에뮬레이터 위치 정보 관련 이슈
진행 중 에뮬레이터 위치 정보가 초기화 되어있지 않음을 확인하여 수정했다.

 

안드로이드 에뮬레이터 위치 정보 초기화 이슈

에뮬레이터 위치 정보 관련 이슈진행 중 에뮬레이터 위치 정보가 초기화 되어있지 않음을 확인하여 수정했다. 내용위치 연동 서비스를 확인하기 위해 에뮬레이터를 실행했지만 현 위치 정보가

joyshu93.tistory.com

 

728x90
반응형