Skip to main content

Building better location permission gathering

This tutorial will guide you on using Radar's location permission manager to build a best-in-class user interface for collecting on-device location with the Radar iOS SDK and Android SDK. Achieving high opt-in rates for location permission helps your app deliver more value to users with location-based features.

While iOS and Android expose their own location permission APIs, using them directly can be confusing and clunky. We provide a comprehensive location permission management system that not only wraps the OS features but also offers advanced state management, tracking, and information collection through various channels. This system offers developers a clean and easy interface to streamline their location permission gathering UI flow while leveraging powerful underlying capabilities.

Languages used#

  • Swift
  • Kotlin

Features used#

Steps#

Step 1: Sign up for Radar#

If you haven't already, sign up for a Radar account to get your API key, which is required to initialize the SDK. The location-permission functionality is provided without limit, free of charge. You can also create up to 1,000 geofences, track 1,000 users, and make 100,000 API calls per month.

Get API keys

Step 2: Install the Radar iOS SDK#

iOS#

If you're starting from scratch, create a new Xcode project of type Single View App.

Install the iOS SDK using CocoaPods or SPM (recommended) or by downloading the framework and dragging it into your project.

Initialize the SDK in your App struct with your publishable API key.

import SwiftUIimport RadarSDK
@mainstruct ExampleApp: App {    init(){        Radar.initialize(publishableKey: "prj_test_pk...")    }    var body: some Scene {        WindowGroup {            ContentView()        }    }}

Android#

The best way to add the SDK to your project is via Gradle. See the Android SDK installation guide.

When your app starts, in application onCreate(), initialize the SDK with your publishable API key.

import io.radar.sdk.Radar
class MyApplication : Application() {
    override fun onCreate() {        super.onCreate()
        val receiver = MyRadarReceiver { context, status ->            updateUI(status)        }
        Radar.initialize(this, "prj_test_pk...", receiver, Radar.RadarLocationServicesProvider.GOOGLE, true)    }}

Step 3: Implement your listeners#

Link the SDK to your own state/navigation management logic by implementing RadarDelegates/RadarReceiver. The radar SDK will send updates on location permission status to your application’s state/navigation management logic via RadarLocationPermissionStatus objects.

You can also get up to date RadarLocationPermissionStatus objects with Radar.getLocationPermissionStatus().

