NativeScript

Open source platform to build Native iOS/Android Apps 

What is NativeScript?

A runtime for building and running *native* iOS and Android apps from a single, JavaScript codebase.

Not PhoneGap or Ionic

  • Real Native Components
  • No DOM to manipulate
  • Not HTML elements styled like native components

Not Xamarin

  • No Cross Compiling
  • 100% access to native APIs without writing bindings
  • No .NET

Not ReactNative

  • No writing ObjectiveC, Swift or Java
  • {N} JavaScript has 100% access to native APIs

So what's the cost?

  • FREE and Open Source

Created and supported by...

Architecture choices...

JavaScript

Write your app

using vanilla JS.

TypeScript

Use TS to get Object Oriented features and compile time error checking.

Angular

Use Angular to architect your app.

Reuse all code between web and mobile.

Cross Platform

iOS, Android, Windows (2016)

Use lots of prebuilt code

  • Thousands of npm plugins
  • Free native controls from CocoaPods and Android Arsenal

NativeScript + Angular2

Ok, enough background.

 

Let's get down to how it works!

Native Layouts

Absolute

Dock

Grid

Stack

Wrap

Native API Access

Android Example


var time = new android.text.format.Time();

time.set( 26, 3, 2016 );

console.log( time.format( "%D" ) );
"04/26/16"

iOS Example


var alert = new UIAlertView();

alert.message = "Hello world!";

alert.addButtonWithTitle( "OK" );

alert.show();

But how does it work?!

Calling Android API...

But I don't want to write iOS and Android code :(

NativeScript Modules

file-system module

// CommonJS

var fileSystem = require( "file-system" );

var file = new fileSystem.File( path );

// ES6

import { File } from 'file-system';

let file = new File( path );
let file = new java.io.File( path );
let fileManager = NSFileManager.defaultManager();
fileManager.createFileAtPathContentsAttributes( path );

http module

// CommonJS

var http = require( "http" );

// ES6

import * as http from 'http';

