Native Apps with Tabris.js

Tim Bond

Developer Week Austin - November 6th, 2019

Truly Native

Web Views

100%

JS ➡️ Bridge ➡️ Native Code

J2V8

JavaScriptCore

A Word on ES6

  • Android: Based on V8
    • Notable exception: async/await
  • iOS: Most features work except on iOS <= 9
  • See docs for full list
  • Best choice: TypeScript, Babel or your favorite transpiler

Build it your way

  • ES5 or ES6
  • TypeScript
  • JSX
  • MVC/MVP/MVVM/MC
  • Any directory structure you like
  • Use Node modules

W3C APIs

  • console
  • Timer
  • XMLHttpRequest
  • Fetch
  • WebSockets
  • Worker
  • Canvas
  • Persistent Storage (localStorage)
  • Random Source (Crypto)

And More, Not W3 Based

  • fs (filesystem)
  • InactivityTimer
  • printer
  • Basic device info
    • Model, OS version, screen size, more

Node packages

>1.1 million packages

>4,500 packages

Develop, Test, Repeat

  • Write code in your favorite editor
  • If transpiling, wait for it to finish
  • Device automatically reloads with new code

Let's Get Started

Let's Get Started

Let's Get Started

Tabris.js

playground.tabris.com

import { contentView, Button, TextView } from 'tabris';

let button = new Button({
  centerX: 0, top: 100,
  text: 'Show Message',
}).appendTo(contentView);

let textView = new TextView({
  centerX: 0, top: [button, 50],
  font: '24px',
}).appendTo(contentView);

button.on({select: () => textView.text = 'Tabris.js rocks!'});
import { contentView, TextView, Button,
  Constraint } from 'tabris';

contentView.append(
  <$>
      <Button center onSelect={showText}>Tap here</Button>
      <TextView centerX padding={16}
        bottom={Constraint.prev} font='24px'/>
  </$>
);

function showText() {
  $(TextView).only().text = 'Tabris.js rocks!';
}

Install Prerequisites

  1. NPM
  2. That's it!

No SDKs, no special editors

npm install -g tabris-cli
tabris init
npm start
.
├── cordova
│   └── config.xml
├── package.json
├── package-lock.json
├── README.md
├── src
│   ├── App.tsx
│   └── index.ts
├── tsconfig.json
└── tslint.json
{
    "main": "dist",
    "private": true,
    "scripts": {
        "test": "npm run build && npm run lint",
        "lint": "tslint --project . -t verbose",
        "build": "tsc -p .",
        "watch": "tsc -p . -w --preserveWatchOutput --inlineSourceMap",
        "start": "tabris serve -a -w"
    },
    "dependencies": {
        "tabris": "~3.2.1"
    },
    "devDependencies": {
        "tslint": "^5.20.1",
        "typescript": "~3.3.4000"
    }
}

cordova/config.xml

<?xml version='1.0' encoding='utf-8'?>
<widget id="com.example.myfirstapp" version="0.1.0">
  <name>My First App</name>
  <description>
    Example Tabris.js App
  </description>
  <author email="john@example.org">
    John Smith
  </author>
  <preference name="EnableDeveloperConsole" value="$IS_DEBUG" />
</widget>

npm start

Public WiFi?  Might need a tunnel

ngrok http 8080

Simple app - Taco Shaker

NavigationView, Page

}

NavigationView

Drawer/back button

{

Page

tabris.contentView

Actions

Page Title

import { ActivityIndicator, NavigationView, Page,
        ScrollView, TextView, contentView } from 'tabris';
import { TacoResponse, getRandomTaco } from './connectors/taco';

let navigationView = new NavigationView({
    left: 0, top: 0, right: 0, bottom: 0
}).appendTo(contentView);

let page = new Page({
    title: 'Taco Shaker'
}).appendTo(navigationView);

let scrollView = new ScrollView({
    left: 0, right: 0, top: 0, bottom: 0
}).appendTo(page);
new TextView({
    top: 0, left: 16, right: 16,
    id: 'title',
    font: 'bold 28px'
}).appendTo(scrollView);

new TextView({
    top: ['prev()', 16], left: 16, right: 16,
    text: 'Base Layer',
    font: 'italic 26px thin sans-serif'
}).appendTo(scrollView);

4 more of these for the other recipe "components"

export function getRandomTaco(): Promise<TacoResponse> {
    return new Promise((resolve, reject) => {
        fetchWithBackoff(URL).then((response: any) => {
            if (!response.ok) {
                throw new Error('Error fetching taco');
            }
            return response.json();
        }).then((json) => {
            resolve(new TacoResponse(json));
        }).catch(reject);
    });
}
function loadTaco() {
    let activityIndicator = new ActivityIndicator({
      centerX: 0, centerY: 0
    }).appendTo(contentView);
  
    getRandomTaco().then((taco: TacoResponse) => {
        scrollView.find(TextView).only('#title').text = taco.title;
        scrollView.find(TextView).only('#recipe').text = taco.recipe;
        // ...
    }).catch((error) => {
        console.error(error);
    }).then(() => {
        activityIndicator.dispose();
    });
}
loadTaco();

cordova/config.xml

<widget>
    <!-- ... -->
    <plugin name="cordova-plugin-shake" version="0.6.0" />
    <!-- ... -->
</widget>

Build your app when:

  • Add a Cordova plugin
  • Update a Cordova plugin
  • Release to app stores (with debug off)

Updating JavaScript?  Just reload

Two Ways To Build

  • Free online build service
  • Local build
    • Must install platform SDKs and Cordova
      • Android: Linux, Windows, OSX
      • iOS: OSX
    • Docker image available for Android builds

Deploy Built App

  • Android
    • adb install
    • Sideload by opening APK
  • iOS
    • Sideload through Xcode
    • OTA distribution (HTML + XML)
    • Test Flight et al.

src/app.ts

// ...

shake.startWatch(loadTaco, 40, console.error);

Debugger?

Yes!

In its current issue 11/2018, the German CHIP computer magazine tested several online banking apps in the context of the article “Our money in safe hands”. Our 1822direkt Banking App developed with the Tabris framework came out best. We are very proud of this great result, which we reached in close co-operation with 1822direkt!

Where to Get Help

  • Join the tabrisjs Slack channel
  • Stack Overflow with tabrisjs tag
  • Open an issue on GitHub for bugs

Questions