Skip to content

NativeScript Cheatsheet

NativeScript - Native Mobile Apps with JavaScript

NativeScript is an open-source framework for building truly native mobile applications using JavaScript, TypeScript, Angular, Vue.js, or React. It provides direct access to native APIs and UI components without WebViews.

Table of Contents

Installation

Prerequisites

# Install Node.js (version 14 or later)
# Download from nodejs.org

# Verify Node.js installation
node --version
npm --version

# Install Android Studio (for Android development)
# Download from developer.android.com

# Install Xcode (for iOS development, macOS only)
# Download from Mac App Store

# Set environment variables
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/platform-tools

NativeScript CLI Installation

# Install NativeScript CLI globally
npm install -g @nativescript/cli

# Verify installation
ns --version
tns --version  # Alternative command

# Check environment setup
ns doctor
ns doctor android
ns doctor ios

# Install platform tools
ns install

Development Environment Setup

# For Android development
# 1. Install Android Studio
# 2. Install Android SDK
# 3. Set ANDROID_HOME environment variable
# 4. Add platform-tools to PATH

# For iOS development (macOS only)
# 1. Install Xcode
# 2. Install Xcode Command Line Tools
xcode-select --install

# 3. Install CocoaPods
sudo gem install cocoapods

# Verify setup
ns doctor

Getting Started

Create New Project

# Create new NativeScript project
ns create MyApp

# Create with specific template
ns create MyApp --template @nativescript/template-hello-world
ns create MyApp --template @nativescript/template-tab-navigation
ns create MyApp --template @nativescript/template-drawer-navigation

# Create with framework
ns create MyApp --ng  # Angular
ns create MyApp --vue # Vue.js
ns create MyApp --react # React

# Create with TypeScript
ns create MyApp --ts

# Navigate to project directory
cd MyApp

# Run the app
ns run android
ns run ios

Project Templates

# JavaScript templates
ns create MyApp --template @nativescript/template-hello-world
ns create MyApp --template @nativescript/template-tab-navigation
ns create MyApp --template @nativescript/template-drawer-navigation
ns create MyApp --template @nativescript/template-master-detail

# TypeScript templates
ns create MyApp --template @nativescript/template-hello-world-ts
ns create MyApp --template @nativescript/template-tab-navigation-ts

# Angular templates
ns create MyApp --template @nativescript/template-hello-world-ng
ns create MyApp --template @nativescript/template-tab-navigation-ng
ns create MyApp --template @nativescript/template-drawer-navigation-ng

# Vue.js templates
ns create MyApp --template @nativescript/template-hello-world-vue
ns create MyApp --template @nativescript/template-tab-navigation-vue

# React templates
ns create MyApp --template @nativescript/template-hello-world-react

Project Structure

Basic Structure

MyApp/
├── app/                    # Application source code
│   ├── app.js             # Application entry point
│   ├── app.css            # Global styles
│   ├── main-page.js       # Main page logic
│   ├── main-page.xml      # Main page markup
│   └── main-view-model.js # Main page view model
├── platforms/             # Platform-specific code (generated)
│   ├── android/
│   └── ios/
├── node_modules/          # Dependencies
├── package.json           # Project configuration
├── nsconfig.json          # NativeScript configuration
├── webpack.config.js      # Webpack configuration
└── App_Resources/         # Platform resources
    ├── Android/
    │   ├── src/main/res/
    │   └── app.gradle
    └── iOS/
        ├── Info.plist
        └── build.xcconfig

app.js (Entry Point)

import { Application } from '@nativescript/core';

// Start the application
Application.run({ moduleName: 'app-root' });

// Alternative with navigation
Application.run({ moduleName: 'main-page' });

main-page.xml (UI Markup)

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo" class="page">
    <Page.actionBar>
        <ActionBar title="My App" icon="" class="action-bar">
        </ActionBar>
    </Page.actionBar>

    <GridLayout class="page">
        <Label text="Hello, NativeScript!" class="h1 text-center" />
        <Button text="Tap me!" tap="{{ onTap }}" class="btn btn-primary" />
    </GridLayout>
</Page>

main-page.js (Page Logic)

import { fromObject } from '@nativescript/core';

export function onNavigatingTo(args) {
    const page = args.object;
    const viewModel = fromObject({
        message: 'Hello, NativeScript!',
        onTap: function() {
            this.set('message', 'Button tapped!');
        }
    });

    page.bindingContext = viewModel;
}

CLI Commands

Project Management

# Create new project
ns create <app-name> [options]

# Add platform
ns platform add android
ns platform add ios

# Remove platform
ns platform remove android
ns platform remove ios

# List platforms
ns platform list

# Clean project
ns platform clean android
ns platform clean ios

# Update platform
ns platform update android
ns platform update ios

Development Commands

# Run on device/emulator
ns run android
ns run ios

# Run with specific options
ns run android --device
ns run ios --device

# Run on specific device
ns run android --device <device-id>
ns run ios --device <device-id>

# Debug mode
ns debug android
ns debug ios

# Preview in browser (limited functionality)
ns preview

# Serve for hot reload
ns run android --hmr
ns run ios --hmr

Build Commands

# Build for platform
ns build android
ns build ios

# Build for release
ns build android --release
ns build ios --release --for-device

# Build with specific configuration
ns build android --env.production
ns build ios --env.production

# Clean build
ns clean

# Prepare platform
ns prepare android
ns prepare ios

Plugin Management

# Install plugin
ns plugin add <plugin-name>

# Remove plugin
ns plugin remove <plugin-name>

# List installed plugins
ns plugin list

# Update plugin
ns plugin update <plugin-name>

# Search for plugins
npm search nativescript-plugin

Core Concepts

Observable and Data Binding

import { Observable, fromObject } from '@nativescript/core';

// Create observable object
const viewModel = new Observable();
viewModel.set('name', 'John Doe');
viewModel.set('age', 30);

// Listen to property changes
viewModel.on(Observable.propertyChangeEvent, (args) => {
    console.log(`Property ${args.propertyName} changed to ${args.value}`);
});

// Create from object
const user = fromObject({
    firstName: 'John',
    lastName: 'Doe',
    fullName: function() {
        return `${this.firstName} ${this.lastName}`;
    },
    updateName: function(first, last) {
        this.set('firstName', first);
        this.set('lastName', last);
    }
});

// Observable Array
import { ObservableArray } from '@nativescript/core';

const items = new ObservableArray([
    { name: 'Item 1', value: 1 },
    { name: 'Item 2', value: 2 },
    { name: 'Item 3', value: 3 }
]);

// Listen to array changes
items.on(ObservableArray.changeEvent, (args) => {
    console.log('Array changed:', args);
});

// Add items
items.push({ name: 'Item 4', value: 4 });

// Remove items
items.splice(0, 1);
import { Frame, topmost } from '@nativescript/core';

// Navigate to page
export function navigateToDetails() {
    const frame = Frame.topmost();
    frame.navigate({
        moduleName: 'details-page',
        context: { id: 123, name: 'Sample Item' }
    });
}

// Navigate with animation
export function navigateWithAnimation() {
    topmost().navigate({
        moduleName: 'details-page',
        animated: true,
        transition: {
            name: 'slide',
            duration: 300,
            curve: 'ease'
        }
    });
}

// Navigate back
export function goBack() {
    topmost().goBack();
}

// Clear history and navigate
export function navigateAndClearHistory() {
    topmost().navigate({
        moduleName: 'home-page',
        clearHistory: true
    });
}

// Modal navigation
export function showModal() {
    const frame = topmost();
    frame.showModal('modal-page', {
        context: { data: 'modal data' },
        fullscreen: false,
        animated: true
    });
}

