NativeScript

Open source framework for building truly native mobile apps
with JavaScript, TypeScript or Angular.

ShoutOutPlay

What is {N} capable of?

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

Use lots of prebuilt code

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

What the heck are CocoaPods?

Is Android Arsenal an Area 51 Project?

...Like npm for native iOS and Android code.

Ok, enough background.

 

How do I code views in NativeScript?

Native Layout Containers

Absolute

Dock

Grid

Stack

Wrap

Like '<div>' - more concise though

<Page xmlns="http://schemas.nativescript.org/tns.xsd">

  <StackLayout class="p-20">
    <Label text="Tap the button" class="h1 text-center"/>
    <Button text="TAP" tap="{{ onTap }}" class="btn btn-primary"/>
  </StackLayout>

</Page>

// DOM comparison...

<html>
<body>

  <div class="p-20">
    <h1 class="h1 text-center">Tap the button</h1>
    <button type="button" class="btn btn-primary" 
        (tap)="onTap()">TAP</button>
  </div>

</body>
</html>

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

Nathan Walker

@wwwalkerrun

{N} Slack is best though

NativeScript Introduction

By Nathan Walker

NativeScript Introduction

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

  • 3,346