1. Code
  2. JavaScript

Code a Real-Time NativeScript App: SQLite

Scroll to top
This post is part of a series called Code a Real-Time NativeScript App.
Code a Real-Time NativeScript App: Geolocation and Google Maps
Code a Real-Time NativeScript App: Social Login and Firebase

NativeScript is a framework for building cross-platform native mobile apps using XML, CSS, and JavaScript. In this series, we're trying 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're building a fitness app with real-time capabilities that will use each of these features.

In this tutorial, you'll learn how to integrate a SQLite database into the app to store data locally. Specifically, we'll be storing the walking sessions data that we gathered in the previous tutorial.

What You'll Be Creating

Picking up from the previous tutorial, you'll be adding a tab view for displaying the different portions of the app. Previously our app just had the Tracking page, so we didn't need tabs. In this post, we'll be adding the Walks page. This page will display the user's walking sessions. A new data point will be added here every time the user tracks their walking session. There will also be a function for clearing the data.

Here's what the final output will look like:

SQL Lite Final OutputSQL Lite Final OutputSQL Lite Final Output

Setting Up the Project

If you have followed the previous tutorial on geolocation, you can simply use the same project and build the features that we will be adding in this tutorial. Otherwise, you can create a new project and copy the starter files into your project's app folder.

1
tns create fitApp --appid "com.yourname.fitApp"

After that, you also need to install the geolocation and Google Maps plugins:

1
tns plugin add nativescript-geolocation
2
tns plugin add nativescript-google-maps-sdk

Once installed, you need to configure the Google Maps plugin. You can read the complete instructions on how to do this by reading the section on Installing the Google Maps Plugin in the previous tutorial.

Once all of those are done, you should be ready to follow along with this tutorial.

Running the Project

You can run the project by executing tns run android. But since this app will build on the geolocation functionality, I recommend you use a GPS emulator for quickly setting and changing your location. You can read about how to do so in the section on Running the App in the previous tutorial

Installing the SQLite Plugin

The first thing that you need to do to start working with SQLite is to install the plugin:

1
tns plugin add nativescript-sqlite

This allows you to do things like connecting to a database and doing CRUD (create, read, update, delete) operations on it.

Connecting to the Database

Open the main-page.js file and import the SQLite plugin:

1
var Sqlite = require("nativescript-sqlite");

You can now connect to the database:

1
var db_name = "walks.db";
2
3
new Sqlite(db_name).then(db => {
4
    // next: create table for storing walks data

5
}, error => {
6
    
7
});

The walks.db file was created from the terminal using the touch command, so it's just an empty file. Copy it into the app folder.

If it successfully connected, the promise's resolve function will be executed. Inside that, we run the SQL statement for creating the walks table. To keep things simple, all we need to save is the total distance covered (in meters) and the total steps, as well as the start and end timestamps. 

1
db.execSQL("CREATE TABLE IF NOT EXISTS walks (id INTEGER PRIMARY KEY AUTOINCREMENT, total_distance INTEGER, total_steps INTEGER, start_datetime DATETIME, end_datetime DATETIME)").then(id => {
2
    page.bindingContext = createViewModel(db);
3
}, error => {
4
    console.log("CREATE TABLE ERROR", error);
5
});

Once the query executes successfully, we pass the database instance (db) into the page context. This will allow us to use it from the main-view-model.js file later on.

Fetching Data

Now we're ready to work with the data. But since we'll be working with dates, we first need to install a library called fecha. This allows us to easily parse and format dates:

1
npm install --save fecha

Once it's installed, open the main-view-model.js file and include the library:

1
var fecha = require('fecha');

Next is the code for checking if geolocation is enabled. First, create a variable (walk_id) for storing the ID of a walking record. We need this because the app will immediately insert a new walk record into the walks table when the user starts location tracking. walk_id will store the ID that's auto-generated by SQLite so that we'll be able to update the record once the user stops tracking.

1
var walk_id;

Next, get the current month and year. We'll use it to query the table so it only returns records that are in the same month and year. This allows us to limit the number of records that appear in the UI.

1
var month = fecha.format(new Date(), 'MM'); //e.g 07

2
var year = fecha.format(new Date(), 'YYYY'); //e.g 2017

We also need a variable for storing the start timestamp. We'll use it later on to update the UI. This is because we're only querying the table once when the app is loaded, so we need to manually update the UI of any new data which becomes available. And since the starting timestamp will only have a value when the user starts tracking, we need to initialize it outside the scope so we can update or access its value later on.

1
var st_datetime; // start datetime

Initialize the walks data that will be displayed in the UI:

1
var walks = [];
2
viewModel.walks = [];
3
viewModel.has_walks = false; 

Get the data from the walks table using the all() method. Here, we're supplying the month and the year as query parameters. The strftime() function is used to extract the month and year part of the start_datetime

1
db.all(
2
    "SELECT * FROM walks WHERE strftime('%m', start_datetime) == ? AND strftime('%Y', start_datetime) == ? ORDER BY start_datetime DESC", 
3
    [month, year]
4
).then((err, rs) => {
5
    if(!err){
6
        // next: update the UI with the walks data

7
8
    }  
9
});

Once a success response is returned, we loop through the result set so that we can format the data correctly. Note that the indexes in which we access the individual values depend on the table structure that was described earlier in the main-page.js file. The first column is ID, the second is the total distance, and so on.