// Close modal
export function closeModal() {
    const frame = topmost();
    frame.closeModal();
}

Application Lifecycle

import { Application, on as applicationOn, launchEvent, suspendEvent, resumeEvent, exitEvent } from '@nativescript/core';

// Application launch
applicationOn(launchEvent, (args) => {
    console.log('Application launched');
    if (args.android) {
        // Android-specific code
        console.log('Launched Android application with the following intent: ' + args.android.getIntent());
    } else if (args.ios) {
        // iOS-specific code
        console.log('Launched iOS application with options: ' + args.ios);
    }
});

// Application suspend
applicationOn(suspendEvent, (args) => {
    console.log('Application suspended');
    // Save application state
});

// Application resume
applicationOn(resumeEvent, (args) => {
    console.log('Application resumed');
    // Restore application state
});

// Application exit
applicationOn(exitEvent, (args) => {
    console.log('Application exit');
    // Cleanup resources
});

// Low memory warning
applicationOn('lowMemory', (args) => {
    console.log('Low memory warning');
    // Free up memory
});

// Orientation change
applicationOn('orientationChanged', (args) => {
    console.log('Orientation changed to:', args.newValue);
});

UI Components

Basic Components

<!-- Label -->
<Label text="Hello World" class="label" />
<Label text="{{ message }}" textWrap="true" />

<!-- Button -->
<Button text="Click Me" tap="{{ onTap }}" class="btn btn-primary" />
<Button text="Disabled" isEnabled="false" />

<!-- TextField -->
<TextField text="{{ username }}" hint="Enter username" />
<TextField secure="true" hint="Password" />

<!-- TextView -->
<TextView text="{{ description }}" editable="false" />
<TextView hint="Enter description" />

<!-- Image -->
<Image src="~/images/logo.png" stretch="aspectFit" />
<Image src="{{ imageUrl }}" loadMode="async" />

<!-- ActivityIndicator -->
<ActivityIndicator busy="{{ isLoading }}" />

<!-- Progress -->
<Progress value="{{ progressValue }}" maxValue="100" />

<!-- Slider -->
<Slider value="{{ sliderValue }}" minValue="0" maxValue="100" />

<!-- Switch -->
<Switch checked="{{ isEnabled }}" />

<!-- DatePicker -->
<DatePicker date="{{ selectedDate }}" />

<!-- TimePicker -->
<TimePicker time="{{ selectedTime }}" />

<!-- SearchBar -->
<SearchBar text="{{ searchQuery }}" hint="Search..." submit="{{ onSearch }}" />

List Components

<!-- ListView -->
<ListView items="{{ items }}" itemTap="{{ onItemTap }}">
    <ListView.itemTemplate>
        <Label text="{{ name }}" class="list-item" />
    </ListView.itemTemplate>
</ListView>

<!-- ListView with complex template -->
<ListView items="{{ users }}" separatorColor="transparent">
    <ListView.itemTemplate>
        <GridLayout columns="auto, *" rows="auto, auto" class="list-item">
            <Image src="{{ avatar }}" row="0" col="0" rowSpan="2" class="avatar" />
            <Label text="{{ name }}" row="0" col="1" class="name" />
            <Label text="{{ email }}" row="1" col="1" class="email" />
        </GridLayout>
    </ListView.itemTemplate>
</ListView>

<!-- Repeater -->
<Repeater items="{{ items }}">
    <Repeater.itemTemplate>
        <StackLayout class="item">
            <Label text="{{ title }}" class="title" />
            <Label text="{{ description }}" class="description" />
        </StackLayout>
    </Repeater.itemTemplate>
</Repeater>

Advanced Components

<!-- WebView -->
<WebView src="{{ url }}" />
<WebView src="~/html/index.html" />

<!-- HtmlView -->
<HtmlView html="{{ htmlContent }}" />

<!-- Placeholder -->
<Placeholder creatingView="{{ onCreatingView }}" />

<!-- ContentView -->
<ContentView content="{{ dynamicContent }}" />

<!-- Frame -->
<Frame defaultPage="main-page" />

<!-- TabView -->
<TabView selectedIndex="{{ selectedTab }}">
    <TabViewItem title="Tab 1">
        <Label text="Content of Tab 1" />
    </TabViewItem>
    <TabViewItem title="Tab 2">
        <Label text="Content of Tab 2" />
    </TabViewItem>
</TabView>

<!-- SegmentedBar -->
<SegmentedBar items="{{ segments }}" selectedIndex="{{ selectedSegment }}" />

<!-- ActionBar -->
<ActionBar title="My App" class="action-bar">
    <ActionItem text="Settings" tap="{{ openSettings }}" ios.position="right" android.position="actionBar" />
    <ActionItem icon="res://ic_menu" tap="{{ openMenu }}" ios.position="left" android.position="actionBar" />
</ActionBar>

Layouts

StackLayout

<!-- Vertical stack (default) -->
<StackLayout>
    <Label text="First" />
    <Label text="Second" />
    <Label text="Third" />
</StackLayout>

<!-- Horizontal stack -->
<StackLayout orientation="horizontal">
    <Button text="Button 1" />
    <Button text="Button 2" />
    <Button text="Button 3" />
</StackLayout>

<!-- With spacing and padding -->
<StackLayout spacing="10" padding="20">
    <Label text="Item 1" />
    <Label text="Item 2" />
</StackLayout>

GridLayout

<!-- Basic grid -->
<GridLayout columns="*, *, *" rows="auto, auto">
    <Label text="(0,0)" row="0" col="0" />
    <Label text="(0,1)" row="0" col="1" />
    <Label text="(0,2)" row="0" col="2" />
    <Label text="(1,0)" row="1" col="0" />
    <Label text="(1,1)" row="1" col="1" />
    <Label text="(1,2)" row="1" col="2" />
</GridLayout>

<!-- Spanning cells -->
<GridLayout columns="*, *" rows="auto, auto, auto">
    <Label text="Header" row="0" col="0" colSpan="2" class="header" />
    <Label text="Left" row="1" col="0" />
    <Label text="Right" row="1" col="1" />
    <Label text="Footer" row="2" col="0" colSpan="2" class="footer" />
</GridLayout>

<!-- Complex layout -->
<GridLayout columns="100, *, 50" rows="auto, *, auto">
    <Label text="Fixed 100" row="0" col="0" />
    <Label text="Flexible" row="0" col="1" />
    <Label text="Fixed 50" row="0" col="2" />
    <ScrollView row="1" col="0" colSpan="3">
        <StackLayout>
            <!-- Content -->
        </StackLayout>
    </ScrollView>
    <Button text="Footer" row="2" col="0" colSpan="3" />
</GridLayout>

FlexboxLayout

<!-- Basic flexbox -->
<FlexboxLayout flexDirection="row" justifyContent="space-between">
    <Label text="Start" />
    <Label text="Center" />
    <Label text="End" />
</FlexboxLayout>

<!-- Vertical flexbox -->
<FlexboxLayout flexDirection="column" alignItems="center">
    <Label text="Centered" />
    <Button text="Button" />
</FlexboxLayout>

<!-- Flex wrap -->
<FlexboxLayout flexDirection="row" flexWrap="wrap">
    <Label text="Item 1" flexGrow="1" />
    <Label text="Item 2" flexGrow="1" />
    <Label text="Item 3" flexGrow="1" />
    <Label text="Item 4" flexGrow="1" />
</FlexboxLayout>

