Saltar a contenido

NativeScript Cheatsheet

■h1 títuloNativeScript - Aplicaciones móviles nativas con JavaScript seleccionado/h1 "Clase de inscripción" NativeScript es un marco de código abierto para la construcción de aplicaciones móviles verdaderamente nativas usando JavaScript, TypeScript, Angular, Vue.js o React. Proporciona acceso directo a los componentes nativos de API y UI sin WebViews. ▪/p] ■/div titulada

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada ■/div titulada

Cuadro de contenidos

Instalación

Prerrequisitos

# 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 Instalación

# 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

Comienzo

Crear un nuevo proyecto

# 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

Plantillas del proyecto

# 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

Estructura del proyecto

Estructura básica

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 (Punto de entrada)

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>

página principal.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 Comandos

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

Comandos de Desarrollo

# 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

Construir comandos

# 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

Gestión de plugins

# 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

Conceptos básicos

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);

Página

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();
}

Aplicación 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

Componentes básicos

<!-- 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 }}" />

Componentes de lista

<!-- 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>

Componentes avanzados

<!-- 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>

Disposiciones

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>
<!-- 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

Aparación de un agua

<!-- 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' }}" />

2-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 }}" />

Reunión de eventos

<!-- 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 Apoyo

# 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%);
        }
    }
}

Plataforma-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;
}

Estilo dinámico

// 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();
}

API de plataforma

Información sobre dispositivos

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
}

Sistema de archivos

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 Solicitudes

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);
    });

Almacenamiento local

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

### Plugins de núcleo
```bash
# 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

Usando Plugin de Cámara

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);
        });
}

Utilizando 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);
}

Desarrollo de Plugin personalizado

// 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);
    });

Integración angular

Configuración del proyecto angular

# 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
```_

### Módulo de aplicación
```typescript
// 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 { }

Componente

// 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>

Servicio

// 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

Configuración del proyecto Vue

# Create Vue project
ns create MyVueApp --vue

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

Aplicación principal

// 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

Configuración del proyecto React

# Create React project
ns create MyReactApp --react

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

Componente de aplicación

// 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;

Componente de reacción

// 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} />
            )}
        />
    );
}

Pruebas

Pruebas de unidad

# 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

Configuración de prueba

// 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
    });
};

Ejemplo de prueba de unidad

// 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 Pruebas

# 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');
    });
});
```_

## Ejecución

### Gestión de memoria
```javascript
// 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 = [];
    }
}

Optimización de imagen

// 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);
        });
}

Función de la Lista

// 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 };
}

Despliegue

Despliegue de Android

# 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/

Despliegue de iOS

# 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

Integración continua

# .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/

Buenas prácticas

Estructura del proyecto

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;
    }
}

Prácticas óptimas de rendimiento

// 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();
    }
}

-...

Resumen

NativeScript ofrece una poderosa plataforma para construir aplicaciones móviles verdaderamente nativas usando tecnologías web:

** Ventajas clave:** - True Native Performance: Acceso directo a las API nativas sin WebViews - Code Sharing: Comparta la lógica empresarial a través de plataformas manteniendo la UI nativa - Framework Flexibility: Support for Angular, Vue.js, React, and vanilla JavaScript - UI nativa: Plataforma específica UI componentes que se ven y se sienten nativos - Plugin Ecosystem: Ecosistema rico de plugins para funcionalidad nativa

Mejores casos de uso: - Aplicaciones que requieren rendimiento nativo y UI - Aplicaciones multiplataforma con lógica empresarial compleja - Equipos con experiencia en desarrollo web - Aplicaciones que necesitan un amplio acceso a API nativa - Aplicaciones de la empresa con requisitos personalizados

Consideración: - Curva de aprendizaje estable que marcos híbridos - Tamaño de aplicación más grande comparado con soluciones basadas en la web - Pruebas específicas de la plataforma necesarias - Necesidad de entender conceptos de plataforma nativa

NativeScript es ideal para desarrolladores que quieren aprovechar las tecnologías web mientras construyen aplicaciones móviles verdaderamente nativas con excelente rendimiento e integración de plataformas.

" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }

función generaPDF() { ventana.print(); } ■/script título