The formatted data is then pushed to the walks array and is used to update the UI. has_walks is used as a toggle for the UI so that we can show or hide things based on its value.

1
rs.forEach((w) => {
2
    let start_datetime = new Date(w[3]);
3
    let end_datetime = new Date(w[4]);
4
    
5
    walks.push({
6
        start: fecha.format(start_datetime, 'MMM D, h:mm'), // e.g Jun 5, 5:30

7
        end: fecha.format(end_datetime, 'h:mm a'), // e.g 6:30 pm

8
        distance: commafy(w[1]) + 'm', // e.g 2,000m

9
        steps: commafy(w[2]) // e.g 2,300

10
    });
11
12
});
13
14
if(walks.length){
15
    viewModel.set('has_walks', true);
16
}
17
viewModel.set('walks', walks);

This will supply the data for the ListView in the main-page.xml file:

1
<ListView items="{{ walks }}">   
2
    <ListView.itemTemplate>
3
        <GridLayout columns="2*,*,*" rows="auto" class="item item-row">
4
            <Label text="{{ start + ' - ' + end }}" textWrap="true" row="0" col="0"/>
5
            <Label text="{{ distance }}" textWrap="true" row="0" col="1" />
6
            <Label text="{{ steps }}" textWrap="true" row="0" col="2" />
7
        </GridLayout>
8
    </ListView.itemTemplate>
9
</ListView>

Saving Data

Once the user starts tracking, set the current datetime as the start_datetime and insert initial values into the table using the execSQL() function. Just like the all() function, this expects the SQL query as the first argument and an array of parameters as the second.

If the query is successful, it should return the auto-generated ID for the inserted record. We then assign it as the value for the walk_id so it can be used later on to update this specific record.

1
st_datetime = new Date();
2
var start_datetime = fecha.format(st_datetime, 'YYYY-MM-DD HH:mm:ss');
3
4
db.execSQL(
5
    "INSERT INTO walks (total_distance, total_steps, start_datetime) VALUES (?, ?, ?)", 
6
    [0, 0, start_datetime]
7
).then((id) => {
8
    walk_id = id; 
9
}, (e) => {
10
    dialogs.alert(e.message);
11
});

Once the user stops tracking, we again get the current timestamp and format it accordingly for storage:

1
var ed_datetime = new Date();
2
var end_datetime = fecha.format(ed_datetime, 'YYYY-MM-DD HH:mm:ss');

Since we've ordered the results from most to least recent, we use unshift() (instead of push()) to add the new item to the top of the walks array.

1
walks.unshift({
2
    start: fecha.format(st_datetime, 'MMM D, h:mm'),
3
    end: fecha.format(ed_datetime, 'h:mm a'),
4
    distance: commafy(total_distance) + 'm',
5
    steps: commafy(total_steps)
6
});
7
8
viewModel.set('walks', walks);
9
if(walks.length > 0){
10
    viewModel.set('has_walks', true);
11
}

After that, we once again we use the execSQL() function to update the record that we inserted earlier:

1
db.execSQL(
2
    "UPDATE walks SET end_datetime = ?, total_steps = ?, total_distance = ? WHERE id = ?", 
3
    [end_datetime, total_steps, total_distance, walk_id]
4
).then(
5
    (err, id) => {
6
        if(!err){
7
           // todo: add code for resetting the tracking UI here

8
        }
9
    }
10
);

Be sure to move the code for resetting the tracking UI (to reset the total distance and steps) inside the promise's resolve function so you can easily test whether the update query executed successfully or not. 

Clearing Data

Deleting data is done by clicking on the Clear Data button below the list of walk data:

1
<ListView items="{{ walks }}"> 
2
    ...
3
</ListView>
4
<Button text="Clear Data" tap="{{ clearData }}" class="bg-danger" />

In the main-view-model.js file, add the code for deleting all the data from the walks table. If you're used to MySQL, you might be wondering why we're using the DELETE query instead of TRUNCATE for emptying the table. Well, that's because SQLite doesn't have the TRUNCATE function. That's why we have to use the DELETE query without supplying a condition so that it will delete all the records that are currently in the table. 

1
viewModel.clearData = function() {
2
3
    dialogs.confirm("Are you sure you want to clear your data?").then((agree) => {
4
        
5
        if(agree){
6
            db.execSQL("DELETE FROM walks", []).then(
7
                (err) => {
8
                    if(!err){
9
                        dialogs.alert("Data has been cleared!");
10
                        walks = [];
11
                        viewModel.set('walks', []);
12
                        viewModel.set('has_walks', false);
13
                    }
14
                }
15
            ); 
16
        }
17
    });
18
    
19
}

Conclusion

In this tutorial, you've learned how to locally store data in your NativeScript apps using the SQLite plugin. As you have seen, SQLite allows you to reuse your existing SQL skills in managing a local database. It's important to note that not all functions that you're used to in MySQL are supported in SQLite. So it's always wise to consult the documentation if you're not sure whether a certain function is supported or not. 

If you want to learn about other options for storing data in NativeScript apps, I recommend you read this article on Going Offline With NativeScript.

In the final post of this series, we'll add push notifications to our app.

In the meantime, check out some of our other posts on NativeScript and cross-platform mobile coding.

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.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.