<!-- Complex flexbox -->
<FlexboxLayout flexDirection="column" height="100%">
    <Label text="Header" class="header" />
    <FlexboxLayout flexDirection="row" flexGrow="1">
        <Label text="Sidebar" class="sidebar" />
        <ScrollView flexGrow="1">
            <StackLayout>
                <!-- Main content -->
            </StackLayout>
        </ScrollView>
    </FlexboxLayout>
    <Label text="Footer" class="footer" />
</FlexboxLayout>

AbsoluteLayout

<!-- Absolute positioning -->
<AbsoluteLayout>
    <Label text="Top Left" left="0" top="0" />
    <Label text="Top Right" right="0" top="0" />
    <Label text="Bottom Left" left="0" bottom="0" />
    <Label text="Bottom Right" right="0" bottom="0" />
    <Label text="Center" left="50%" top="50%" />
</AbsoluteLayout>

<!-- Overlapping elements -->
<AbsoluteLayout>
    <Image src="~/images/background.jpg" width="100%" height="100%" />
    <Label text="Overlay Text" left="20" top="50" class="overlay-text" />
    <Button text="Action" right="20" bottom="50" />
</AbsoluteLayout>

DockLayout

<!-- Basic dock layout -->
<DockLayout>
    <Label text="Top" dock="top" class="dock-top" />
    <Label text="Bottom" dock="bottom" class="dock-bottom" />
    <Label text="Left" dock="left" class="dock-left" />
    <Label text="Right" dock="right" class="dock-right" />
    <Label text="Fill" class="dock-fill" />
</DockLayout>

<!-- App layout with dock -->
<DockLayout>
    <ActionBar dock="top" title="My App" />
    <GridLayout dock="bottom" columns="*, *, *" class="tab-bar">
        <Button text="Home" col="0" />
        <Button text="Search" col="1" />
        <Button text="Profile" col="2" />
    </GridLayout>
    <ScrollView>
        <StackLayout>
            <!-- Main content -->
        </StackLayout>
    </ScrollView>
</DockLayout>

WrapLayout

<!-- Horizontal wrap -->
<WrapLayout orientation="horizontal" itemWidth="100" itemHeight="100">
    <Label text="1" class="item" />
    <Label text="2" class="item" />
    <Label text="3" class="item" />
    <Label text="4" class="item" />
    <Label text="5" class="item" />
</WrapLayout>

<!-- Vertical wrap -->
<WrapLayout orientation="vertical" itemWidth="150" itemHeight="50">
    <Button text="Button 1" />
    <Button text="Button 2" />
    <Button text="Button 3" />
    <Button text="Button 4" />
</WrapLayout>

Frame Navigation

import { Frame, topmost } from '@nativescript/core';

// Basic navigation
export function navigateToPage() {
    topmost().navigate('details-page');
}

// Navigation with context
export function navigateWithData() {
    topmost().navigate({
        moduleName: 'details-page',
        context: {
            id: 123,
            title: 'Sample Item',
            data: { key: 'value' }
        }
    });
}

// Navigation with transition
export function navigateWithTransition() {
    topmost().navigate({
        moduleName: 'details-page',
        animated: true,
        transition: {
            name: 'slideLeft',
            duration: 300,
            curve: 'easeIn'
        }
    });
}

// Back navigation
export function goBack() {
    if (topmost().canGoBack()) {
        topmost().goBack();
    }
}

// Clear history
export function navigateAndClearHistory() {
    topmost().navigate({
        moduleName: 'home-page',
        clearHistory: true
    });
}
// Show modal
export function showModal() {
    const frame = topmost();
    frame.showModal({
        moduleName: 'modal-page',
        context: { message: 'Hello from modal' },
        fullscreen: false,
        animated: true,
        stretched: false
    }).then((result) => {
        console.log('Modal closed with result:', result);
    });
}

// Close modal with result
export function closeModal() {
    const frame = topmost();
    frame.closeModal('Modal result data');
}

// Modal page setup
// modal-page.js
export function onShownModally(args) {
    const page = args.object;
    const context = args.context;

    page.bindingContext = fromObject({
        message: context.message,
        closeModal: function() {
            page.closeModal('Data from modal');
        }
    });
}

Tab Navigation

<!-- TabView navigation -->
<TabView selectedIndex="{{ selectedTab }}" selectedIndexChanged="{{ onTabChanged }}">
    <TabViewItem title="Home" iconSource="res://ic_home">
        <Frame defaultPage="home-page" />
    </TabViewItem>
    <TabViewItem title="Search" iconSource="res://ic_search">
        <Frame defaultPage="search-page" />
    </TabViewItem>
    <TabViewItem title="Profile" iconSource="res://ic_profile">
        <Frame defaultPage="profile-page" />
    </TabViewItem>
</TabView>

Drawer Navigation

<!-- RadSideDrawer (requires plugin) -->
<nsDrawer:RadSideDrawer xmlns:nsDrawer="@nativescript/ui-sidedrawer">
    <nsDrawer:RadSideDrawer.drawerContent>
        <StackLayout class="drawer-content">
            <Label text="Menu" class="drawer-header" />
            <Button text="Home" tap="{{ navigateToHome }}" />
            <Button text="Settings" tap="{{ navigateToSettings }}" />
            <Button text="About" tap="{{ navigateToAbout }}" />
        </StackLayout>
    </nsDrawer:RadSideDrawer.drawerContent>

    <nsDrawer:RadSideDrawer.mainContent>
        <Frame defaultPage="main-page" />
    </nsDrawer:RadSideDrawer.mainContent>
</nsDrawer:RadSideDrawer>

Data Binding

One-Way Binding

<!-- Text binding -->
<Label text="{{ message }}" />
<Label text="{{ 'Hello ' + name }}" />

<!-- Property binding -->
<Button isEnabled="{{ canSubmit }}" />
<Image src="{{ imageUrl }}" />
<ActivityIndicator busy="{{ isLoading }}" />

<!-- Conditional binding -->
<Label text="{{ isLoggedIn ? 'Welcome' : 'Please login' }}" />
<Button visibility="{{ hasData ? 'visible' : 'collapsed' }}" />

Two-Way Binding

<!-- TextField two-way binding -->
<TextField text="{{ username, mode=twoWay }}" />
<Slider value="{{ volume, mode=twoWay }}" />
<Switch checked="{{ isEnabled, mode=twoWay }}" />
<DatePicker date="{{ selectedDate, mode=twoWay }}" />

Event Binding

<!-- Event handlers -->
<Button text="Click Me" tap="{{ onButtonTap }}" />
<TextField text="{{ username }}" textChange="{{ onTextChange }}" />
<ListView items="{{ items }}" itemTap="{{ onItemTap }}" />
<SearchBar text="{{ query }}" submit="{{ onSearch }}" clear="{{ onClear }}" />

List Binding

// View model with observable array
import { Observable, ObservableArray } from '@nativescript/core';

export function createViewModel() {
    const viewModel = new Observable();

    const items = new ObservableArray([
        { id: 1, name: 'Item 1', description: 'First item' },
        { id: 2, name: 'Item 2', description: 'Second item' },
        { id: 3, name: 'Item 3', description: 'Third item' }
    ]);

    viewModel.set('items', items);

    viewModel.set('addItem', function() {
        const newItem = {
            id: items.length + 1,
            name: `Item ${items.length + 1}`,
            description: `Item number ${items.length + 1}`
        };
        items.push(newItem);
    });

    viewModel.set('removeItem', function(index) {
        items.splice(index, 1);
    });

    viewModel.set('onItemTap', function(args) {
        const item = items.getItem(args.index);
        console.log('Tapped item:', item.name);
    });

    return viewModel;
}
<!-- List with binding -->
<StackLayout>
    <Button text="Add Item" tap="{{ addItem }}" />
    <ListView items="{{ items }}" itemTap="{{ onItemTap }}">
        <ListView.itemTemplate>
            <GridLayout columns="*, auto" class="list-item">
                <StackLayout col="0">
                    <Label text="{{ name }}" class="item-name" />
                    <Label text="{{ description }}" class="item-description" />
                </StackLayout>
                <Button text="Delete" col="1" tap="{{ $parents['Page'].removeItem, $index }}" />
            </GridLayout>
        </ListView.itemTemplate>
    </ListView>
