Getting Started with NativeScript

Warning: This article was last updated on June 23, 2015, and as such, many of the examples are now out of date with newer NativeScript releases. You’re welcome to read through the article, but we recommend developers get started with NativeScript on either our JavaScript tutorial, or our TypeScript & Angular 2 tutorial.

NativeScript is a framework that lets you use JavaScript to build mobile applications utilizing platform specific, native API calls. It supports iOS, Android, and (as of February 2015) Windows Universal.

In this article, you will learn how to build a native mobile app using NativeScript. If you’d like some background on how NativeScript works first, you can read NativeScript – a Technical Overview before getting started.

Assumptions

This article is about the NativeScript library itself, and assumes you have a NativeScript development environment up and running. Specifically, you must

  • have a working IDE with NativeScript configured;
  • know how to create a new NativeScript project;
  • know how to run a NativeScript app in an emulator or deploy to a real device;
  • and (optionally) have the NativeScript companion app installed on a real device.

If you don’t have all of this ready, start by heading over to the NativeScript getting started page to get everything you need set up. If you’re using the NativeScript CLI, the NativeScript documentation has everything you need to get up and running for Windows or OS X or Linux.

Good? Alright, let’s get started.

Baby Steps

The NativeScript framework provides you with different building blocks to structure your project. This is why it is best to approach it in steps, one feature at a time. In this article you’ll learn about the following features:

  • 1) Views
  • 2) Code Behind
  • 3) Binding
  • 4) Observable
  • 5) CSS

If you want to see the final result, you can check the code at any time at https://github.com/sebawita/GettingStarted.

For clarity I added components folder (inside the app folder) where I am going to put all examples.

Here is how your folder structure should look.

Folder Structure Screenshot

Example 1: Hello world (views)

Now you are ready to create your first NativeScript app.

First

Add helloWorldView.xml to the components folder, with the following code:

<Page>
    <Label text="Hello World" />
</Page>

Next

Open app.js and change application.module to components/helloWorldView.

Your app.js code should look as follows:

var application = require("application");
application.mainModule = "components/helloWorldView";
application.cssFile = "./app.css";
application.start();

Finally

Build the app and deploy it to your device or emulator (for instructions see the getting started page, Step 4).

And voila! You have your very first Native app built with NativeScript.

Result

Hello World Screenshot

Important Info:

  • It is not necessary to use “View” in the name, however the convention makes the project’s file structure easier to follow.
  • The <Page> tag can only contain one item. Adding more items to the page will break it. In the next example we’ll examine how to add multiple items.
  • application.module is used to specify the starting page.

Example 2: Sign-up form (code behind)

In this example you are going to add a few UI components. Since the <Page> element can only contain one component you’re going to need a component that can hold and organise other components (a container). We’ll use StackLayout since it’s easy to use.

To explore other containers see the documentation area and Demystifying NativeScript Layouts

First

In app.js change the line that sets the mainModule to:

application.mainModule = "components/signUpView";

Next

Add signUpView.xml to the components folder with the following contents:

<Page loaded="loadSignUpView">
    <StackLayout>
        <Label text="Name:" />
        <TextField id="name" width="200"/>

        <Label text="Email:" />
        <TextField id="email" width="200"/>

        <Label text="Twitter Handle:" />
        <TextField id="twitterHandle" text="@" width="200"/>

        <Button id="signUpButton" text="Sign Up" width="100"/>

        <Label id="result" text=" " textWrap="true"/>
    </StackLayout> 
</Page>

The bit worth noting is loaded="loadSignUpView". loaded is an event that NativeScript triggers each time a page is loaded. The definition of loadSignUpView is located in the JavaScript file (see below).

Finally

Add signUpView.js to the components folder with the following contents:

var view = require("ui/core/view");

function onLoad(args) {
    var page = args.object;

    var nameTextField = view.getViewById(page, "name");
    var emailTextField = view.getViewById(page, "email");
    var twitterHandleTextField = view.getViewById(page, "twitterHandle");
    var signUpButton = view.getViewById(page, "signUpButton");
    var resultLabel = view.getViewById(page, "result");

    signUpButton.on("tap", function () {

        var result = "";

        if(nameTextField.text === "" || emailTextField.text === "" || twitterHandleTextField.text === "")
            result = "Error: All fields required";
        else
            result = "Success!!!\nName: " + nameTextField.text + "\nEmail:" + emailTextField.text + "\nTwitter Handle: " + twitterHandleTextField.text;

        resultLabel.text = result;
    });
}
exports.loadSignUpView = onLoad;