import Foundationimport CoreLocationimport RadarSDKclass PermissionManager: NSObject, RadarDelegate, ObservableObject  {        @Published var viewState:RadarLocationPermissionState        private override init() {        viewState = RadarLocationPermissionState.NoPermissionGranted        super.init()        viewState = Radar.getLocationPermissionStatus().locationPermissionState        Radar.setDelegate(self)    }        func didReceiveEvents(_ events: [RadarEvent], user: RadarUser?) {        // do nothing    }        func didUpdateLocation(_ location: CLLocation, user: RadarUser) {        // do nothing    }        func didUpdateClientLocation(_ location: CLLocation, stopped: Bool, source: RadarLocationSource){        // do nothing    }        func didFail(status: RadarStatus) {        // do nothing    }        func didLog(message: String) {        // do nothing    }        func didUpdateClientLocationPermissionStatus(status: RadarLocationPermissionStatus) {        viewState = status.locationPermissionState    }    static let shared = PermissionManager()            }

Step 4: Implement navigation logic#

Navigate to the appropriate view via the enum field, locationPermissionState, exposed on RadarLocationPermissionStatus. Each enumeration will map to a specific view on your application. You may opt to map multiple states to the same view to simplify your own implementation.

import SwiftUIimport CoreLocationimport RadarSDK
struct ContentView: View {    @ObservedObject var permissionManager = PermissionManager.shared    var body: some View {        NavigationView {            Group {                switch permissionManager.viewState {                case .NoPermissionGranted:                    GetForegroundPermissionStateView()                case .ForegroundPermissionPending:                    WaitingForForegroundPermissionView()                case .ForegroundPermissionRejected:                    GoToSettingsViewForegroundDenied()                case .ForegroundPermissionGranted:                    GetBackgroundPermissionStateView()                case .BackgroundPermissionPending:                    WaitingForBackgroundPermissionView()                case .BackgroundPermissionRejected:                    GoToSettingsViewBackgroundDenied()                case .BackgroundPermissionGranted:                    SuccessView()                default:                    GoToSettingsView()                }            }        }    }}

Suggested UI views#

Overview:#

Don't approach requesting location permission as a linear UI flow. The state of location permission can change in various ways inside and outside the application. Build a robust UI that gracefully handles any state the app is in. The code in this tutorial presents a minimalistic UI for simplicity. You should enhance the provided template to develop a production-ready UI with additional features and polish.

Handling Foreground Permission:#

NO_PERMISSIONS_GRANTED:#

If the application doesn't have any permission, explain why you need foreground permission before allowing the user to grant them. Take users to a screen explaining the need for foreground permission, using verbiage similar to the system prompt.

struct GetForegroundPermissionStateView: View {    var body: some View {        VStack {            Text("You currently have not granted any locaiton permission. To get foreground location permission, explain why you need them here.")            Button("Request Foreground Permission") {                Radar.requestForegroundLocationPermission()            }        }.navigationBarTitle("Get foreground", displayMode: .inline)    }}

FOREGROUND_LOCATION_PENDING:#

Optionally handle the waiting period while the permission popup is active.

struct WaitingForForegroundPermissionView: View {    var body: some View {        Text("Waiting for foreground permission")        .navigationBarTitle("Waiting", displayMode: .inline)    }}

FOREGROUND_PERMISSIONS_REJECTED:#

If foreground permission are not granted and essential for your application's core functionality, you should guide the user through a process to enable the required permission in the device settings. Provide clear instructions and a straightforward way for the user to access the appropriate settings screen to grant the necessary foreground permission.

struct GoToSettingsViewForegroundDenied: View {    var body: some View {        VStack {            Text("Go to settings, you cannot proceed without the  foreground permission")            Button("Open Settings") {                Radar.openAppSettings()            }        }.navigationBarTitle("Go to settings", displayMode: .inline)    }}

FOREGROUND_PERMISSIONS_GRANTED:#

If foreground permission are granted, you have two options for requesting background permission:

  • Promptly request background permission immediately after obtaining foreground permission. This proactive approach ensures background permission are acquired upfront.

  • Wait until the user attempts to use a feature or functionality that requires background permission before prompting for them. This just-in-time approach defers the permission request until it's absolutely necessary.

Choose the approach that aligns best with your application's design and user experience goals.

Handling Background Permission:#

FOREGROUND_PERMISSIONS_GRANTED:#

After foreground permission are granted, clearly explain to the user why your app requires background permission before prompting them to grant these permission. Consider using language similar to the operating system's prompts to increase user understanding and buy-in. Keep in mind that an app can only request location background permission if foreground location permission have already been granted. The Radar SDK enforces these prerequisites and will handle the appropriate permission requests accordingly.

struct GetBackgroundPermissionStateView: View {    var body: some View {        VStack {            Text("We have foregorund permission. To get background location permission, explain why you need them here")            Button("Request Background Permission") {                Radar.requestBackgroundLocationPermission()            }        }.navigationBarTitle("Get background", displayMode: .inline)    }}

BACKGROUND_LOCATION_PENDING:#

Optionally handle the waiting period while the permission popup is active in iOS. Requesting background permission in android takes the user to the setting screen.

struct WaitingForBackgroundPermissionView: View {    var body: some View {        Text("Waiting for background permission")        .navigationBarTitle("Waiting", displayMode: .inline)    }}

BACKGROUND_PERMISSIONS_REJECTED:#

If permission are not granted, provide clear information on the limited functionality and a way to redirect users to the settings if needed. Note that you are not allowed to block users who do not grant background permission and you should offer users a way to proceed without background permission.

struct GoToSettingsViewBackgroundDenied: View {    var body: some View {        VStack {            Text("Go to settings, you cannot proceed without the background permission")            Button("Open Settings") {                Radar.openAppSettings()            }        }.navigationBarTitle("Go to settings", displayMode: .inline)    }}

BACKGROUND_PERMISSIONS_GRANTED:#

If permission are granted, proceed with location-based functionalities that require background access.

Platform specific states:#

Android:#

  • FOREGROUND_PERMISSIONS_REJECTED_ONCE: The user has rejected to provide foreground permission once. You can still request for foreground permission but you are strongly encouraged to provide additional justification for requesting the permission via the UI.
  • APPROXIMATE_PERMISSIONS_GRANTED: Instead of granting ACCESS_FINE_LOCATION, the user has opted to grant ACCESS_FINE_LOCATION instead. You can still request for background permission but your foreground location tracking will be less accurate.
  • BACKGROUND_PERMISSIONS_REJECTED_ONCE: The user has rejected to provide background permission once. You can still request for background permission but you are strongly encouraged to provide additional justification for requesting the permission via the UI.

iOS#

  • PermissionRestricted: The operating system or the user has restricted the app from accessing location services. You should prompt the user to manually resolve this state.

Conclusion#

Using the Radar SDK can dramatically simplify your implementation of your applications location permission gathering UI. Start implementing best in class locations permission gathering UI with the Radar SDK.

Support#

Have questions or feedback on this documentation? Let us know! Contact us at radar.com/support.