</StackLayout>

Styling

CSS Styling

/* app.css - Global styles */

/* Import NativeScript theme */
@import '@nativescript/theme/css/core.css';
@import '@nativescript/theme/css/default.css';

/* Global styles */
.page {
    background-color: #f0f0f0;
}

.action-bar {
    background-color: #3f51b5;
    color: white;
}

/* Typography */
.h1 {
    font-size: 24;
    font-weight: bold;
    color: #333;
}

.h2 {
    font-size: 20;
    font-weight: bold;
    color: #666;
}

.body {
    font-size: 16;
    color: #333;
}

/* Buttons */
.btn {
    font-size: 16;
    padding: 12;
    margin: 8;
    border-radius: 4;
}

.btn-primary {
    background-color: #3f51b5;
    color: white;
}

.btn-secondary {
    background-color: #f0f0f0;
    color: #333;
    border-width: 1;
    border-color: #ccc;
}

/* Layout helpers */
.text-center {
    text-align: center;
}

.text-left {
    text-align: left;
}

.text-right {
    text-align: right;
}

.m-10 {
    margin: 10;
}

.p-10 {
    padding: 10;
}

/* List styles */
.list-item {
    padding: 16;
    border-bottom-width: 1;
    border-bottom-color: #e0e0e0;
}

.list-item:active {
    background-color: #f5f5f5;
}

/* Form styles */
.form-field {
    margin: 8;
    padding: 12;
    border-width: 1;
    border-color: #ccc;
    border-radius: 4;
}

.form-field:focus {
    border-color: #3f51b5;
}

/* Platform-specific styles */
.android .action-bar {
    height: 56;
}

.ios .action-bar {
    height: 44;
}

/* Responsive styles */
@media (orientation: landscape) {
    .container {
        flex-direction: row;
    }
}

SCSS Support

# Install SCSS support
npm install --save-dev sass

# Create SCSS files
# app.scss
// app.scss
@import '@nativescript/theme/scss/variables/blue';
@import '@nativescript/theme/scss/core';
@import '@nativescript/theme/scss/default';

// Variables
$primary-color: #3f51b5;
$secondary-color: #f0f0f0;
$text-color: #333;
$border-color: #e0e0e0;

// Mixins
@mixin button-style($bg-color, $text-color) {
    background-color: $bg-color;
    color: $text-color;
    padding: 12;
    border-radius: 4;
    font-size: 16;
}

@mixin card-style {
    background-color: white;
    border-radius: 8;
    elevation: 2;
    margin: 8;
    padding: 16;
}

// Styles
.page {
    background-color: $secondary-color;
}

.btn-primary {
    @include button-style($primary-color, white);
}

.btn-secondary {
    @include button-style($secondary-color, $text-color);
    border-width: 1;
    border-color: $border-color;
}

.card {
    @include card-style;
}

// Nested styles
.list-container {
    .list-item {
        padding: 16;
        border-bottom-width: 1;
        border-bottom-color: $border-color;

        .item-title {
            font-size: 18;
            font-weight: bold;
            color: $text-color;
        }

        .item-subtitle {
            font-size: 14;
            color: lighten($text-color, 20%);
        }
    }
}

Platform-Specific Styling

/* Platform-specific CSS */
.android .action-bar {
    background-color: #3f51b5;
    color: white;
    height: 56;
}

.ios .action-bar {
    background-color: #007aff;
    color: white;
    height: 44;
}

/* Device-specific styles */
.phone .container {
    padding: 16;
}

.tablet .container {
    padding: 32;
    max-width: 600;
    horizontal-align: center;
}

/* Orientation-specific styles */
.portrait .layout {
    flex-direction: column;
}

.landscape .layout {
    flex-direction: row;
}

Dynamic Styling

// Dynamic styling in code
import { Color } from '@nativescript/core';

export function applyDynamicStyles(view, theme) {
    if (theme === 'dark') {
        view.backgroundColor = new Color('#333');
        view.color = new Color('#fff');
    } else {
        view.backgroundColor = new Color('#fff');
        view.color = new Color('#333');
    }
}

// CSS class manipulation
export function toggleTheme(view) {
    if (view.className.includes('dark-theme')) {
        view.className = view.className.replace('dark-theme', 'light-theme');
    } else {
        view.className = view.className.replace('light-theme', 'dark-theme');
    }
}

// Style animation
import { Animation } from '@nativescript/core';

export function animateButton(button) {
    const animation = new Animation([{
        target: button,
        scale: { x: 1.2, y: 1.2 },
        duration: 200
    }, {
        target: button,
        scale: { x: 1, y: 1 },
        duration: 200
    }]);

    animation.play();
}

Platform APIs

Device Information

import { Device, Screen, isAndroid, isIOS } from '@nativescript/core';

// Device information
console.log('Device Type:', Device.deviceType);
console.log('OS:', Device.os);
console.log('OS Version:', Device.osVersion);
console.log('Model:', Device.model);
console.log('Manufacturer:', Device.manufacturer);
console.log('UUID:', Device.uuid);
console.log('Language:', Device.language);
console.log('Region:', Device.region);

// Screen information
console.log('Screen Width:', Screen.mainScreen.widthDIPs);
console.log('Screen Height:', Screen.mainScreen.heightDIPs);
console.log('Screen Scale:', Screen.mainScreen.scale);

// Platform detection
if (isAndroid) {
    console.log('Running on Android');
    // Android-specific code
} else if (isIOS) {
    console.log('Running on iOS');
    // iOS-specific code
}

// Platform-specific API access
if (isAndroid) {
    const context = Application.android.context;
    const packageManager = context.getPackageManager();
    // Use Android APIs
} else if (isIOS) {
    const currentDevice = UIDevice.currentDevice;
    // Use iOS APIs
}

File System

import { File, Folder, knownFolders, path } from '@nativescript/core';

// Known folders
const documentsFolder = knownFolders.documents();
const tempFolder = knownFolders.temp();
const appFolder = knownFolders.currentApp();

// Create file
const file = documentsFolder.getFile('data.txt');

// Write to file
file.writeText('Hello, NativeScript!')
    .then(() => {
        console.log('File written successfully');
    })
    .catch((error) => {
        console.error('Error writing file:', error);
    });

// Read from file
file.readText()
    .then((content) => {
        console.log('File content:', content);
    })
    .catch((error) => {
        console.error('Error reading file:', error);
    });

// Create folder
const folder = documentsFolder.getFolder('myFolder');

// List folder contents
folder.getEntities()
    .then((entities) => {
        entities.forEach((entity) => {
            console.log('Entity:', entity.name, entity.path);
        });
    });

// File operations
const sourceFile = documentsFolder.getFile('source.txt');
const targetFile = documentsFolder.getFile('target.txt');

// Copy file
sourceFile.copy(targetFile.path)
    .then(() => {
        console.log('File copied');
    });

// Move file
sourceFile.move(targetFile.path)
    .then(() => {
        console.log('File moved');
    });

