티스토리 뷰

Android/Kotlin

[Kotlin]  위치 권한

혀가 길지 않은 개발자 2021. 5. 27. 04:32

1. 위치 권한 팝업 (2가지) (주의사항)

2. GPS 허용 팝업, 위치 얻기


Manifest.permission.ACCESS_FINE_LOCATION (해당 권한 팝업 허용시 ACCESS_COARSE_LOCATION 권한도 허용됨)

Manifest.permission.ACCESS_COARSE_LOCATION (팝업 허용시 ACCESS_FINE_LOCATION 권한은 허용이 안됨)

ContextCompat.checkSelfPermission()

ActivityResultContracts.RequestPermission()

ActivityResultContracts.RequestMultiplePermissions()

PackageManager.PERMISSION_GRANTED

checkSelfPermission

requestPermissions

FusedLocationProviderApi

FusedLocationProviderClient

 


1. 위치 권한 팝업 (2가지) (주의사항)

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_latitude"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24dp"
        tools:text="111.12313"/>

    <TextView
        android:id="@+id/tv_longitude"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24dp"
        tools:text="122.244231"/>

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/bt_permission_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="권한 요청 첫 번째 방법"
        android:textAllCaps="false" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/bt_permission_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="권한 요청 두 번째 방법"
        android:textAllCaps="false" />

</LinearLayout>

activity_main.xml

 

 

MainActivity.kt

package com.example.gpsexample

import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat

/**
 * 1. ActivityResultLauncher.launch() 를 이용하여 위치 권한 요청
 * 2. Activity.RequestPermissions() 를 이용하여 위치 권한 요청
 *
 * 주의 사항 : 첫 번째 방법 사용 시 onRequestPermissionsResult() 의 super() 먼저 호출되고
 *          registerForActivityResult 콜백이 실행된 후 다시
 *          onRequestPermissionsResult() super() 하위 코드가 실행된다.
 *          when() 으로 분기 처리 안 하면 위치 권한 응답 처리가 두 번 된다.
 *
 *          두 번째 방법 사용 시엔 onRequestPermissionsResult() 만 실행됨.
 *
 *          즉, 사용자가 시스템 권한 대화상자에 응답하면 onRequestPermissionsResult() 실행됨.
 */
class MainActivity : AppCompatActivity() {

    companion object {
        const val REQUEST_CODE_PERMISSIONS = 1001
    }

    private val permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

    private val tvLatitude: TextView by lazy { findViewById(R.id.tv_latitude) }
    private val tvLongitude: TextView by lazy { findViewById(R.id.tv_longitude) }
    private val btPermission1: AppCompatButton by lazy { findViewById(R.id.bt_permission_1) }
    private val btPermission2: AppCompatButton by lazy { findViewById(R.id.bt_permission_2) }

    private lateinit var activityResultLauncher: ActivityResultLauncher<Array<String>>