Things to Note:

  • Both the JavaScript and XML files must have the same name to be linked together.
  • The .js file contains the UI logic written in JavaScript.
  • The .xml file contains UI component descriptions.
  • [line 1] The require function loads modules in NativeScript. In this case it loads a view module linked with the current page, which gives you access to UI components on the page.
  • [line 24] The exports variable exposes functions and attributes to the elements on the page. In this case it exposes the loadSignUpView function (which corresponds to line 1 in signUpView.xml).
  • [line 6-10] view.getViewById retrieves a UI component from the page.
  • [line 12] The tap event adds code to be called when the button referenced by signUpButton is tapped.

Result

Sign Up Form Screenshot

Example 3: Journey Calculator (bindings)

Calling an element by its id and manually getting its text is a bad practice, as it ties the business logic of your app to the specific UI components. It also forces you to know the id of each UI component in your JavaScript code. Luckily NativeScript provides a more elegant approach: binding UI properties to attributes of a JavaScript object.

In this example you will create a journey cost calculator. The user will enter distance, miles per gallon and fuel cost, and the app will calculate the cost for the journey.

First

In app.js change the line that sets the mainModule to:

application.mainModule = "components/journeyCalculatorView";

Next

Add journeyCalculatorView.js to the components folder with the following contents:

var view = require("ui/core/view");

var journeyInfo = {
    distance: "24", 
    mpg: "35", 
    fuelCost: "120.5"
};

var page;

exports.loadSignUpView = function(args) {
    page = args.object;

    page.bindingContext = journeyInfo;
}

exports.calculate = function() {
    var distance = parseFloat(journeyInfo.distance);
    var milesPerGallon = parseFloat(journeyInfo.mpg);
    var milesPerLitre = milesPerGallon / 4.54609;

    var fuelCost = parseFloat(journeyInfo.fuelCost) / 100; // cost in £

    var costPerMile = fuelCost / milesPerLitre;
    var journeyCost = distance * costPerMile;

    var resultLabel = view.getViewById(page, "result");
    resultLabel.text = "Journey cost £" + journeyCost.toFixed(2)
                     + "\nCost per mile: £" + costPerMile.toFixed(2);
}

Things to Note:

  • [Lines 3-7] This is the definition of the data model. Its attributes are later bound to the properties of the UI components. In short: this is where your data goes.
  • [Line 14] This line sets the Page’s context to the previously created data object. After this assignment, UI components can bind to the journeyInfo object’s attributes using {{ attribute name }}
  • [Lines 17-30] Notice how data access is done using the journeyInfo object, rather than getting explicit references to UI components and setting their text property.

Finally

Add journeyCalculatorView.xml to the components folder with the following contents:

<Page loaded="loadSignUpView">
    <StackLayout>
        <StackLayout orientation="horizontal">
            <Label text="Distance" width="50"/>
            <TextField text="{{distance}}" width="100"/>
            <Label text="Miles" />
        </StackLayout>
        <StackLayout orientation="horizontal">
            <Label text="MPG"  width="50" />
            <TextField text="{{mpg}}" width="100"/>
            <Label text="MPG" />
        </StackLayout>
        <StackLayout orientation="horizontal">
            <Label text="Fuel Cost" width="50" />
            <TextField text="{{fuelCost}}" width="100"/>
            <Label text="Pence/Liter" />
        </StackLayout>

        <Button tap="calculate" id="calculateButton" text="Calculate" width="100"/>

        <Label id="result" textWrap="true"/>
    </StackLayout>
</Page>

Things to Note:

  • [Lines 5, 10, 15] Binding text values to attributes of the page’s context, or in this case the journeyInfo object, are created using {{ attributeName }}.

Result

Journey Calculator Screenshot

Important Info

  • Binding to a standard JavaScript object gives us direct access to the data input in the UI. However, as this is currently written the binding doesn’t work in reverse. That is, if you were to change attribute values in JavaScript, the UI wouldn’t get updated. To overcome that you need to use an observable object, which we’ll look at in the next example.
  • Bindings can be one-way (read only) or two-way (read/write).
  • By default bindings defined in XML are two-way.
  • You can also create a one-way binding expression —
    e.g. text={{ miles + "miles" }}.

Example 4: Number Game (observables)

The observable object is the missing piece in the binding puzzle. Its main purpose is to notify the UI about any relevant (bound) changes so that the UI can update itself.

In the previous example, if the journeyInfo was an observable object, you wouldn’t need to retrieve the result label to display the result on the page. Instead you would update the journeyInfo result and the UI would display the new value automatically.