// Delete file
file.remove()
    .then(() => {
        console.log('File deleted');
    });

// Check if file exists
if (file.exists) {
    console.log('File exists');
}

// Get file info
console.log('File size:', file.size);
console.log('Last modified:', file.lastModified);

HTTP Requests

import { Http } from '@nativescript/core';

// GET request
Http.getJSON('https://api.example.com/users')
    .then((result) => {
        console.log('Users:', result);
    })
    .catch((error) => {
        console.error('Error:', error);
    });

// POST request
Http.request({
    url: 'https://api.example.com/users',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token'
    },
    content: JSON.stringify({
        name: 'John Doe',
        email: 'john@example.com'
    })
})
.then((response) => {
    console.log('Response:', response.content.toJSON());
})
.catch((error) => {
    console.error('Error:', error);
});

// File upload
Http.request({
    url: 'https://api.example.com/upload',
    method: 'POST',
    headers: {
        'Content-Type': 'multipart/form-data'
    },
    content: {
        file: file,
        description: 'Uploaded file'
    }
})
.then((response) => {
    console.log('Upload successful');
})
.catch((error) => {
    console.error('Upload failed:', error);
});

// Download file
Http.getFile('https://example.com/file.pdf', documentsFolder.path + '/downloaded.pdf')
    .then((file) => {
        console.log('File downloaded:', file.path);
    })
    .catch((error) => {
        console.error('Download failed:', error);
    });

Local Storage

import { ApplicationSettings } from '@nativescript/core';

// Store data
ApplicationSettings.setString('username', 'john_doe');
ApplicationSettings.setNumber('userId', 123);
ApplicationSettings.setBoolean('isLoggedIn', true);

// Retrieve data
const username = ApplicationSettings.getString('username', 'default_user');
const userId = ApplicationSettings.getNumber('userId', 0);
const isLoggedIn = ApplicationSettings.getBoolean('isLoggedIn', false);

// Remove data
ApplicationSettings.remove('username');

// Check if key exists
if (ApplicationSettings.hasKey('username')) {
    console.log('Username exists');
}

// Clear all data
ApplicationSettings.clear();

// Store complex objects
const user = {
    id: 123,
    name: 'John Doe',
    email: 'john@example.com'
};

ApplicationSettings.setString('user', JSON.stringify(user));

// Retrieve complex objects
const storedUser = JSON.parse(ApplicationSettings.getString('user', '{}'));

Plugins

Core Plugins

# Camera plugin
ns plugin add @nativescript/camera

# Geolocation plugin
ns plugin add @nativescript/geolocation

# Local notifications
ns plugin add @nativescript/local-notifications

# Social share
ns plugin add @nativescript/social-share

# Email plugin
ns plugin add @nativescript/email

# Phone plugin
ns plugin add @nativescript/phone

# Fingerprint authentication
ns plugin add @nativescript/fingerprint-auth

# Secure storage
ns plugin add @nativescript/secure-storage

# Background HTTP
ns plugin add @nativescript/background-http

Using Camera Plugin

import { Camera, requestPermissions } from '@nativescript/camera';

export function takePicture() {
    requestPermissions()
        .then(() => {
            const options = {
                width: 300,
                height: 300,
                keepAspectRatio: true,
                saveToGallery: false
            };

            return Camera.takePicture(options);
        })
        .then((imageAsset) => {
            console.log('Picture taken:', imageAsset);
            // Use the image
            const image = new Image();
            image.src = imageAsset;
        })
        .catch((error) => {
            console.error('Camera error:', error);
        });
}

export function selectFromGallery() {
    const options = {
        width: 300,
        height: 300,
        keepAspectRatio: true
    };

    Camera.requestPermissions()
        .then(() => {
            return Camera.selectFromGallery(options);
        })
        .then((imageAsset) => {
            console.log('Image selected:', imageAsset);
        })
        .catch((error) => {
            console.error('Gallery error:', error);
        });
}

Using Geolocation Plugin

import { Geolocation, CoreTypes } from '@nativescript/geolocation';

export function getCurrentLocation() {
    const options = {
        desiredAccuracy: CoreTypes.Accuracy.high,
        maximumAge: 5000,
        timeout: 10000
    };

    Geolocation.getCurrentLocation(options)
        .then((location) => {
            console.log('Location:', location);
            console.log('Latitude:', location.latitude);
            console.log('Longitude:', location.longitude);
            console.log('Altitude:', location.altitude);
            console.log('Speed:', location.speed);
        })
        .catch((error) => {
            console.error('Location error:', error);
        });
}

export function watchLocation() {
    const options = {
        desiredAccuracy: CoreTypes.Accuracy.high,
        updateDistance: 10,
        minimumUpdateTime: 1000
    };

    const watchId = Geolocation.watchLocation(
        (location) => {
            console.log('Location update:', location);
        },
        (error) => {
            console.error('Location error:', error);
        },
        options
    );

    // Stop watching
    setTimeout(() => {
        Geolocation.clearWatch(watchId);
    }, 30000);
}

Custom Plugin Development

// Create custom plugin
// my-plugin/index.js
export class MyPlugin {
    static getMessage() {
        return 'Hello from custom plugin!';
    }

    static performAction(data) {
        return new Promise((resolve, reject) => {
            // Plugin logic here
            setTimeout(() => {
                resolve(`Action completed with: ${data}`);
            }, 1000);
        });
    }
}

// my-plugin/index.android.js
export class MyPlugin {
    static getMessage() {
        // Android-specific implementation
        return 'Hello from Android plugin!';
    }

    static performAction(data) {
        return new Promise((resolve, reject) => {
            // Android-specific logic
            const context = Application.android.context;
            // Use Android APIs
            resolve(`Android action completed with: ${data}`);
        });
    }
}

// my-plugin/index.ios.js
export class MyPlugin {
    static getMessage() {
        // iOS-specific implementation
        return 'Hello from iOS plugin!';
    }

    static performAction(data) {
        return new Promise((resolve, reject) => {
            // iOS-specific logic
            // Use iOS APIs
            resolve(`iOS action completed with: ${data}`);
        });
    }
}

// Usage
import { MyPlugin } from './my-plugin';

console.log(MyPlugin.getMessage());

MyPlugin.performAction('test data')
    .then((result) => {
        console.log('Plugin result:', result);
    })
    .catch((error) => {
        console.error('Plugin error:', error);
    });

Angular Integration

Setup Angular Project

# Create Angular project
ns create MyAngularApp --ng

# Add Angular dependencies
cd MyAngularApp
npm install @angular/core @angular/common @angular/forms @angular/router

# Generate components
ng generate component home
ng generate component details
ng generate service data

App Module

// app.module.ts
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { NativeScriptModule } from '@nativescript/angular';
import { NativeScriptRouterModule } from '@nativescript/angular';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { DetailsComponent } from './details/details.component';

@NgModule({
    bootstrap: [AppComponent],
    imports: [
        NativeScriptModule,
        AppRoutingModule
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        DetailsComponent
    ],
    providers: [],
    schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }

Routing

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes } from '@angular/router';
import { NativeScriptRouterModule } from '@nativescript/angular';

import { HomeComponent } from './home/home.component';
import { DetailsComponent } from './details/details.component';

const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full' },
    { path: 'home', component: HomeComponent },
    { path: 'details/:id', component: DetailsComponent }
];

@NgModule({
    imports: [NativeScriptRouterModule.forRoot(routes)],
    exports: [NativeScriptRouterModule]
})
export class AppRoutingModule { }

Component

// home.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { DataService } from '../data.service';