    @RequiresApi(Build.VERSION_CODES.M)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        activityResultLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            if (it.all { permission -> permission.value == true }) {

            } else {
                Toast.makeText(this, "권한 거부", Toast.LENGTH_SHORT).show()
            }
        }

        setOnClick()
    }

    /**
     * 버튼 클릭 이벤트
     */
    @RequiresApi(Build.VERSION_CODES.M)
    private fun setOnClick() {

        // 권한 요청 첫 번째 방법
        btPermission1.setOnClickListener {
            if (checkPermission(permissions)) {
                activityResultLauncher.launch(permissions)
            }
        }

        // 권한 요청 두 번째 방법
        btPermission2.setOnClickListener {
            if (!checkPermission(permissions)) {
                requestPermissions(permissions, REQUEST_CODE_PERMISSIONS)
            }
        }
    }

    /**
     * 권한 체크
     */
    private fun checkPermission(permissions: Array<String>): Boolean {
        return permissions.all {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
    }

    /**
     * 권한 요청 결과
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            REQUEST_CODE_PERMISSIONS -> {
                if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {

                } else {
                    Toast.makeText(this, "권한 거부", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

Activity.RequestPermissions() 사용시 @RequiresApi(Build.VERSION_CODES.M) 필요

즉, ActivityResultLauncher 사용 추천

주의사항둘 다 onRequestPermissionResult 호출됨. (when 분기처리 한 이유)

실행 결과

 

 


2. GPS 허용 팝업, 위치 얻기

LocationRequest

LocationSettingRequest

FusedLocationProviderClient

LocationCallback()

ActivityResultContracts.StartIntentSenderForResult()


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_latitude"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24dp"
        tools:text="111.12313"/>

    <TextView
        android:id="@+id/tv_longitude"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="24dp"
        tools:text="122.244231"/>

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/bt_permission_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:text="권한 요청 첫 번째 방법"
        android:textAllCaps="false" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/bt_permission_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="권한 요청 두 번째 방법"
        android:textAllCaps="false" />

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/bt_location"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="위치 얻기"
        android:textAllCaps="false" />

</LinearLayout>

activity_main.xml

 

 

MainActivity.kt

package com.example.gpsexample

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.IntentSender
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatButton
import androidx.core.content.ContextCompat
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.*

/**
 * 1. ActivityResultLauncher.launch() 를 이용하여 위치 권한 요청
 * 2. Activity.RequestPermissions() 를 이용하여 위치 권한 요청
 *
 * 주의 사항 : 첫 번째 방법 사용 시 onRequestPermissionsResult() 의 super() 먼저 호출되고
 *          registerForActivityResult 콜백이 실행된 후 다시
 *          onRequestPermissionsResult() super() 하위 코드가 실행된다.
 *          when() 으로 분기 처리 안 하면 위치 권한 응답 처리가 두 번 된다.
 *
 *          두 번째 방법 사용 시엔 onRequestPermissionsResult() 만 실행됨.
 *
 *          즉, 사용자가 시스템 권한 대화상자에 응답하면 onRequestPermissionsResult() 실행됨.
 */
class MainActivity : AppCompatActivity() {

    companion object {
        const val REQUEST_CODE_PERMISSIONS = 1001
    }

    private val permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

    private val tvLatitude: TextView by lazy { findViewById(R.id.tv_latitude) }
    private val tvLongitude: TextView by lazy { findViewById(R.id.tv_longitude) }
    private val btPermission1: AppCompatButton by lazy { findViewById(R.id.bt_permission_1) }
    private val btPermission2: AppCompatButton by lazy { findViewById(R.id.bt_permission_2) }
    private val btLocation: AppCompatButton by lazy { findViewById(R.id.bt_location) }

    private lateinit var permissionResultLauncher: ActivityResultLauncher<Array<String>>
    private lateinit var resolutionResultLauncher: ActivityResultLauncher<IntentSenderRequest>

    private val locationRequest = LocationRequest.create().apply {
        priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        interval = 10000
        fastestInterval = 5000
        numUpdates = 10
    }

    private val builder = LocationSettingsRequest.Builder()
        .addLocationRequest(locationRequest)
        .setAlwaysShow(true)

    private lateinit var fusedLocationClient: FusedLocationProviderClient

    private val locationCallback = object : LocationCallback() {
        override fun onLocationResult(p0: LocationResult) {
            super.onLocationResult(p0)
            tvLatitude.text = p0.lastLocation.latitude.toString()
            tvLongitude.text = p0.lastLocation.longitude.toString()
        }

        override fun onLocationAvailability(p0: LocationAvailability) {
            super.onLocationAvailability(p0)
        }
    }

    @SuppressLint("VisibleForTests")
    @RequiresApi(Build.VERSION_CODES.M)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        fusedLocationClient = FusedLocationProviderClient(this)

        permissionResultLauncher = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
            if (it.all { permission -> permission.value == true }) {
                getLocation()
            } else {
                Toast.makeText(this, "권한 거부", Toast.LENGTH_SHORT).show()
            }
        }

        resolutionResultLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
            if (it.resultCode == RESULT_OK) {
                getLocation()
            }
        }

        setOnClick()
    }

    override fun onPause() {
        super.onPause()
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    /**
     * 버튼 클릭 이벤트
     */
    @RequiresApi(Build.VERSION_CODES.M)
    private fun setOnClick() {
        // 권한 요청 첫 번째 방법
        btPermission1.setOnClickListener {
            if (!checkPermission(permissions)) {
                permissionResultLauncher.launch(permissions)
            } else {
                Toast.makeText(this, "이미 권한이 있습니다.", Toast.LENGTH_SHORT).show()
            }
        }

        // 권한 요청 두 번째 방법
        btPermission2.setOnClickListener {
            if (!checkPermission(permissions)) {
                requestPermissions(permissions, REQUEST_CODE_PERMISSIONS)
            } else {
                Toast.makeText(this, "이미 권한이 있습니다.", Toast.LENGTH_SHORT).show()
            }
        }

        // 위치 얻기
        btLocation.setOnClickListener {
            getLocation()
        }
    }

    /**
     * 권한 체크
     */
    private fun checkPermission(permissions: Array<String>): Boolean {
        return permissions.all {
            ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_GRANTED
        }
    }

    /**
     * 위치 얻기
     */
    private fun getLocation() {
        if (checkPermission(permissions)) {
            LocationServices.getSettingsClient(this@MainActivity).checkLocationSettings(builder.build()).run {
                addOnSuccessListener { response ->
                    // All location settings are satisfied. The client can initialize
                    // location requests here.
                    // ...
                    fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
                }

                addOnFailureListener { exception ->
                    if (exception is ResolvableApiException) {
                        // Location settings are not satisfied, but this can be fixed
                        // by showing the user a dialog.
                        try {
                            // Show the dialog by calling startResolutionForResult(),
                            // and check the result in onActivityResult().
                            val intentSenderRequest = IntentSenderRequest.Builder(exception.resolution).build()
                            resolutionResultLauncher.launch(intentSenderRequest)
                        } catch (sendEx: IntentSender.SendIntentException) {
                            // Ignore the error.
                        }
                    }
                }
            }
        } else {
            permissionResultLauncher.launch(permissions)
        }
    }

    /**
     * 권한 요청 결과
     */
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            REQUEST_CODE_PERMISSIONS -> {
                if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) {

                } else {
                    Toast.makeText(this, "권한 거부", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

시스템 위치 OFF 상태

 

실행 결과

 

위치 사용 ON 상태

 

 

 

 

 

참고.

https://developer.android.com/training/location/change-location-settings?hl=ko (위치 요청 다이얼로그)

 

위치 설정 변경  |  Android 개발자  |  Android Developers

앱에서 위치를 요청하거나 권한 업데이트를 수신해야 한다면 기기는 GPS 또는 Wi-Fi 검색과 같은 적절한 시스템 설정을 사용해야 합니다. 앱은 서비스(예: 기기의 GPS)를 직접 사용 설정하기보다는

developer.android.com

https://developer.android.com/training/location/request-updates?hl=ko#stop-updates 

 

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

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

developer.android.com

https://stackoverflow.com/questions/65158308/deprecated-onactivityresult-in-androidx  (startResolutionForResult 응답 처리)

 

deprecated OnActivityResult() in androidx

OnActivityResult() is deprecated in androidx. I took reference from below links https://developer.android.com/training/basics/intents/result https://developer.android.com/jetpack/androidx/releases/

stackoverflow.com

 

'Android > Kotlin' 카테고리의 다른 글

[Kotlin]  Lifecycle, LifecycleOwner, LifecycleObserver  (0) 2021.06.05
[Kotlin]  registerForActivityResult  (0) 2021.05.27
[Kotlin]  View  (0) 2021.05.25
[Kotlin] dp와 px  (0) 2021.05.25
[Kotlin] CoordinatorLayout  (0) 2021.05.24
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함