In this example you will create a small game. In the game there are 10 numbers (1-10) in a random order. The player is presented with a number, and needs to guess whether the next number is higher or lower. The game needs to display the current card, the score, and the number of cards left. Finally, it must allow users to start a new game. Let’s see how this is built.

First

In app.js change the line that sets the mainModule to:

application.mainModule = "components/upDownView";

Next

Create upDownViewModel.js in the components folder with the following contents:

var observable = require("data/observable");
var upDownViewModel = new observable.Observable();

upDownViewModel.noOfCards = 10;
upDownViewModel.cards = [];

upDownViewModel.currentCardIndex = 0;

upDownViewModel.currentCard = 0;
upDownViewModel.nextCard = 0;

upDownViewModel.score = 0;
upDownViewModel.streak = 0;

upDownViewModel.cardsLeft = 0;


upDownViewModel.startNewGame = function() {
    this.set("score", 0);
    this.set("streak", 0);

    this.shuffleCards();

    this.getFirstCard();
}

upDownViewModel.goHigher = function() {
    if(!this.hasMoves())
        return;

    if(this.currentCard < this.nextCard)
        this.goodGuess();
    else
        this.badGuess();

    this.getNextCard();
}

upDownViewModel.goLower = function() {
    if(!this.hasMoves())
        return;

    if(this.currentCard > this.nextCard)
        this.goodGuess();
    else
        this.badGuess();

    this.getNextCard();
}

upDownViewModel.getFirstCard = function() {
    this.currentCardIndex = -1;
    this.getNextCard();
}

upDownViewModel.getNextCard = function() {
    this.currentCardIndex = this.currentCardIndex +1;
    index = this.currentCardIndex;

    this.set("currentCard", this.cards[index]);
    this.nextCard = this.cards[index+1];

    this.set("cardsLeft", this.noOfCards - index -1);
}

upDownViewModel.hasMoves = function() {
    return this.cardsLeft > 0;
}

upDownViewModel.goodGuess = function() {
    this.set("streak", this.streak + 1);
    this.set("score", this.score + this.streak);
}

upDownViewModel.badGuess = function() {
    this.set("streak", 0);
}

upDownViewModel.shuffleCards = function() {
    var cardDeck = [];
    for(var i=0; i<this.noOfCards; i++) {
        cardDeck.push(i+1);
    }

    var shuffledCards = [];
    while(cardDeck.length > 0) {
        var index = getRandom(cardDeck.length);

        var card = cardDeck.splice(index, 1)[0];

        shuffledCards.push(card);
    }

    this.set("cards", shuffledCards);
}

module.exports = upDownViewModel;

function getRandom (max) {
    return Math.floor(Math.random() * max);
}

Things to Note:

  • [Line 1] – Loads observable module.
  • [Line 2] – Creates an instance of an observable object.
  • [Lines 4-15] – Adds attributes to the observable object including those displayed in the UI:
    • [Line 9] – Current card
    • [Line 12] – Score
    • [Line 15] – Cards left
  • [Lines 18-49] – The definition of the startNewGame, goHigher and goLower functions.
  • [Line 97] – Exposes the observable object as a module, which can be required from another js file.
  • [Line 19] – set the score to 0 and notifies the UI that the score value is 0.

The above code is self-contained and is completely separated from the UI. Its sole purpose is to provide the game logic.

Next

Create upDownView.js in the components folder with the following contents:

var view = require("ui/core/view");
var model = require("./upDownViewModel");

exports.loadUpDownView = function(args) {
    var page = args.object;
    page.bindingContext = model;
}

Things to Note:

  • [Line 2] – Gets the observable object defined in the upDownViewModel.js file.
  • [Line 6] – Sets the observable object as the page’s context.

Finally

Create upDownView.xml with the following contents:

<Page loaded="loadUpDownView">
    <StackLayout>
        <Button tap="{{startNewGame}}" text="New Game"/>

        <Label text="{{'Cards Left: ' + cardsLeft}}" />
        <Label text="{{'Score (Streak):' + score + ' ('+ streak +')  '}}" />

        <StackLayout orientation="horizontal" horizontalAlignment="center">
            <Button tap="{{goLower}}" text="Lower" />
            <Label text="{{currentCard}}" />
            <Button tap="{{goHigher}}" text="Higher" />
        </StackLayout>
    </StackLayout>
</Page>

Things to Note:

  • {{ xyz }} is used to bind events and attributes of the UI to those exposed by the observable object (ViewModel) from upDownViewModel.js:
    • [Lines 3,9,11] – button tap event bindings
    • [Line 5,6,10] – labels text bindings

Result

Game Screenshot