@Component({
    selector: 'ns-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
    items: any[] = [];
    isLoading = false;

    constructor(
        private router: Router,
        private dataService: DataService
    ) { }

    ngOnInit(): void {
        this.loadData();
    }

    loadData(): void {
        this.isLoading = true;
        this.dataService.getItems()
            .subscribe(
                (data) => {
                    this.items = data;
                    this.isLoading = false;
                },
                (error) => {
                    console.error('Error loading data:', error);
                    this.isLoading = false;
                }
            );
    }

    onItemTap(item: any): void {
        this.router.navigate(['/details', item.id]);
    }

    onRefresh(): void {
        this.loadData();
    }
}
<!-- home.component.html -->
<ActionBar title="Home" class="action-bar">
    <ActionItem text="Refresh" (tap)="onRefresh()" ios.position="right"></ActionItem>
</ActionBar>

<GridLayout rows="*">
    <ActivityIndicator [busy]="isLoading" *ngIf="isLoading"></ActivityIndicator>

    <ListView [items]="items" (itemTap)="onItemTap($event.object.bindingContext)" *ngIf="!isLoading">
        <ng-template let-item="item">
            <GridLayout columns="auto, *" rows="auto, auto" class="list-item">
                <Label [text]="item.title" row="0" col="1" class="item-title"></Label>
                <Label [text]="item.description" row="1" col="1" class="item-description"></Label>
            </GridLayout>
        </ng-template>
    </ListView>
</GridLayout>

Service

// data.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Http } from '@nativescript/core';

@Injectable({
    providedIn: 'root'
})
export class DataService {
    private apiUrl = 'https://api.example.com';

    constructor() { }

    getItems(): Observable<any[]> {
        return new Observable((observer) => {
            Http.getJSON(`${this.apiUrl}/items`)
                .then((data: any[]) => {
                    observer.next(data);
                    observer.complete();
                })
                .catch((error) => {
                    observer.error(error);
                });
        });
    }

    getItem(id: number): Observable<any> {
        return new Observable((observer) => {
            Http.getJSON(`${this.apiUrl}/items/${id}`)
                .then((data: any) => {
                    observer.next(data);
                    observer.complete();
                })
                .catch((error) => {
                    observer.error(error);
                });
        });
    }

    createItem(item: any): Observable<any> {
        return new Observable((observer) => {
            Http.request({
                url: `${this.apiUrl}/items`,
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                content: JSON.stringify(item)
            })
            .then((response) => {
                observer.next(response.content.toJSON());
                observer.complete();
            })
            .catch((error) => {
                observer.error(error);
            });
        });
    }
}

Vue.js Integration

Setup Vue Project

# Create Vue project
ns create MyVueApp --vue

# Add Vue dependencies
cd MyVueApp
npm install vue@next @vue/runtime-core

Main App

// app.js
import { createApp } from 'nativescript-vue';
import Home from './components/Home.vue';

const app = createApp(Home);

app.start();

Vue Component

<!-- components/Home.vue -->
<template>
    <Frame>
        <Page>
            <ActionBar title="Home" />

            <GridLayout rows="auto, *">
                <StackLayout row="0" class="form">
                    <TextField v-model="newItem" hint="Enter new item" />
                    <Button text="Add Item" @tap="addItem" class="btn btn-primary" />
                </StackLayout>

                <ListView row="1" :items="items" @itemTap="onItemTap">
                    <template #default="{ item, index }">
                        <GridLayout columns="*, auto" class="list-item">
                            <Label col="0" :text="item.name" class="item-name" />
                            <Button col="1" text="Delete" @tap="deleteItem(index)" class="btn btn-secondary" />
                        </GridLayout>
                    </template>
                </ListView>
            </GridLayout>
        </Page>
    </Frame>
</template>

<script>
import { ref, reactive } from 'vue';

export default {
    setup() {
        const newItem = ref('');
        const items = reactive([
            { id: 1, name: 'Item 1' },
            { id: 2, name: 'Item 2' },
            { id: 3, name: 'Item 3' }
        ]);

        const addItem = () => {
            if (newItem.value.trim()) {
                items.push({
                    id: Date.now(),
                    name: newItem.value
                });
                newItem.value = '';
            }
        };

        const deleteItem = (index) => {
            items.splice(index, 1);
        };

        const onItemTap = (event) => {
            const item = event.item;
            console.log('Tapped item:', item.name);
        };

        return {
            newItem,
            items,
            addItem,
            deleteItem,
            onItemTap
        };
    }
};
</script>

<style scoped>
.form {
    padding: 20;
}

.list-item {
    padding: 16;
    border-bottom-width: 1;
    border-bottom-color: #e0e0e0;
}

.item-name {
    font-size: 16;
    vertical-align: center;
}

.btn {
    margin: 8;
}
</style>

Vue Router

// router.js
import { createRouter } from 'nativescript-vue';
import Home from './components/Home.vue';
import Details from './components/Details.vue';

const router = createRouter({
    routes: [
        { path: '/', component: Home },
        { path: '/details/:id', component: Details }
    ]
});

export default router;

React Integration

Setup React Project

# Create React project
ns create MyReactApp --react

# Add React dependencies
cd MyReactApp
npm install react react-nativescript

App Component

// app.jsx
import React from 'react';
import { StackNavigator } from 'react-nativescript-navigation';
import { Home } from './components/Home';
import { Details } from './components/Details';

const AppContainer = StackNavigator(
    {
        Home: {
            screen: Home
        },
        Details: {
            screen: Details
        }
    },
    {
        initialRouteName: 'Home'
    }
);

export default AppContainer;

React Component

// components/Home.jsx
import React, { useState, useEffect } from 'react';

export function Home({ navigation }) {
    const [items, setItems] = useState([]);
    const [newItem, setNewItem] = useState('');
    const [isLoading, setIsLoading] = useState(false);

    useEffect(() => {
        loadData();
    }, []);

    const loadData = async () => {
        setIsLoading(true);
        try {
            // Simulate API call
            const data = [
                { id: 1, name: 'Item 1', description: 'First item' },
                { id: 2, name: 'Item 2', description: 'Second item' },
                { id: 3, name: 'Item 3', description: 'Third item' }
            ];
            setItems(data);
        } catch (error) {
            console.error('Error loading data:', error);
        } finally {
            setIsLoading(false);
        }
    };

    const addItem = () => {
        if (newItem.trim()) {
            const item = {
                id: Date.now(),
                name: newItem,
                description: `Description for ${newItem}`
            };
            setItems([...items, item]);
            setNewItem('');
        }
    };

    const deleteItem = (id) => {
        setItems(items.filter(item => item.id !== id));
    };

    const navigateToDetails = (item) => {
        navigation.navigate('Details', { item });
    };

    return (
        <page>
            <actionBar title="Home" />

            <gridLayout rows="auto, *">
                <stackLayout row={0} className="form">
                    <textField
                        text={newItem}
                        onTextChange={(args) => setNewItem(args.value)}
                        hint="Enter new item"
                    />
                    <button
                        text="Add Item"
                        onTap={addItem}
                        className="btn btn-primary"
                    />
                </stackLayout>

                {isLoading ? (
                    <activityIndicator row={1} busy={true} />
                ) : (
                    <listView
                        row={1}
                        items={items}
                        onItemTap={(args) => navigateToDetails(args.item)}
                        cellFactory={(item) => (
                            <gridLayout columns="*, auto" className="list-item">
                                <stackLayout col={0}>
                                    <label text={item.name} className="item-name" />
                                    <label text={item.description} className="item-description" />
                                </stackLayout>
                                <button
                                    col={1}
                                    text="Delete"
                                    onTap={() => deleteItem(item.id)}
                                    className="btn btn-secondary"
                                />
                            </gridLayout>
                        )}
                    />
                )}
            </gridLayout>
        </page>
    );
}

React Hooks

// hooks/useApi.js
import { useState, useEffect } from 'react';
import { Http } from '@nativescript/core';

export function useApi(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            try {
                setLoading(true);
                const result = await Http.getJSON(url);
                setData(result);
            } catch (err) {
                setError(err);
            } finally {
                setLoading(false);
            }
        };

        fetchData();
    }, [url]);

    return { data, loading, error };
}

