NativeScript is a framework for building cross-platform native mobile apps using XML, CSS, and JavaScript. In this series, we'll try out some of the cool things you can do with a NativeScript app: geolocation and Google Maps integration, SQLite database, Firebase integration, and push notifications. Along the way, we'll build a fitness app with real-time capabilities that will use each of these features.
In this tutorial, you'll learn how to work with geolocation and Google Maps in NativeScript apps.
I'm assuming that you already know how to create apps in NativeScript. If you're new to NativeScript, I recommend that you first check out one of the earlier tutorials in NativeScript before trying to follow this tutorial.
An Introduction to NativeScript
Create Your First NativeScript App
Create a Weather App With TypeScript and NativeScript
What You'll Be Creating
You'll be creating a walking tracker using geolocation and Google Maps. It will show the user how much distance they've covered and the number of steps they've taken to cover that distance. There will also be a map that will show the user's current location.
To give you an idea, here's what the final output will look like:
Setting Up the Project
Start by creating a new NativeScript app:
tns create fitApp --appid "com.yourname.fitApp"
To make it easier to set up the UI of the app, I've created a GitHub repo which includes both the starter and final version of the project. You can go ahead and copy the contents of the app folder to your project's app folder. We will only be working with two files: main-page.xml and main-page.js file. The rest is just boilerplate from the NativeScript demo project.
Running the App
We will be using the Android emulator provided by Android Studio to test the app. This will allow us to use the Android GPS Emulator to simulate the changing of locations from the comfort of our own homes. I don't really like aimlessly walking around outside to test geolocation either! But if that's your thing then I won't stop you.
If you execute tns run android
, it will automatically call the Android emulator if it's already installed. If it's not yet installed, you can install it by launching Android Studio, clicking configure, and selecting SDK Manager. This will open the SDK Platforms by default. Click on the SDK Tools tab and make sure to select Android Emulator, and click on Apply to install it.
To use the GPS emulator, download it from GitHub and run the executable war file:
java -jar android-gps-emulator-0.2.war
Once that's done, you should be able to access http://localhost:8080/gpsemulator/
from your browser and connect to localhost
. Make sure that the Android emulator is already running when you do this. Once you're connected, simply zoom in the map and click on any place you want to use as the location. The app will detect this and use it as its current location.
Working With Geolocation
Geolocation in NativeScript is similar to the Geolocation API in JavaScript. The only difference in functionality is the addition of a distance()
function which is used for calculating the distance between two locations.
Installing the Geolocation Plugin
In order to work with geolocation, you first need to install the geolocation plugin:
tns plugin add nativescript-geolocation
Once that's done, you can now include it from your script files:
var geolocation = require("nativescript-geolocation");
Getting the User's Current Location
The NativeScript geolocation plugin includes three functions which you can use for working with the user's current location. We will be using each of these in this app:
getCurrentLocation
watchLocation
distance
Open the main-view-model.js file and add the following code inside the createViewModel()
function. Here we're initializing the variables that we will be using later on for storing the different values that are needed for keeping track of the user's location.
I've added some comments in the code so you know what's going on. There are also some lines of code that are commented out; these are for the Google Maps integration. I've commented them out for now to keep things simple. Once we get to the Google Maps integration, you'll need to remove those comments.
function createViewModel() { var viewModel = new Observable(); var watchId; // stores the ID of the location watcher so we can stop it later var start_location; // stores the location of the user when they first started tracking var current_location; // stores the current location of the user viewModel.is_tracking = false; // whether the user's location is currently being tracked or not //viewModel.latitude = 15.447819409392789; //viewModel.longitude = 120.93888133764267; //viewModel.zoom = 20; var total_distance = 0; var total_steps = 0; var locations = []; // array which will store the locations //var mapView; //var marker = new mapsModule.Marker(); if (!geolocation.isEnabled()) { // check if geolocation is not enabled geolocation.enableLocationRequest(); // request for the user to enable it } // next: add code for getting the user's current location }
Next, add the code for getting the user's current location. This code is executed when the user taps on the button for starting and stopping the location tracking. The geolocation.getCurrentLocation()
method is used to get the current location.
Here we've specified three options: desiredAccuracy
, updateDistance
, and timeout
. desiredAccuracy
allows you to specify the accuracy in meters. It has two possible values: Accuracy.high
, which is about 3 meters, and Accuracy.any
, which is about 300 meters. updateDistance
specifies how much difference (in meters) there must be between the previous location and the current location before it will update. Lastly, timeout
specifies how many milliseconds to wait for a location.
Once a location is received, we set it as the start_location
and push it on the locations
array. Later on, this location will be used along with the first location that will be fetched from watching the user's current location to determine the distance traveled.
viewModel.toggleTracking = function() { if (geolocation.isEnabled()) { this.set('is_tracking', !viewModel.is_tracking); // flip the toggle for tracking if (viewModel.is_tracking) { geolocation.getCurrentLocation( { desiredAccuracy: Accuracy.high, // 3 meter accuracy updateDistance: 5, // 5 meters timeout: 2000 // 2 seconds } ). then(function(loc) { if (loc) { start_location = loc; locations.push(start_location); //viewModel.set('latitude', loc.latitude); //viewModel.set('longitude', loc.longitude); } }, function(e){ dialogs.alert(e.message); }); // next: add code for watching user's current location } else { // next: add code to stop watching the user's current location } } else { dialogs.alert("Please enable Geolocation"); } }
Watching for the User's Current Location
To get the current location, we use the geolocation.watchLocation()
function. This function is similar to the setInterval()
function in JavaScript, because it also executes the callback function repeatedly until you stop it with the geolocation.clearWatch()
function. The callback function is automatically called based on the updateDistance
and minimumUpdateTime
.
In the code below, the location will be updated if it is at least 5 meters different from the previous location that was fetched. But this update will only happen every 5 seconds. This means that if the user hasn't walked 5 meters or more within 5 seconds, the location won't update.
watchId = geolocation.watchLocation( function (loc) { if (loc) { current_location = loc; // next: add code for getting the distance between two locations } }, function(e){ dialogs.alert(e.message); }, { desiredAccuracy: Accuracy.high, updateDistance: 5, // 5 meters minimumUpdateTime : 5000 // update every 5 seconds } );
Once the user indicates that they want to stop tracking, you need to call the geolocation.clearWatch()
function. You also need to reset the rest of the values that are being updated every time the location is changed.
geolocation.clearWatch(watchId); // stop watching the user's location total_distance = 0; total_steps = 0; locations = []; viewModel.set('distance', "distance travelled: " + total_distance + " meters"); viewModel.set('steps', "steps: " + total_steps);
Getting the Distance Between Two Locations
Now we're ready to get the distance. This can be done by calling the geolocation.distance()
function. This function accepts two location
objects as its arguments, so we'll use the last two locations that were pushed to the locations
array to determine the distance (in meters) traveled by the user from a previously recorded location to the current one. From there, we can use an approximate conversion from meters to the number of steps—I say approximate because not all people will travel the same distance in a single step.
After that, we can just add the resulting distance
and steps
to the total_distance
and total_steps
so we can keep track of the total distance and steps they have taken since they started tracking their location.
locations.push(loc); //viewModel.set('latitude', loc.latitude); //viewModel.set('longitude', loc.longitude); //marker.position = mapsModule.Position.positionFromLatLng(viewModel.latitude, viewModel.longitude); location_count = locations.length; if (location_count >= 2) { var distance = Math.round(geolocation.distance(locations[location_count - 2], locations[location_count - 1])); // get the distance between the last two locations var steps = Math.round(distance * 1.3123); // determine the approximate number of steps // add the current distance to the overall distance travelled total_distance = total_distance + distance; total_steps = total_steps + steps; // update the UI viewModel.set('distance', "distance travelled: " + total_distance + " meters"); viewModel.set('steps', "steps: " + total_steps); }
At this point, you can now start testing the app using the GPS emulator that I mentioned earlier. Do note that you need to hit save on the main-view-model.js file to trigger an app reload.
Then pick a location in the GPS emulator so that a fresh location will be fetched by the app once it loads. If you don't do this, it will default to the Googleplex location in Mountain View, California. This means that the next time you pick a location on the emulator, it will jump from this location to the location that you picked. If it's far away then you'll get a really large number for the distance and steps.
Alternately, you could test on a real device with internet and GPS enabled. Only GPS is required at this point, but once we add Google Maps, the app will need an internet connection.
Working With Google Maps
We will now use Google Maps to add a map that shows the user's current location.
Installing the Google Maps Plugin
tns plugin add nativescript-google-maps-sdk
Once installed, you need to copy the template string resource files for Android:
cp -r node_modules/nativescript-google-maps-sdk/platforms/android/res/values app/App_Resources/Android/
Next, open the app/App_Resources/Android/values/nativescript_google_maps_api.xml file and add your own Google Maps API key (server key):
<?xml version="1.0" encoding="utf-8"?><resources><string name="nativescript_google_maps_api_key">YOUR GOOGLE MAPS API KEY HERE</string></resources>
Make sure that you have enabled the Google Maps Android API from the Google Console before you try to use it.
Adding the Map
For the map, open the main-page.xml file and you should see the following:
<maps:mapView latitude="{{ latitude }}" longitude="{{ longitude }}" zoom="{{ zoom }}" mapReady="{{ onMapReady }}" />
Here we've specified three options (longitude
, latitude
, and zoom
) and a function to execute once the map is ready. longitude
and latitude
specify the location you want to render in the map. The zoom
specifies the zoom level of the map. mapReady
is where we specify the function for adding the marker on the map. This marker represents the user's current location, so it will be rendered at the center of the map.
By default, this won't work as you haven't added the schema definition for the maps yet. So in your Page
element, add the definition for the maps
element:
<Page xmlns="http://schemas.nativescript.org/tns.xsd" xmlns:maps="nativescript-google-maps-sdk"></Page>
Once that's done, a Google map instance should be rendered right below the button for tracking location. It won't have any maps yet since the latitude
and longitude
haven't been specified yet. To do that, go back to the main-view-model.js file and remove the comments for the lines of code for working with Google Maps:
// default coordinates viewModel.latitude = 15.447819409392789; viewModel.longitude = 120.93888133764267; viewModel.zoom = 20; // default map zoom level var mapView; // variable for storing the current map instance var marker = new mapsModule.Marker(); // variable for storing the marker instance
Adding the Marker
Since we've already declared default coordinates for the marker, we can actually plot a marker once the map is ready:
viewModel.onMapReady = function(args) { mapView = args.object; // get the map view marker.position = mapsModule.Position.positionFromLatLng(viewModel.latitude, viewModel.longitude); // set the marker's position on the map mapView.addMarker(marker); // add the marker to the map }
Next, we need to update the marker position once the user starts tracking their location. You can do that inside the success callback function for the getCurrentLocation()
function:
locations.push(start_location); // remove the comments for these: //viewModel.set('latitude', loc.latitude); //viewModel.set('longitude', loc.longitude); //marker.position = mapsModule.Position.positionFromLatLng(viewModel.latitude, viewModel.longitude);
We also need update it when the user's location is updated (inside the success callback function for watchLocation
):
current_location = loc; locations.push(loc); // remove the comments for these: //viewModel.set('latitude', loc.latitude); //viewModel.set('longitude', loc.longitude); //marker.position = mapsModule.Position.positionFromLatLng(viewModel.latitude, viewModel.longitude);
Once that's done, a map which renders the default location should show in the app.
Conclusion
In this tutorial, you've created a NativeScript app that allows the user to track how much distance they have covered and the approximate number of steps they've taken to cover that distance. You've also used Google Maps to let the user view their current location. By doing so, you've learned how to use the geolocation and Google Maps plugins for NativeScript.
This is just the start! In the next posts of this series, we'll add a local database, push notifications and other cool features to our app.
In the meantime, check out some of our other posts on NativeScript and cross-platform mobile coding.
- Mobile AppCreate a Weather App With TypeScript and NativeScript
- Mobile DevelopmentIntroducing Vue and Weex for Native Mobile Apps
- IonicGet Started With Ionic Services: Auth
For a comprehensive introduction to NativeScript, try our video course Code a Mobile App With NativeScript. In this course, Keyvan Kasaei will show you step by step how to build a simple application. Along the way, you'll learn how to implement a simple app workflow with network requests, an MVVM architecture, and some of the most important NativeScript UI components. By the end, you'll understand why you should consider NativeScript for your next mobile app project.