http.getJSON( "https://api.myservice.com )
    .then((result) => {
        
        // result is JSON Object
 
    });
import image = require("image-source");
import httpRequest = require("http/http-request");
import dts = require("http");

global.moduleMerge(httpRequest, exports);

export function getString(arg: any): Promise<string> {
    return new Promise<string>((resolve, reject) => {
        httpRequest.request(typeof arg === "string" ? { url: arg, method: "GET" } : arg)
            .then(r => {
            try {
                var str = r.content.toString();
                resolve(str);
            } catch (e) {
                reject(e);
            }
        }, e => reject(e));
    });
}

export function getJSON<T>(arg: any): Promise<T> {
    return new Promise<T>((resolve, reject) => {
        httpRequest.request(typeof arg === "string" ? { url: arg, method: "GET" } : arg)
            .then(r => {
            try {
                var json = r.content.toJSON();
                resolve(json);
            } catch (e) {
                reject(e);
            }
        }, e => reject(e));
    });
}

export function getImage(arg: any): Promise<image.ImageSource> {
    return new Promise<image.ImageSource>((resolve, reject) => {
        httpRequest.request(typeof arg === "string" ? { url: arg, method: "GET" } : arg)
            .then(r => {
            r.content.toImage().then(source => resolve(source), e => reject(e));
        }, e => reject(e));
    });
}

export function getFile(arg: any, destinationFilePath?: string): Promise<any> {
    return new Promise<any>((resolve, reject) => {
        httpRequest.request(typeof arg === "string" ? { url: arg, method: "GET" } : arg)
            .then(r => {
                try {
                    var file = r.content.toFile(destinationFilePath);
                    resolve(file);
                } catch (e) {
                    reject(e);
                }
            }, e => reject(e));
    });
}

export function addHeader(headers: dts.Headers, key: string, value: string): void{
    if(!headers[key]) {
        headers[key] = value;
    } else if (Array.isArray(headers[key])){
        (<string[]>headers[key]).push(value);
    } else {
        let values: string[] = [<string>headers[key]];
        values.push(value);
        headers[key] = values;
    }
}
/**
 * Android specific http request implementation.
 */
import types = require("utils/types");
import * as utilsModule from "utils/utils";
import * as imageSourceModule from "image-source";
import * as platformModule from "platform";
import * as fsModule from "file-system";

// this is imported for definition purposes only
import http = require("http");

var requestIdCounter = 0;
var pendingRequests = {};

var utils: typeof utilsModule;
function ensureUtils() {
    if (!utils) {
        utils = require("utils/utils");
    }
}

var imageSource: typeof imageSourceModule;
function ensureImageSource() {
    if (!imageSource) {
        imageSource = require("image-source");
    }
}

var platform: typeof platformModule;
function ensurePlatform() {
    if (!platform) {
        platform = require("platform");
    }
}

var completeCallback: com.tns.Async.CompleteCallback;
function ensureCompleteCallback() {
    if (completeCallback) {
        return;
    }

    completeCallback = new com.tns.Async.CompleteCallback({
        onComplete: function (result: any, context: any) {
            // as a context we will receive the id of the request
            onRequestComplete(context, result);
        }
    });
}

function onRequestComplete(requestId: number, result: com.tns.Async.Http.RequestResult) {
    var callbacks = pendingRequests[requestId];
    delete pendingRequests[requestId];

    if (result.error) {
        callbacks.rejectCallback(new Error(result.error.toString()));
        return;
    }

    // read the headers
    var headers: http.Headers = {};
    if (result.headers) {
        var jHeaders = result.headers;
        var length = jHeaders.size();
        var i;
        var pair: com.tns.Async.Http.KeyValuePair;
        for (i = 0; i < length; i++) {
            pair = jHeaders.get(i);
            
            (<any>http).addHeader(headers, pair.key, pair.value);
        }
    }

    callbacks.resolveCallback({
        content: {
            raw: result.raw,
            toString: () => {
                if (types.isString(result.responseAsString)) {
                    return result.responseAsString;
                } else {
                    throw new Error("Response content may not be converted to string");
                }
            },
            toJSON: () => {
                ensureUtils();
                return utils.parseJSON(result.responseAsString);
            },
            toImage: () => {
                ensureImageSource();

                return new Promise<any>((resolveImage, rejectImage) => {
                    if (result.responseAsImage != null) {
                        resolveImage(imageSource.fromNativeSource(result.responseAsImage));
                    }
                    else {
                        rejectImage(new Error("Response content may not be converted to an Image"));
                    }
                });
            },
            toFile: (destinationFilePath?: string) => {
                var fs: typeof fsModule = require("file-system");
                var fileName = callbacks.url;
                if (!destinationFilePath) {
                    destinationFilePath = fs.path.join(fs.knownFolders.documents().path, fileName.substring(fileName.lastIndexOf('/') + 1));
                }
                var stream: java.io.FileOutputStream;
                try {
                    var javaFile = new java.io.File(destinationFilePath);
                    stream = new java.io.FileOutputStream(javaFile);
                    stream.write(result.raw.toByteArray());
                    return fs.File.fromPath(destinationFilePath);
                }
                catch (exception) {
                    throw new Error(`Cannot save file with path: ${destinationFilePath}.`);
                }
                finally {
                    if (stream) {
                        stream.close();
                    }
                }
            }
        },
        statusCode: result.statusCode,
        headers: headers
    });
}

function buildJavaOptions(options: http.HttpRequestOptions) {
    if (!types.isString(options.url)) {
        throw new Error("Http request must provide a valid url.");
    }

    var javaOptions = new com.tns.Async.Http.RequestOptions();

    javaOptions.url = options.url;

    if (types.isString(options.method)) {
        javaOptions.method = options.method;
    }
    if (types.isString(options.content) || options.content instanceof FormData) {
        javaOptions.content = options.content.toString();
    }
    if (types.isNumber(options.timeout)) {
        javaOptions.timeout = options.timeout;
    }

    if (options.headers) {
        var arrayList = new java.util.ArrayList<com.tns.Async.Http.KeyValuePair>();
        var pair = com.tns.Async.Http.KeyValuePair;

        for (var key in options.headers) {
            arrayList.add(new pair(key, options.headers[key] + ""));
        }

        javaOptions.headers = arrayList;
    }

    ensurePlatform();

    // pass the maximum available image size to the request options in case we need a bitmap conversion
    var screen = platform.screen.mainScreen;
    javaOptions.screenWidth = screen.widthPixels;
    javaOptions.screenHeight = screen.heightPixels;

    return javaOptions;
}

export function request(options: http.HttpRequestOptions): Promise<http.HttpResponse> {
    if (!types.isDefined(options)) {
        // TODO: Shouldn't we throw an error here - defensive programming
        return;
    }
    return new Promise<http.HttpResponse>((resolve, reject) => {

        try {
            // initialize the options
            var javaOptions = buildJavaOptions(options);

            // remember the callbacks so that we can use them when the CompleteCallback is called
            var callbacks = {
                url: options.url,
                resolveCallback: resolve,
                rejectCallback: reject
            };
            pendingRequests[requestIdCounter] = callbacks;

            ensureCompleteCallback();
            //make the actual async call
            com.tns.Async.Http.MakeRequest(javaOptions, completeCallback, new java.lang.Integer(requestIdCounter));

            // increment the id counter
            requestIdCounter++;
        } catch (ex) {
            reject(ex);
        }
    });
}
/**
 * iOS specific http request implementation.
 */

import http = require("http");

import * as types from "utils/types";
import * as imageSourceModule from "image-source";
import * as utilsModule from "utils/utils";
import * as fsModule from "file-system";

import domainDebugger = require("./../debugger/debugger");

var GET = "GET";
var USER_AGENT_HEADER = "User-Agent";
var USER_AGENT = "Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25";
var sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration();
var queue = NSOperationQueue.mainQueue();
var session = NSURLSession.sessionWithConfigurationDelegateDelegateQueue(sessionConfig, null, queue);

var utils: typeof utilsModule;
function ensureUtils() {
    if (!utils) {
        utils = require("utils/utils");
    }
}

var imageSource: typeof imageSourceModule;
function ensureImageSource() {
    if (!imageSource) {
        imageSource = require("image-source");
    }
}

export function request(options: http.HttpRequestOptions): Promise<http.HttpResponse> {
    return new Promise<http.HttpResponse>((resolve, reject) => {

        try {
            var debugRequest = domainDebugger.network && domainDebugger.network.create();

            var urlRequest = NSMutableURLRequest.requestWithURL(
                NSURL.URLWithString(options.url));

            urlRequest.HTTPMethod = types.isDefined(options.method) ? options.method : GET;

            urlRequest.setValueForHTTPHeaderField(USER_AGENT, USER_AGENT_HEADER);

            if (options.headers) {
                for (var header in options.headers) {
                    urlRequest.setValueForHTTPHeaderField(options.headers[header] + "", header);
                }
            }

            if (types.isString(options.content) || options.content instanceof FormData) {
                urlRequest.HTTPBody = NSString.alloc().initWithString(options.content.toString()).dataUsingEncoding(4);
            }

            if (types.isNumber(options.timeout)) {
                urlRequest.timeoutInterval = options.timeout / 1000;
            }

            var dataTask = session.dataTaskWithRequestCompletionHandler(urlRequest,
                function (data: NSData, response: NSHTTPURLResponse, error: NSError) {
                    if (error) {
                        reject(new Error(error.localizedDescription));
                    } else {
                        var headers: http.Headers = {};
                        if (response && response.allHeaderFields) {
                            var headerFields = response.allHeaderFields;
                            
                            headerFields.enumerateKeysAndObjectsUsingBlock((key, value, stop) => {
                                (<any>http).addHeader(headers, key, value);
                            });
                        }
                        
                        if (debugRequest) {
                            debugRequest.mimeType = response.MIMEType;
                            debugRequest.data = data;
                            var debugResponse = {
                                url: options.url,
                                status: response.statusCode,
                                statusText: NSHTTPURLResponse.localizedStringForStatusCode(response.statusCode),
                                headers: headers,
                                mimeType: response.MIMEType,
                                fromDiskCache: false
                            }
                            debugRequest.responseReceived(debugResponse);
                            debugRequest.loadingFinished();
                        }

                        resolve({
                            content: {
                                raw: data,
                                toString: () => { return NSDataToString(data); },
                                toJSON: () => {
                                    ensureUtils();
                                    return utils.parseJSON(NSDataToString(data));
                                },
                                toImage: () => {
                                    ensureImageSource();
                                    if (UIImage.imageWithData["async"]) {
                                        return UIImage.imageWithData["async"](UIImage, [data])
                                                      .then(image => {
                                                          if (!image) {
                                                              throw new Error("Response content may not be converted to an Image");
                                                          }
                                    
                                                          var source = new imageSource.ImageSource();
                                                          source.setNativeSource(image);
                                                          return source;
                                                      });
                                    }
   
                                    return new Promise<any>((resolveImage, rejectImage) => {
                                        var img = imageSource.fromData(data);
                                        if (img instanceof imageSource.ImageSource) {
                                            resolveImage(img);
                                        } else {
                                            rejectImage(new Error("Response content may not be converted to an Image"));
                                        }

                                    });
                                },
                                toFile: (destinationFilePath?: string) => {
                                    var fs: typeof fsModule = require("file-system");
                                    var fileName = options.url;
                                    if (!destinationFilePath) {
                                        destinationFilePath = fs.path.join(fs.knownFolders.documents().path, fileName.substring(fileName.lastIndexOf('/') + 1));
                                    }
                                    if (data instanceof NSData) {
                                        data.writeToFileAtomically(destinationFilePath, true);
                                        return fs.File.fromPath(destinationFilePath);
                                    } else {
                                        reject(new Error(`Cannot save file with path: ${destinationFilePath}.`));
                                    }
                                }
                            },
                            statusCode: response.statusCode,
                            headers: headers
                        });
                    }
                });

            if(options.url && debugRequest) {
                var request = {
                    url: options.url,
                    method: "GET",
                    headers: options.headers
                };
                debugRequest.requestWillBeSent(request);
            }

            dataTask.resume();
        } catch (ex) {
            reject(ex);
        }
    });
}

function NSDataToString(data: any): string {
    return NSString.alloc().initWithDataEncoding(data, 4).toString();
}

CSS Styling

ActionBar {
  background-color: #000;
  color:white;
}

.logo-text {
  color:purple;
  horizontal-align:center;
  font-size:30;
}

.fa {
  font-family: FontAwesome;
  font-size:80;
  horizontal-align:center;
}

.instruction {
  font-size:35;
  font-style: italic;
  horizontal-align:center;
  color:#555;
  margin:8;
}
<Page xmlns="http://schemas.nativescript.org/tns.xsd">
  <!--navigatingTo="navigatingTo"-->
  <Page.actionBar>
    <ActionBar title="CSS Styling"/>
  </Page.actionBar>
  <StackLayout padding="10">
    <Label text="Logo text" margin="10" class="logo-text"/>
    <Label text="\uf09b" margin="10" class="fa"/>
    <Label text="Instruction text" margin="10" class="instruction"/>
  </StackLayout>
</Page>

NativeScript

NativeScript Introduction

By Telerik DevRel

NativeScript Introduction

An introduction to NativeScript, an open source platform to build Native iOS/Android Apps.

  • 2,412