Requesting the Current Location of iOS device from Kotlin using Compose Multiplatform.
In this blog post, we will see how we can use Apple’s CoreLocation APIs to request the user’s current location using Kotlin. essentially we do not need to write a Swift library, but given that Kotlin multiplatform interoperates with most Apple APIs, we can safely call them within Kotlin.
The steps required for doing this are as follows:
- Create a Compose multiplatform project on the Kmp website
- Create a LocationManager class that implements the CLLocationManagerDelegate protocol and extends NSObject in the iOS source set.
- Override CLLocationManager protocol functions and request permissions.
The Apple APIs are imported through platform.*
and we have a simple enum with three states depending on whether the location authorization has been accepted by the user, has been denied or is restricted or the user has accepted one of the required permissions. Most importantly, a CLLocationManager object is created and the current class is set as its delegate and the location manager's desired accuracy is set to the best accuracy in init
.
Two CancellableContinuation , one which holds the status of the location authorization and one which holds the result which could be a CLLocation object. Let us proceed to write the crucial code that is requesting authorization.
Requesting Location Services Authorization
Start by going to the iOS app source set and add two keys and values in Info.plist
for the appropriate message that will be shown to the user when the authorization for location services prompt is shown.
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location permission request to get weather for your location</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Location permission request to get weather for your location</string>
When you request authorization from the iOS location manager if the authorization to use the device location has not been shown to the user before, then its status is not determined, we can go ahead and request the authorization using requestWhenInUseAuthorization()
which requests that the app gets access to the app only when in use, if the app is restricted from using the location or the user denies the request, we resume locationPermissionStatusCancellableContinuation
with the value RESTRICTED_OR_DENIED
, else if the user grants authorization to the app to use location services, our app receives with kCLAuthorizationStatusAuthorizedWhenInUse
or kCLAuthorizationStatusAuthorizedAlways
and we resume our continuation with LocationPermissionStatus.ACCEPTED
to signal that the authorization was accepted.
Requesting Current Device Location
The main call to CLLocationManager’s requestLocation
method will deliver a single CLLocation to us if it succeeds. We ensure that the created continuation is assigned to locationResultContinuation
.
Overriding locationManager functions
We will implement override three locationManager
functions which will deliver our continuations different values for the LocationPermissionStatus
and CLLocation
accordingly. The first method to override tells our delegate when authorization status changes. The equivalent Swift method is here.
Each time the authorization status changes, we resume our continuation with the appropriate value for LocationPermissionStatus
.
The next function override handles situations when the location manager fails to retrieve a value, thus in this case we resume our status with RESTRICTED_OR_DENIED
and the location result continuation with an exception. The equivalent Swift function is here.
The next override delivers new a list of CLLocation
s to the delegate when location data is available. In this case, we verify that at least a valid location is found and resume the location result continuation. The equivalent Swift method is here. The location data is delivered in didUpdateLocations.
At this point, the full class should be as follows.
The CVLocation.coordinate
field returns a CLLocationCoordinate2D Swift struct which has the latitude and longitude. We will destructure this structure to have the equivalent values using two extension functions.
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.useContents
import platform.CoreLocation.CLLocation
@OptIn(ExperimentalForeignApi::class)
fun CLLocation.getLatitude(): Double = this.coordinate().useContents { latitude }
fun CLLocation.getLongitude(): Double = this.coordinate().useContents { longitude }
At this point, let us test this by this in a Composable specific to the iOS source set.
When the composable is entered, we use a LauchedEffect
to trigger the requestPermission
method from our IosLocationManager
class and depending on the status, we verify if a location was retrieved.
Running the composable for the first time shows a dialog to show the authorization dialog to the user and if a user selects the options Allow Once
or Allow While Using App
, the next screen gets the user’s current longitude and latitude else, it displays that they permission was denied.
Well, we have successfully been able to complete the task. Congrats.
NB: I am from the Android ecosystem, and I am used to saying permissions unlike iOS land which uses authorization, so here, I use them interchangeably 😆.
I hope you felt like a superstar writing iOS code without using Swift .