Skip to content

Getting Current Indoor Position

For use cases that require getting users location (e.g. mark your car), the following code snippets can be used. At first, you need to make sure that the Pointr instance is up and running. We can have a helper function that handles Pointr initialization and starting:

func startPointr(onComplete: @escaping (PointrState) -> Void) {
    print("starting pointr")
    if (Pointr.shared.state == .running) {
        print("already running")
        onComplete(Pointr.shared.state)
        return
    }
    // check for terminal states
    guard Pointr.shared.state != .failedInvalidDeepLinkUrl, Pointr.shared.state != .failedValidation, Pointr.shared.state != .failedRegistration, Pointr.shared.state != .failedRegistration, Pointr.shared.state != .failedNoInternet else {
        print("failed to start")
        onComplete(Pointr.shared.state)
        return
    }
    if (Pointr.shared.state.rawValue > 0) {
        print("start operation is in progress")
        let listener = MyPointrStateChangeListener(onComplete: onComplete)
        // means start operation is in progress
        // add another listener to forward the result
        Pointr.shared.addListener(listener)
        return
    }
    let params = PTRParams()
    params.loggerLevel = .all
    params.mode = PointrDebugMode()
    params.baseUrl = "https://sample-app-v8-api.pointr.cloud"
    params.clientIdentifier = "72df7035-cdea-4c19-93b8-6deff5177894"
    params.licenseKey = "c93f0101-36ed-4129-8f95-558f4cd8e82f"
    print("pointr is off...")
    // SDK only works when started from main thread for react-native, ionic and flutter
    // but our function is not a blocking function
    Pointr.shared.start(with: params) { state in
        print("\(PTRPointrStateToString(state))")
        if (state.rawValue > 0 && state != .running) { return }
        onComplete(state)
    }
}
class MyPointrStateChangeListener: NSObject, PointrStateChangeListener {
    let onComplete: (PointrState) -> Void
    init(onComplete: @escaping (PointrState) -> Void) {
        self.onComplete = onComplete
    }
    func pointrStateDidChange(to state: PointrState) {
        print("state: \(PTRPointrStateToString(state))")
        // start in progress
        if (state.rawValue > 0  && state.rawValue < PointrState.running.rawValue) { return }
        Pointr.shared.removeListener(self)
        onComplete(state)
    }
}
fun startPointr(context: Context, onComplete: (Pointr.State) -> Unit) {
    Pointr.getPointr()?.let { pointr ->
        if (pointr.state == Pointr.State.RUNNING) {
            // pointr is already running
            onComplete(Pointr.State.RUNNING)
            return
        }
        if (pointr.state.`val` < 0) {
            // failed to start
            onComplete(pointr.state)
            return
        }
        if (pointr.state.`val` > 0) {
            // means start operation is in progress
            // add another listener to forward the result
            pointr.addListener { state ->
                if (state.`val` < Pointr.State.RUNNING.`val` && state.`val` > 0) {
                    return@addListener
                }
                Pointr.getPointr()?.removeListener(this)
                onComplete(state)
            }
            return
        }
    }
    val params = PTRParams(
        "72df7035-cdea-4c19-93b8-6deff5177894",
        "c93f0101-36ed-4129-8f95-558f4cd8e82f",
        "https://sample-app-v8-api.pointr.cloud"
    )
    params.logLevel = Plog.LogLevel.VERBOSE
    Pointr.with(context, params)
    Pointr.getPointr()?.addListener { state ->
        if (state.`val` < Pointr.State.RUNNING.`val` && state.`val` > 0) {
            return@addListener
        }
        // remove and complete when a terminal state has been reached
        Pointr.getPointr()?.removeListener(this)
        onComplete(state)
    }
    Pointr.getPointr?.start()
}

This method initializes and starts the Pointr instance if it is not already started. It returns the state asynchronously. Callback function onComplete must be waited upon before proceeding to next event.

A listener is needed for special case where Pointr is in a state that is between running and off such as a scenario where multiple calls to start the SDK could be done.

Listener here removes itself from Pointr instance once start operation ends with a terminal state.

At this point, if we have the Pointr instance running, we can wait for position updates by adding PositionManager a listener

let dispatchGroup = DispatchGroup()
let positionManagerDelegate = MyPositionManagerDelegate(dispatchGroup: dispatchGroup)
dispatchGroup.enter()
Pointr.shared.positionManager?.addListener(positionManagerDelegate)
print("wait for position update")
let result = dispatchGroup.wait(wallTimeout: .now() + DispatchTimeInterval.seconds(60))
if result != .success {
    print("unable to get location within defined interval")
    Pointr.shared.positionManager?.removeListener(positionManagerDelegate)
}
return Pointr.shared.positionManager?.currentCalculatedLocation
where MyPositionManagerDelegate is a delegate that takes the DispatchGroup instance in the constructor.
class MyPositionManagerDelegate: NSObject, PTRPositionManagerDelegate {
    let dispatchGroup: DispatchGroup
    init(dispatchGroup: DispatchGroup) {
        self.dispatchGroup = dispatchGroup
    }
    func onPositionManagerCalculatedLocation(_ calculatedLocation: PTRCalculatedLocation) {
        print("position update: \(calculatedLocation.coordinate.latitude), \(calculatedLocation.coordinate.longitude)")
        if (!calculatedLocation.isValid()) { return }
        // remove it self as listener when done
        Pointr.shared.positionManager?.removeListener(self)
        dispatchGroup.leave()
    }
}

It may take some time to fetch the site and content data if Pointr SDK is doing a fresh start. Because of this, a DispatchGroup is used to wait for callback from PositionManagerDelegate. You can define a timeout period, which is 60 seconds as in this example.

val semaphore = Semaphore(1)
semaphore.acquire()
Pointr.getPointr()?.positionManager?.addListener(object: PositionManager.Listener {
    override fun onPositionManagerCalculatedLocation(p0: CalculatedLocation?) {
        val calculatedLocation = p0 ?: return
        if (!calculatedLocation.isValid()) return
        Pointr.getPointr()?.positionManager?.removeListener(this)
        semaphore.release()
    }
    override fun onPositionManagerDetectedPositionLevelChange(p0: Level) { }
    override fun onPositionManagerPositionIsFading() { }
    override fun onPositionManagerPositionIsLost() { }
    override fun onPositionManagerPositioningServiceStateChangedTo(p0: PositioningTypes.PositioningServiceState?) { }
})
Plog.i("wait for position update")
val result = semaphore.tryAcquire(60, java.util.concurrent.TimeUnit.SECONDS)
if (!result) {
    Plog.e("unable to get location within defined interval")
    Pointr.getPointr()?.removeListener(this)
}
return Pointr.getPointr()?.positionManager?.currentCalculatedLocation

It may take some time to fetch the site and content data if Pointr SDK is doing a fresh start. Because of this, a Semaphore is used to wait for callback from PositionManager.Listener. You can define a timeout period, which is 60 seconds as in this example.

Based on business uses cases, a geographically valid location (valid latitude and longitude) can be enough. In this case, isGeoValid function can be used to check the validity of the outdoor location. But in this example an indoor location is expected which can be verified with the isValid check to have a valid level, building and site information.

You can serialize and save the location to storage if it is going to be utilized later

Note

App needs to have all the necessary permissions to be able to get a position. Checkout out permission page for details.


Last update: April 1, 2024
Back to top