Important Info:

  • Your application can be made of multiple pages. Each page can have their own observable object, observable objects can be shared across pages.
  • In the observable object, you can set the value of any attribute in two ways:
    • set("attributeName", value) sets the value of the attributeName attribute and notifies all bound properties in the UI. This should be used on attributes that are expected to be bound in the UI (like score, currentCard and cardsLeft).
    • Calling “=” (e.g. myObject.attributeName = value) sets the value of the attribute, but it doesn’t notify the UI. This should be used on attributes that are not meant to be displayed in the UI (like cards, nextCard and currentCardIndex).
  • The above example follows an MVVM architectural pattern, where the UI (View) and the application logic (ViewModel) are linked via set of bindings, resulting in a tidy separation of what you see versus what you do.
  • To read more about bindings go to the NativeScript documentation page about Data Binding.

Example 5: CSS

The game from the previous example works, but the UI isn’t all that attractive. Now is a good time to introduce NativeScript CSS styling and to make the game look better.

First

Add upDownViewWithStyle.css to the components folder, with the following code:

.info  {
    font-size: 30;
    color: #2E4F8A;
}

.infoLabel  {
    font-size: 22;
    color: #99ABD6;
}

.card {
    color: #D63326;
    font-size: 100;
    width: 100;
}

Button {
    vertical-align: center;
    background-color: #037AC4;
    color: #8CE8FC;
    font-size: 30;
}

Things to Note:

  • [Lines 1-15] – creates 3 css class names
  • [Line 17-22] – defines the default properties for all buttons

Next

In app.js change the line that sets the mainModule to:

application.mainModule = "components/upDownViewWithStyle";

Create upDownViewWithStyle.js in the components folder with the following contents:

var view = require("ui/core/view");
var viewModel = require("./upDownViewModel");

exports.loadUpDownView = function(args) {
    var page = args.object;
    page.bindingContext = viewModel;
}

Finally

Create upDownViewWithStyle.xml with the following code:

<Page loaded="loadUpDownView">
    <StackLayout>
        <Button tap="{{startNewGame}}" text="New Game" horizontalAlignment="center"  cssClass="actionButton"/>

        <GridLayout rows="auto">
            <Label text="  Cards Left:" cssClass="infoLabel" horizontalAlignment="left" />
            <Label text="Score (Streak):  " cssClass="infoLabel" horizontalAlignment="right" />
        </GridLayout>
        <GridLayout rows="auto">
            <Label text="{{'  ' + cardsLeft}}" cssClass="info" horizontalAlignment="left" />
            <Label text="{{score + ' ('+ streak +')  '}}" cssClass="info" horizontalAlignment="right" />
        </GridLayout>

        <StackLayout orientation="horizontal" horizontalAlignment="center">
            <Button tap="{{goLower}}" text="Lower" cssClass="actionButton" />
            <Label text="{{currentCard}}" cssClass="card" />
            <Button tap="{{goHigher}}" text="Higher" cssClass="actionButton" />
        </StackLayout>
    </StackLayout>
</Page>

Things to Note:

  • [Lines 6,7,10,11,16] – each label is assigned a cssClass that corresponds to a class name defined in upDownViewWithStyle.css.
  • [Lines 3,15,17] – The Button elements aren’t given a cssClass attribute because their styling is defined by the Button selector in upDownViewWithStyle.css.

Result

Game with CSS Screenshot

Important Info:

  • There are few ways to add styling to your app:
    • Global style: use app.css to add styling that will affect all views.
    • View style: use view-name.css to add styling to the specific view-name.js view (i.e. helloWorldView.js -> helloWorldView.css).
    • Component style: add in-line styling to a component.
  • The code in upDownViewWithStyle.css follows the CSS structures and naming conventions. However only a small subset of CSS properties are available for use at the moment: font-size, color, background-color, etc.
  • There was no need to change upDownViewModel for this example, as the styling of the UI is separate from the logic of the game.
  • The list of the supported styles is still growing, however some styles that exist in HTML may not possible to implement in native mobile apps.
  • Although this is not advised, You could copy and paste a CSS file from an existing web project. All supported styles will affect the UI, while the unsupported styles will get ignored.

Summary

Now you should have a working example of a NativeScript app with bindings and CSS styling. Hopefully these examples have prepared you to build your own NativeScript apps.

There are other things that you will need to know in order to build a successful app such as page navigation, adding 3rd-party native libraries, and adding platform-specific code. Don’t worry – we’ll be covering those topics (and more) in the near future.

For more information on NativeScript check out the docs.

Header image courtesy of Opassande

Comments