Titanium

Geolocation with Titanium – Part 1

Geolocation

As a freelance Titanium developer, I often get enquiries that relate to apps that require Geolocation services; either pin-pointing the user to a location in order to show where they are, OR delivering results based on the users location such as local restaurants or services.

To get started with Geolocation with Titanium, there’s a few things you need to do.

Firstly, you need to tell Apple and Google that your app requires access to the device GPS and any related services. This is important as it enables the OS to display the necessary permission dialogs to the user so they can allow / deny permission.

Without doing this, you won’t be able to run any Geolocation code.

To get started — in the tiapp.xml file, you need to add this for iOS:

<ios>
    <plist>
        <dict>
            <key>NSLocationWhenInUseUsageDescription</key>
            <string>We need your location while using because...</string>
            <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
            <string>We always need you location because...</string>
        </dict>
    </plist>
</ios>

For Android, you’ll need to add the following:

<android xmlns:android=“https://schemas.android.com/apk/res/android”>
    <manifest>
        <uses-permission android:name=“android.permission.ACCESS_FINE_LOCATION”/>
        <uses-permission android:name=“android.permission.ACCESS_COARSE_LOCATION”/>
    </manifest>
</android>

Once you’ve added these, the first time you try to use Geolocation services in the app, the OS will tell the user that the app needs permission and they can approve or deny requested access.

Starting with Titanium SDK 7.1.0.GA, there’s also support for FusedLocationProvider on Android. To enable this, the only thing you need to do is include the Ti.PlayServices module in your app. This will enable battery efficient geolocation in your app.

Assuming they approve the request to track location, you can get the device’s position on both platforms using the following code:

function getLocation() {
    Ti.Geolocation.getCurrentPosition(function(e) {
        console.log(e);
    });
}

getLocation();

While you can just go-ahead and request the location like this, it’s only going to work if the user accepted the location request. If they selected “deny”, then your app won’t have permission and the code above will fail.

To ensure your app offers the best experience, it’s important to ensure you have the relevant permission before getting the current location — that way you can handle a situation where the app doesn’t have permission, or has lost permission since a location update was last obtained, and display the relevant error to the user.

It’s important to remember that location permissions can be updated at any time by the user within the devices settings and on iOS, there’s a friendly reminder that can come up from time-to-time to remind the user that an app is still using the location and does the user want to continue to allow it.

If the user changes settings or denies access when prompted, the code above will fail to run successfully and could display a nasty error to the user (or nothing at all).

In order to ensure the app still has the required permission, we need to wrap our code in a new block that checks a) if the app has permission and b) if it has the permissions we need:

if (Ti.Geolocation.hasLocationPermissions()) {
    getLocation();
} else {
    Ti.Geolocation.requestLocationPermissions(Ti.Geolocation.AUTHORIZATION_WHEN_IN_USE, function (e) {
        if (e.success) {
            getLocation();
        } else {
            alert(‘could not obtain location permissions’);
        }
    });
}

In the above example, we are checking for AUTHORIZATION_WHEN_IN_USE because we’re currently focusing on getting the location while the app is running. We’ll come back to AUTHORIZATION_ALWAYS usage in Part 2.

Now, in the example above we are just getting the location once. And while we could wrap all this in a timer to run every few seconds or minutes, if the user’s position doesn’t change we’re just wasting code cycles checking for updates when there are now. What we need is our code to ONLY run when the location updates.

That’s where the location event comes in — by adapting the getLocation function we can “subscribe” to location updates whenever they occur and then act on them.

function getLocation() {
 Ti.Geolocation.addEventListener("location",function(e) {
 console.log(e);
 });
}

With this code running once at startup, we have a function that will run when the app is running and when it receives a location update from the device. If the user doesn’t move, the code doesn’t run, but the moment they do, the app gets a location update and can handle it accordingly.

What you get back from a location update is a lot of useful data like this:

{
       code = 0;
       coords =     {
          accuracy = 5;
          altitude = 0;
          altitudeAccuracy = "-1";
          floor =         {
              level = 0;
          };
          heading = "-1";
          latitude = "51.25243759155273";
          longitude = "-1.603847026824951";
          speed = "-1";
          timestamp = 1553776951640;
      };
      source = "[object GeolocationModule]";
      success = 1;
      type = location;
  }

Alongside longitude and latitude, you get heading, speed and elevation so you can determine if someone is up a mountain, or is walking, running, riding a bike, driving a car etc. based on the speed.

There’s also some additional settings you can play with within Ti.Geolocation to fine tune how and when your app gets location updates and how and when the device delivers notification updates. This is important not only for accuracy but to ensure your app is not killing the device battery by checking the location too much or when the user hasn’t even moved!

You can set the accuracy you require for location updates, and depending on the OS and device, different aspects of the hardware will be used to work out where you are. For example, in a low power and low accuracy mode, the OS might use cell towers, WiFi or some other method to work out your approximate position. With high accuracy modes, the OS might use all this and/or the GPS of the device to get a super-accurate location (the kind you might require for displaying your exact position on a map.).

For accuracy, you can use the Ti.Geolocation.accuracy property to set this to different accuracy levels such as Ti.Geolocation.ACCURACY_BEST or Ti.Geolocation.ACCURACY_LOW .

If you’re using iOS, you can use the Ti.Geolocation.distanceFilter property to set a number of meters you require the user to move before a location event is fired.

If you’re on Android, you can setup a Ti.Geolocation.Android.LocationRule to fine tune the settings. So, in the following example, we’re telling the device we don’t want location updates unless the location update has an accuracy less than 20m and at least 10 seconds has passed since the last update.

Ti.Geolocation.Android.addLocationRule({accuracy: 20, minAge: 10000});

There’s also an activity property which is used to determine when location updates may be automatically paused by the OS, to save power e.g. Ti.Geolocation.ACTIVITYTYPE_FITNESS or Ti.Geolocation.ACTIVITYTYPE_AUTOMOTIVE_NAVIGATION

All the code so far works great when the app is running (or has been backgrounded but is still active) but what happens if the app is in the background and iOS or Android decides to kill the app to save memory and resources? What happens if the app crashes or the device is rebooted?

In Part 2, I’ll be covering these scenarios and showing you how you can write apps that use background location updates in a way that’s accurate, uses little power and can survive an app crash or device reboot.

I hope you’ve enjoyed this so far — please leave comments below and let us know how you’re using Geolocation services with Titanium and share any links to libraries, code or examples you like or you’ve written. Also, let us know if there’s anything we’ve missed you’d like to cover and we may cover these in subsequent posts.

Thanks for reading and Happy Coding!