iT邦幫忙

2023 iThome 鐵人賽

DAY 23
0
Kotlin

喝咖啡要30天?一起用 Kotlin 打造尋找好喝咖啡的 App系列 第 23

Day23 在 Google 地圖上顯示全台咖啡廳資訊 - 2 向使用者取得位置權限

  • 分享至 

  • xImage
  •  

找咖啡專案的需求 :

  • [x] 點擊地圖標記顯示商家資訊
  • [ ] 顯示我的位置
  • [ ] 在地圖上顯示咖啡廳標記
  • [ ] 點擊地圖標記顯示咖啡廳資訊
  • [ ] 顯示我的位置附近的咖啡廳資訊
  • [ ] 加入篩選功能

昨天已經成功的實作出我們的第一個需求 - 點擊地圖標記顯示商家資訊,接著繼續做出第二的需求 - 顯示我的位置附近的商家資訊。

要顯示我的位置附近的商家,首先需要取得使用者裝置的位置,就從這邊下手吧 !

今天的實作範例 :

取得裝置位置

位置存取權類型

每種權限都具有以下特性:

前景位置資訊 :

文件上說,如果應用程式包含只會分享或接收位置資訊一次,或只在指定時間內分享位置資訊的功能,則必須取得前景位置資訊存取權才能執行該功能。

在 Android 10 以上,系統會強制要宣告前景服務類型 :

<!-- Recommended for Android 9 (API level 28) and lower. -->
<!-- Required for Android 10 (API level 29) and higher. -->
<service
    android:name="MyNavigationService"
    android:foregroundServiceType="location" ... >
    <!-- Any inner elements would go here. -->
</service>

背景位置資訊

如果應用程式中會持續和其他地使用者分享位置或是使用 Geofencing API,我們就需要背景位置資訊存取權。

在 Android 10 (API 級別 29) 以上版本中,會被強制宣告 [ACCESS_BACKGROUND_LOCATION](https://developer.android.com/reference/android/Manifest.permission?hl=zh-tw#ACCESS_BACKGROUND_LOCATION) 權限 :

<manifest ... >
  <!-- Required only when requesting background location access on
       Android 10 (API level 29) and higher. -->
  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
</manifest>

但我們專案應該用不到,就提供給各位參考了!

取得使用者權限

先來取得使用者權限,Android 提供兩種位置存取權,選擇的權限會決定 API 傳回的位置精確度。

  • android.permisjsion.ACCESS_COARSE_LOCATION : 允許 API 傳回裝置的大概位置。取得權限後,就能依據定位服務取得裝置的概略位置,這邊有官方文件可以看更多。
  • android.permission.ACCESS_FINE_LOCATION  : 允許 API 透過可用的定位服務供應商 (包括全球定位系統 (GPS) 以及 Wi-Fi 和行動數據,盡可能精確判斷位置。

文件有提供大概位置和精確位置的定義 :

d22_1.png

以下為範例程式碼 :

val locationPermissionRequest = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        when {
            permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
                // Precise location access granted.
            }
            permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
                // Only approximate location access granted.
            } else -> {
                // No location access granted.
            }
        }
    }

// ...

// Before you perform the actual permission request, check whether your app
// already has the permissions, and whether your app needs to show a permission
// rationale dialog. For more details, see Request permissions.
locationPermissionRequest.launch(arrayOf(
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION))

接著來專案內實作,我們在onMapReady() 內向使用者取得權限 :

/**
 * Flag indicating whether a requested permission has been denied after returning in
 * [.onRequestPermissionsResult].
 */
private var permissionDenied = false

// Update the map configuration at runtime.
override fun onMapReady(googleMap: GoogleMap) {

    // return early if the map was not initialised properly
    map = googleMap

    // create bounds that encompass every location we reference
    val boundsBuilder = LatLngBounds.Builder()

    // include all places we have markers for on the map
    places.keys.map { place -> boundsBuilder.include(places.getValue(place)) }
    val bounds = boundsBuilder.build()

    with(map) {

        // Set the map type
        mapType = com.google.android.gms.maps.GoogleMap.MAP_TYPE_NORMAL

        // Display traffic 、指南針、縮放按鈕
        isTrafficEnabled = true
        uiSettings.isCompassEnabled = true
        uiSettings.isZoomControlsEnabled = true

        // Override the default content description on the view, for accessibility mode.
        // Ideally this string would be localised.
        setContentDescription("Map with lots of markers.")

        moveCamera(com.google.android.gms.maps.CameraUpdateFactory.newLatLngBounds(bounds, 50))

        setOnMyLocationButtonClickListener(this@MainActivity)
        setOnMyLocationClickListener(this@MainActivity)
    }

    // Add lots of markers to the googleMap.
    addMarkersToMap()

    // 取得位置權限
    requestLocationPermissions()
}

// ...

/**
 * 權限 callback
 */
private val locationPermissionRequest = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    when {
        permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
            // Precise location access granted.
            enableMyLocation()
        }
        permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
            // Only approximate location access granted.
            enableMyLocation()
        }
        else -> {
            // No location access granted.
            permissionDenied = true
        }
    }
}

/**
 * 取得位置權限
 */
private fun requestLocationPermissions() {
    locationPermissionRequest.launch(
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        )
    )
}

/**
 * Enables the My Location layer if the fine location permission has been granted.
 */
@SuppressLint("MissingPermission")
private fun enableMyLocation() {

    // 1. Check if permissions are granted, if so, enable the my location layer
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED
    ) {
        map.isMyLocationEnabled = true
        return
    }

    // 3. Otherwise, request permission
    ActivityCompat.requestPermissions(
        this,
        arrayOf(
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        ),
        LOCATION_PERMISSION_REQUEST_CODE
    )
}

override fun onMyLocationButtonClick(): Boolean {
    Toast.makeText(this, "MyLocation button clicked", Toast.LENGTH_SHORT)
        .show()
    // Return false so that we don't consume the event and the default behavior still occurs
    // (the camera animates to the user's current position).
    return false
}

override fun onMyLocationClick(location: Location) {
    Toast.makeText(this, "Current location:\n$location", Toast.LENGTH_LONG)
        .show()
}

companion object {
    /**
     * Request code for location permission request.
     *
     * @see .onRequestPermissionsResult
     */
    private const val LOCATION_PERMISSION_REQUEST_CODE = 1
}

執行後就會跳出詢問權限的對話框了~

d22_3.png

今日推推

Yes


上一篇
Day22 在 Google 地圖上顯示全台咖啡廳資訊 - 1
下一篇
Day24 在 Google 地圖上顯示全台咖啡廳資訊 - 3 顯示我的位置
系列文
喝咖啡要30天?一起用 Kotlin 打造尋找好喝咖啡的 App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言