// Usage in component
export function DataComponent() {
    const { data, loading, error } = useApi('https://api.example.com/data');

    if (loading) return <activityIndicator busy={true} />;
    if (error) return <label text={`Error: ${error.message}`} />;

    return (
        <listView
            items={data}
            cellFactory={(item) => (
                <label text={item.name} />
            )}
        />
    );
}

Testing

Unit Testing

# Install testing dependencies
npm install --save-dev @nativescript/unit-test-runner
npm install --save-dev jasmine
npm install --save-dev karma
npm install --save-dev karma-jasmine
npm install --save-dev karma-nativescript-launcher

# Run tests
ns test android
ns test ios

Test Configuration

// karma.conf.js
module.exports = function(config) {
    config.set({
        basePath: '',
        frameworks: ['jasmine'],
        files: [
            'app/**/*.spec.js'
        ],
        exclude: [],
        preprocessors: {},
        reporters: ['progress'],
        port: 9876,
        colors: true,
        logLevel: config.LOG_INFO,
        autoWatch: false,
        browsers: ['NativeScript'],
        singleRun: true,
        concurrency: Infinity
    });
};

Unit Test Example

// tests/example.spec.js
describe('Calculator', function() {
    let calculator;

    beforeEach(function() {
        calculator = new Calculator();
    });

    it('should add two numbers correctly', function() {
        const result = calculator.add(2, 3);
        expect(result).toBe(5);
    });

    it('should subtract two numbers correctly', function() {
        const result = calculator.subtract(5, 3);
        expect(result).toBe(2);
    });

    it('should multiply two numbers correctly', function() {
        const result = calculator.multiply(4, 3);
        expect(result).toBe(12);
    });

    it('should divide two numbers correctly', function() {
        const result = calculator.divide(10, 2);
        expect(result).toBe(5);
    });

    it('should throw error when dividing by zero', function() {
        expect(function() {
            calculator.divide(10, 0);
        }).toThrow();
    });
});

// Calculator class
class Calculator {
    add(a, b) {
        return a + b;
    }

    subtract(a, b) {
        return a - b;
    }

    multiply(a, b) {
        return a * b;
    }

    divide(a, b) {
        if (b === 0) {
            throw new Error('Division by zero');
        }
        return a / b;
    }
}

E2E Testing

# Install Appium
npm install --save-dev appium
npm install --save-dev wd

# Install test framework
npm install --save-dev mocha
npm install --save-dev chai
// e2e/test.spec.js
const wd = require('wd');
const { expect } = require('chai');

describe('App E2E Tests', function() {
    let driver;

    before(async function() {
        this.timeout(60000);

        driver = wd.promiseChainRemote('localhost', 4723);

        const desiredCaps = {
            platformName: 'Android',
            platformVersion: '10',
            deviceName: 'emulator-5554',
            app: './platforms/android/app/build/outputs/apk/debug/app-debug.apk',
            automationName: 'UiAutomator2'
        };

        await driver.init(desiredCaps);
    });

    after(async function() {
        if (driver) {
            await driver.quit();
        }
    });

    it('should display home screen', async function() {
        const title = await driver.elementByAccessibilityId('home-title');
        const titleText = await title.text();
        expect(titleText).to.equal('Home');
    });

    it('should navigate to details screen', async function() {
        const button = await driver.elementByAccessibilityId('details-button');
        await button.tap();

        const detailsTitle = await driver.elementByAccessibilityId('details-title');
        const titleText = await detailsTitle.text();
        expect(titleText).to.equal('Details');
    });

    it('should add new item', async function() {
        const textField = await driver.elementByAccessibilityId('new-item-field');
        await textField.sendKeys('Test Item');

        const addButton = await driver.elementByAccessibilityId('add-button');
        await addButton.tap();

        const listItem = await driver.elementByAccessibilityId('list-item-0');
        const itemText = await listItem.text();
        expect(itemText).to.contain('Test Item');
    });
});

Performance

Memory Management

// Proper cleanup
export function onNavigatingFrom(args) {
    const page = args.object;

    // Remove event listeners
    if (page.bindingContext && page.bindingContext.cleanup) {
        page.bindingContext.cleanup();
    }

    // Clear timers
    if (page._timer) {
        clearInterval(page._timer);
        page._timer = null;
    }

    // Clear references
    page.bindingContext = null;
}

// Observable cleanup
class ViewModel extends Observable {
    constructor() {
        super();
        this._subscriptions = [];
    }

    addSubscription(subscription) {
        this._subscriptions.push(subscription);
    }

    cleanup() {
        this._subscriptions.forEach(sub => {
            if (sub && sub.unsubscribe) {
                sub.unsubscribe();
            }
        });
        this._subscriptions = [];
    }
}

Image Optimization

// Lazy loading images
export function onImageLoading(args) {
    const image = args.object;
    const listView = image.parent;

    // Only load images for visible items
    if (listView && listView.isItemAtIndexVisible) {
        const index = listView.getItemAtIndex(args.index);
        if (listView.isItemAtIndexVisible(index)) {
            image.src = args.item.imageUrl;
        }
    }
}

// Image caching
class ImageCache {
    constructor() {
        this.cache = new Map();
        this.maxSize = 50; // Maximum number of cached images
    }

    get(url) {
        return this.cache.get(url);
    }

    set(url, imageSource) {
        if (this.cache.size >= this.maxSize) {
            // Remove oldest entry
            const firstKey = this.cache.keys().next().value;
            this.cache.delete(firstKey);
        }
        this.cache.set(url, imageSource);
    }

    clear() {
        this.cache.clear();
    }
}

const imageCache = new ImageCache();

// Optimized image loading
export function loadOptimizedImage(imageView, url) {
    // Check cache first
    const cachedImage = imageCache.get(url);
    if (cachedImage) {
        imageView.imageSource = cachedImage;
        return;
    }

    // Load and cache image
    ImageSource.fromUrl(url)
        .then((imageSource) => {
            imageCache.set(url, imageSource);
            imageView.imageSource = imageSource;
        })
        .catch((error) => {
            console.error('Error loading image:', error);
        });
}

List Performance

// Virtual scrolling for large lists
export function createVirtualList(container, items, itemHeight, visibleItems) {
    let startIndex = 0;
    let endIndex = Math.min(visibleItems, items.length);

    const updateList = () => {
        container.removeChildren();

        for (let i = startIndex; i < endIndex; i++) {
            const item = items[i];
            const itemView = createItemView(item, i);
            container.addChild(itemView);
        }
    };

    const onScroll = (scrollY) => {
        const newStartIndex = Math.floor(scrollY / itemHeight);
        const newEndIndex = Math.min(newStartIndex + visibleItems, items.length);

        if (newStartIndex !== startIndex || newEndIndex !== endIndex) {
            startIndex = newStartIndex;
            endIndex = newEndIndex;
            updateList();
        }
    };

    updateList();

    return { updateList, onScroll };
}

