본문 바로가기
안드로이드 코틀린

[Kotlin][Android] GPS Permission 현재 위치 값 불러오기 기능

by teamnova 2021. 5. 7.

저번 시간에 Java를 통해 사용자의 위치를 TextView에 표시하는 기능을 구현했었습니다.

이번 포스팅에서는 앞선 기능을 Kotlin으로 구현해 보도록 하겠습니다.

 

개발 순서

1. manifest.xml 권한 추가

2. 화면 그리기 (activity_g_p_s_permission.xml)

3. 의존성 추가

4. 코드 작성(GPSPermissionActivity.kt)

5. 결과물

개발  

1. manifest.xml 권한 추가

사용자의 위치를 받아 오기 위해서는 매니페스트에 권한을 추가해줘야 합니다.

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

developer.android.com/reference/android/Manifest.permission

 

Manifest.permission  |  Android 개발자  |  Android Developers

 

developer.android.com

안드로이 개발자 페이지에서 메니페스트 설명을 보면 아래와 같이 적혀있습니다. protection level 이 위험(dangerous) 단계이므로 사용자에게 권한을 요청해야 합니다. 두 권한 중 하나만 manifest에 작성하셔도 무방합니다.

 

2. 화면 그리기 (activity_g_p_s_permission.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".GPSPermissionActivity">

    <Button
        android:id="@+id/btn_start_upds"
        android:layout_width="match_parent"
        android:layout_marginRight="12dp"
        android:layout_marginLeft="12dp"
        android:layout_marginTop="24dp"
        android:layout_height="wrap_content"
        android:text="위치 정보 업데이트 시작" />
    <Button
        android:enabled="false"
        android:id="@+id/btn_stop_upds"
        android:layout_width="match_parent"
        android:layout_marginRight="12dp"
        android:layout_marginLeft="12dp"
        android:layout_height="wrap_content"
        android:text="위치 정보 업데이트 종료" />


    <TextView
        android:padding="12dp"
        android:layout_marginTop="12dp"
        android:textSize="18sp"
        android:layout_marginRight="12dp"
        android:layout_marginLeft="12dp"
        android:layout_width="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_height="wrap_content"
        android:text="위도"
        android:id="@+id/txtLat"
        />
    <TextView
        android:padding="12dp"
        android:layout_gravity="center_horizontal"
        android:textSize="18sp"
        android:layout_marginRight="12dp"
        android:layout_marginLeft="12dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="경도"
        android:id="@+id/txtLong"
        />
    <TextView
        android:layout_gravity="center_horizontal"
        android:padding="12dp"
        android:textSize="18sp"
        android:layout_marginRight="12dp"
        android:layout_marginLeft="12dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="업데이트 시간"
        android:id="@+id/txtTime" />

</LinearLayout>

 

위처럼 레이아웃을 만들었습니다. '위치 정보 업데이트 시작' 버튼을 누르게 되면 일정하게 위치 정보를 받아와 각 위도, 경도, 업데이트 시간 Text 란에 업데이트를 시켜주고 '위치 정보 업데이트 종료' 버튼을 누르게 되면 업데이트를 종료하도록 구현할 예정입니다.

 

3. 의존성 추가

우선 Location API 사용을 위하여 app.gradle(app)에 의존성을 추가해주었습니다.

 

4. 코드 작성 (GPSPermissionActivity.kt)

위치 값을 가져오기 위해서는 LocationRequest를 이용합니다. 

developer.android.com/training/location/request-updates?hl=ko

 

위치 업데이트 요청  |  Android 개발자  |  Android Developers

위치 정보를 적절하게 사용하면 앱 사용자에게 도움이 될 수 있습니다. 예를 들어 사용자가 걷거나 운전하는 동안 길을 찾아주는 앱 또는 애셋의 위치를 추적하는 앱은 일정한 간격으로 기기의

developer.android.com

기존에 LocationRequest를 생성할 때 생성자 메서드(LocationRequest())를 사용하였는데요. 이 메서드가 deprecated 됨으로써 아래와 같이 메서드를 이용해서 객체를 생성해줍시다.

 

위치 정보를 받아 오는 코드를 저는 스틱코드를 이용해서 쉽게 구현하였습니다. 크게 변수, 권한, 제어 하는 코드로 불러왔습니다.

stickode.com/detail.html?no=2102

 

스틱코드

 

stickode.com

스틱코드를 이용하여 변수 불러오기
스틱코드로 불린 변수
스틱코드를 이용하여 권한 코드 불러오기
스틱코드를 통해 불러온 권한 코드
스틱코드를 통해 불러온 제어 코드
스틱코드를 통해 불러온 제어 코드 결과

class GPSPermissionActivity : AppCompatActivity() {

    val TAG: String = "로그"

    private var mFusedLocationProviderClient: FusedLocationProviderClient? = null // 현재 위치를 가져오기 위한 변수
    lateinit var mLastLocation: Location // 위치 값을 가지고 있는 객체
    internal lateinit var mLocationRequest: LocationRequest // 위치 정보 요청의 매개변수를 저장하는
    private val REQUEST_PERMISSION_LOCATION = 10

    lateinit var btnStartupdate: Button
    lateinit var btnStopUpdates: Button
    lateinit var txtLat: TextView
    lateinit var txtLong: TextView
    lateinit var txtTime: TextView

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

        // 화면뷰 inflate.
        btnStartupdate = findViewById(R.id.btn_start_upds)
        btnStopUpdates = findViewById(R.id.btn_stop_upds)
        txtLat = findViewById(R.id.txtLat)
        txtLong = findViewById(R.id.txtLong)
        txtTime = findViewById(R.id.txtTime)

        // LocationRequest() deprecated 되서 아래 방식으로 LocationRequest 객체 생성
        // mLocationRequest = LocationRequest() is deprecated
        mLocationRequest =  LocationRequest.create().apply {
            interval = 2000 // 업데이트 간격 단위(밀리초)
            fastestInterval = 1000 // 가장 빠른 업데이트 간격 단위(밀리초)
            priority = LocationRequest.PRIORITY_HIGH_ACCURACY // 정확성
            maxWaitTime= 2000 // 위치 갱신 요청 최대 대기 시간 (밀리초)
        }

        // 위치 추척 시작 버튼 클릭시 처리
        btnStartupdate.setOnClickListener {
            if (checkPermissionForLocation(this)) {
                startLocationUpdates()
                // View Button 활성화 상태 변경
                btnStartupdate.isEnabled = false
                btnStopUpdates.isEnabled = true
            }
        }

        // 위치 추적 중지 버튼 클릭시 처리
        btnStopUpdates.setOnClickListener {
            stoplocationUpdates()
            txtTime.text = "Updates Stoped"
            // View Button 활성화 상태 변경
            btnStartupdate.isEnabled = true
            btnStopUpdates.isEnabled = false
        }

    }

    protected fun startLocationUpdates() {
        Log.d(TAG, "startLocationUpdates()")

        //FusedLocationProviderClient의 인스턴스를 생성.
        mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
            && ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            Log.d(TAG, "startLocationUpdates() 두 위치 권한중 하나라도 없는 경우 ")
            return
        }
        Log.d(TAG, "startLocationUpdates() 위치 권한이 하나라도 존재하는 경우")
        // 기기의 위치에 관한 정기 업데이트를 요청하는 메서드 실행
        // 지정한 루퍼 스레드(Looper.myLooper())에서 콜백(mLocationCallback)으로 위치 업데이트를 요청합니다.
        mFusedLocationProviderClient!!.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper())
    }

    // 시스템으로 부터 위치 정보를 콜백으로 받음
    private val mLocationCallback = object : LocationCallback() {
        override fun onLocationResult(locationResult: LocationResult) {
            Log.d(TAG, "onLocationResult()")
            // 시스템에서 받은 location 정보를 onLocationChanged()에 전달
            locationResult.lastLocation
            onLocationChanged(locationResult.lastLocation)
        }
    }

    // 시스템으로 부터 받은 위치정보를 화면에 갱신해주는 메소드
    fun onLocationChanged(location: Location) {
        Log.d(TAG, "onLocationChanged()")
        mLastLocation = location
        val date: Date = Calendar.getInstance().time
        val simpleDateFormat = SimpleDateFormat("hh:mm:ss a")
        txtTime.text = "Updated at : " + simpleDateFormat.format(date) // 갱신된 날짜
        txtLat.text = "LATITUDE : " + mLastLocation.latitude // 갱신 된 위도
        txtLong.text = "LONGITUDE : " + mLastLocation.longitude // 갱신 된 경도
    }

    // 위치 업데이터를 제거 하는 메서드
    private fun stoplocationUpdates() {
        Log.d(TAG, "stoplocationUpdates()")
        // 지정된 위치 결과 리스너에 대한 모든 위치 업데이트를 제거
        mFusedLocationProviderClient!!.removeLocationUpdates(mLocationCallback)
    }

    // 위치 권한이 있는지 확인하는 메서드
    fun checkPermissionForLocation(context: Context): Boolean {
        Log.d(TAG, "checkPermissionForLocation()")
        // Android 6.0 Marshmallow 이상에서는 지리 확보(위치) 권한에 추가 런타임 권한이 필요합니다.
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "checkPermissionForLocation() 권한 상태 : O")
                true
            } else {
                // 권한이 없으므로 권한 요청 알림 보내기
                Log.d(TAG, "checkPermissionForLocation() 권한 상태 : X")
                ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), REQUEST_PERMISSION_LOCATION)
                false
            }
        } else {
            true
        }
    }

    // 사용자에게 권한 요청 후 결과에 대한 처리 로직
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        Log.d(TAG, "onRequestPermissionsResult()")
        if (requestCode == REQUEST_PERMISSION_LOCATION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "onRequestPermissionsResult() _ 권한 허용 클릭")
                startLocationUpdates()
                // View Button 활성화 상태 변경
                btnStartupdate.isEnabled = false
                btnStopUpdates.isEnabled = true
            } else {
                Log.d(TAG, "onRequestPermissionsResult() _ 권한 허용 거부")
                Toast.makeText(this@GPSPermissionActivity, "권한이 없어 해당 기능을 실행할 수 없습니다.", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

 

 

5. 결과물