// Efficient list item recycling
export function createRecyclingListView(items, itemTemplate) {
    const recycledViews = [];
    const activeViews = new Map();

    const getRecycledView = () => {
        return recycledViews.pop() || null;
    };

    const recycleView = (view) => {
        recycledViews.push(view);
    };

    const createItemView = (item, index) => {
        let view = getRecycledView();

        if (!view) {
            view = itemTemplate();
        }

        // Bind data to view
        view.bindingContext = item;
        activeViews.set(index, view);

        return view;
    };

    const removeItemView = (index) => {
        const view = activeViews.get(index);
        if (view) {
            activeViews.delete(index);
            recycleView(view);
        }
    };

    return { createItemView, removeItemView };
}

Deployment

Android Deployment

# Build debug APK
ns build android

# Build release APK
ns build android --release --key-store-path myapp.keystore --key-store-password password --key-store-alias myapp --key-store-alias-password password

# Generate keystore
keytool -genkey -v -keystore myapp.keystore -alias myapp -keyalg RSA -keysize 2048 -validity 10000

# Build AAB (Android App Bundle)
ns build android --release --aab --key-store-path myapp.keystore --key-store-password password --key-store-alias myapp --key-store-alias-password password

# Upload to Google Play Console
# Use the generated AAB file in platforms/android/app/build/outputs/bundle/release/

iOS Deployment

# Build for device
ns build ios --for-device --release

# Build with provisioning profile
ns build ios --for-device --release --provision "MyApp Distribution Profile"

# Create IPA
ns build ios --for-device --release --provision "MyApp Distribution Profile" --team-id TEAM_ID

# Upload to App Store
# Use Xcode or Application Loader to upload the IPA

Continuous Integration

# .github/workflows/build.yml
name: Build and Test

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: macos-latest

    steps:
    - uses: actions/checkout@v2

    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '16'

    - name: Install dependencies
      run: npm install

    - name: Install NativeScript CLI
      run: npm install -g @nativescript/cli

    - name: Add platforms
      run: |
        ns platform add android
        ns platform add ios

    - name: Build Android
      run: ns build android --release

    - name: Build iOS
      run: ns build ios --for-device --release

    - name: Run tests
      run: ns test android --justlaunch

    - name: Upload artifacts
      uses: actions/upload-artifact@v2
      with:
        name: builds
        path: |
          platforms/android/app/build/outputs/apk/release/
          platforms/ios/build/Release-iphoneos/

Best Practices

Project Structure

app/
├── core/                   # Core functionality
│   ├── services/          # Business logic services
│   ├── models/            # Data models
│   ├── utils/             # Utility functions
│   └── constants/         # App constants
├── shared/                # Shared components
│   ├── components/        # Reusable UI components
│   ├── directives/        # Custom directives
│   └── pipes/             # Custom pipes
├── features/              # Feature modules
│   ├── home/             # Home feature
│   ├── profile/          # Profile feature
│   └── settings/         # Settings feature
├── assets/               # Static assets
│   ├── images/
│   ├── fonts/
│   └── sounds/
└── styles/               # Global styles
    ├── _variables.scss
    ├── _mixins.scss
    └── app.scss

Code Organization

// Use consistent naming conventions
// PascalCase for classes and constructors
class UserService {
    constructor() {
        this.users = [];
    }
}

// camelCase for variables and functions
const userName = 'john_doe';
const getUserById = (id) => {
    return users.find(user => user.id === id);
};

// UPPER_CASE for constants
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRY_ATTEMPTS = 3;

// Use meaningful names
// Bad
const d = new Date();
const u = users.filter(x => x.a);

// Good
const currentDate = new Date();
const activeUsers = users.filter(user => user.isActive);

// Use consistent error handling
class ApiService {
    async fetchData(url) {
        try {
            const response = await Http.getJSON(url);
            return { success: true, data: response };
        } catch (error) {
            console.error('API Error:', error);
            return { success: false, error: error.message };
        }
    }
}

// Use proper async/await patterns
// Bad
function loadUserData(userId) {
    getUserById(userId)
        .then(user => {
            return getProfileData(user.id);
        })
        .then(profile => {
            return getPreferences(profile.id);
        })
        .then(preferences => {
            console.log('User data loaded');
        })
        .catch(error => {
            console.error('Error:', error);
        });
}

// Good
async function loadUserData(userId) {
    try {
        const user = await getUserById(userId);
        const profile = await getProfileData(user.id);
        const preferences = await getPreferences(profile.id);
        console.log('User data loaded');
        return { user, profile, preferences };
    } catch (error) {
        console.error('Error loading user data:', error);
        throw error;
    }
}

Performance Best Practices

// Optimize data binding
// Use one-time binding for static data
// XML: text="{{ message, mode=oneTime }}"

// Minimize property changes
class OptimizedViewModel extends Observable {
    constructor() {
        super();
        this._batchUpdates = false;
        this._pendingUpdates = {};
    }

    startBatch() {
        this._batchUpdates = true;
    }

    endBatch() {
        this._batchUpdates = false;
        Object.keys(this._pendingUpdates).forEach(key => {
            super.set(key, this._pendingUpdates[key]);
        });
        this._pendingUpdates = {};
    }

    set(name, value) {
        if (this._batchUpdates) {
            this._pendingUpdates[name] = value;
        } else {
            super.set(name, value);
        }
    }
}

// Use efficient list operations
// Bad
items.forEach((item, index) => {
    if (item.shouldRemove) {
        items.splice(index, 1);
    }
});

// Good
const itemsToKeep = items.filter(item => !item.shouldRemove);
items.splice(0, items.length, ...itemsToKeep);

// Optimize image loading
const optimizeImageLoading = (imageView, url, placeholder) => {
    // Show placeholder immediately
    imageView.src = placeholder;

    // Load actual image asynchronously
    ImageSource.fromUrl(url)
        .then(imageSource => {
            imageView.imageSource = imageSource;
        })
        .catch(error => {
            console.error('Image loading failed:', error);
            // Keep placeholder or show error image
        });
};

// Use proper cleanup
class ComponentManager {
    constructor() {
        this.components = new Map();
        this.timers = new Set();
        this.subscriptions = new Set();
    }

    addTimer(timer) {
        this.timers.add(timer);
    }

    addSubscription(subscription) {
        this.subscriptions.add(subscription);
    }

    cleanup() {
        // Clear timers
        this.timers.forEach(timer => clearTimeout(timer));
        this.timers.clear();

        // Unsubscribe from observables
        this.subscriptions.forEach(sub => {
            if (sub && sub.unsubscribe) {
                sub.unsubscribe();
            }
        });
        this.subscriptions.clear();

        // Clear component references
        this.components.clear();
    }
}

Summary

NativeScript provides a powerful platform for building truly native mobile applications using web technologies:

Key Advantages: - True Native Performance: Direct access to native APIs without WebViews - Code Sharing: Share business logic across platforms while maintaining native UI - Framework Flexibility: Support for Angular, Vue.js, React, and vanilla JavaScript - Native UI: Platform-specific UI components that look and feel native - Plugin Ecosystem: Rich ecosystem of plugins for native functionality

Best Use Cases: - Apps requiring native performance and UI - Cross-platform apps with complex business logic - Teams with web development expertise - Apps needing extensive native API access - Enterprise applications with custom requirements

Considerations: - Steeper learning curve than hybrid frameworks - Larger app size compared to web-based solutions - Platform-specific testing required - Need to understand native platform concepts

NativeScript is ideal for developers who want to leverage web technologies while building truly native mobile applications with excellent performance and platform integration.