Back to Blog Home
← all posts

NativeScript for Capacitor v2 released

April 25, 2022 — by Technical Steering Committee (TSC)

With @nativescript/capacitor 2.0 comes more maturity with advancements including:

  • Ability to use NativeScript plugins from Capacitor
  • Ability to use @nativescript/core from Capacitor
  • Fast startup time (~ < 20ms)
  • Using v8 engine for both iOS and Android

Last year we introduced v1 which brought the ability to take advantage of NativeScript from Capacitor for the very first time and we continue to be inspired by the effective solutions it makes possible.

Broadening Capacitor options

With the ability to utilize NativeScript plugins from Capacitor, the community has more options to solve development challenges.

Let's take for example a Capacitor Plugin Request for Zip handling found here.

You may come across a plugin which has support for one platform but perhaps not another. In this case, a skilled contributor has provided an Android zip plugin here however it does not currently support iOS.

If you are familiar with Swift and Objective C, you may be able to help contribute to that plugin to add iOS support but what if you're not?

Let's consider you are working on a mobile project under a tight deadline and you need Zip handling support for iOS right now.

Using @nativescript/zip with Capacitor

By installing @nativescript/capacitor following the docs here you can now use various NativeScript plugins such as:

It supports iOS and Android in addition to even supporting password protected zip/unzip! 👀

What's neat about the ability to use NativeScript plugins in your Capacitor projects is you can use only what you need. You could use capacitor-zip for Android and you could also use @nativescript/zip for iOS.

@nativescript/capacitor gives you an isolated development area, src/nativescript, alongside your Ionic codebase only active when Capacitor is running. It also isolates NativeScript dependencies for clean architectural control and organization.

  1. Install NativeScript plugin:
cd src/nativescript
npm install @nativescript/zip
  1. Modify src/native-custom.d.ts to contain the strongly typed utility you want to use from Ionic. This file helps you maintain a strongly typed interface to any custom NativeScript utilties you expose to your Ionic codebase.
/**
 * Define your own custom strongly typed native helpers here.
 */
export interface ZipOptions {
  directory: string;
  archive?: string;
  unzip?: boolean;
}

export interface nativeCustom {
  // example provided with every install
  openNativeModalView(): void;

  /*
   * Added for our new zip/unzip abilities!
   */
  fileZip(options: ZipOptions): void;
}
  1. Add a new file for our zip features: src/nativescript/examples/zip.ts

  2. Import into src/nativescript/index.ts (the NativeScript entry point):

/**
 * Zip/Unzip handling
 */
import './examples/zip';
  1. Implement your new utility method in src/nativescript/examples/zip.ts:
import { Zip } from '@nativescript/zip';
import { path, knownFolders } from '@nativescript/core';
import { ZipOptions } from '../../native-custom';

native.fileZip = function (options: ZipOptions) {
  const directory = path.join(knownFolders.documents().path, options.directory);
  const archive = path.join(knownFolders.temp().path, options.archive);
  if (options.unzip) {
    console.log('Unzipping archive:', archive);
    console.log('Into folder:', directory);

    Zip.unzip({
      directory,
      archive,
      onProgress: (progress) => {
        console.log('unzip progress:', progress);
      },
    }).then((filePath) => {
      console.log('Unzipped files are here:', filePath);
    });
  } else {
    console.log('Zipping directory:', directory);
    console.log('Into archive:', archive);

    Zip.zip({
      directory,
      archive,
      onProgress: (progress) => {
        console.log('zip progress:', progress);
      },
    }).then((filePath) => {
      console.log('Zipped file is here:', filePath);
    });
  }
};
  1. Use in your Ionic codebase, for example: src/app/explore-container.component.ts:
import { native } from '@nativescript/capacitor';

// ...
export class ExploreContainerComponent {
  fileZip(unzip?: boolean) {
    const directory = 'assets';
    const archive = 'assets.zip';
    if (unzip) {
      native.fileUnzip({
        directory,
        archive,
      });
    } else {
      native.fileZip({
        directory,
        archive,
      });
    }
  }
}
  1. Bind to your view for user interaction:
<div id="container">
  <p><a (click)="fileZip()">Zip File</a></p>
  <p><a (click)="fileZip(true)">Unzip Files</a></p>
</div>

This shows how you can take advantage of utilities within @nativescript/core itself alongside anything you need.

Build and Run

For any NativeScript plugin you use, there can be iOS or Android dependencies associated with them. You can find out by opening any node_modules package to see if a platforms folder exists. Capacitor also needs to know about these dependencies.

With @nativescript/zip we can see them, for example in VS Code, with the following:

code src/nativescript/node_modules/@nativescript/zip

You will see contents, in part, as follows:

platforms
  android
    include.gradle
  ios
    Podfile
index.android.js
index.d.ts
index.ios.js

The compiled index.{android,ios}.js files are the implementation and the platforms folder specifies the native dependencies for each applicable platform. Opening platforms/ios/Podfile reveals:

pod 'SSZipArchive', '~> 2.4.3'

That is the iOS Cocoapod that @nativescript/zip uses.

Open ios/App/Podfile and let Capacitor's iOS build use it as well:

def capacitor_pods
  pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
  pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
  pod 'NativescriptCapacitor', :path => '../../node_modules/@nativescript/capacitor'
end

target 'App' do
  capacitor_pods
  # Add your Pods here

  # NativeScript
  pod 'NativeScript'
  pod 'NativeScriptUI'

  # Zip/Unzip
  pod 'SSZipArchive', '~> 2.4.3'
end

We can now prepare our build for Capacitor with:

yarn build:mobile

This will build your web app followed by bundling your src/nativescript in isolation.

You now have fully operational zip/unzip handling:

ionic cap run ios -l --external
NativeScript Zip plugin working from Capacitor

Developer Notes

Right now with v2, when using NativeScript plugins with native dependencies (if they include a platforms/ios or platforms/android folder), you can add them manually to Capacitor Podfile (for iOS) or gradle (for Android).

In future versions, these will be added automatically.

There are a lot of features inside @nativescript/core and we have only tested a few with Capacitor at this time but feel free to experiment!

Example: Create a new file and open it

This shows using @nativescript/core file APIs to create a file and open it:

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

native.createAndOpenFile = function (
  content: string,
  filename = 'new-file.txt'
) {
  const newFile = File.fromPath(
    path.join(knownFolders.documents().path, 'assets', filename)
  );
  newFile.writeTextSync(content, (err) => {
    if (err) {
      console.log('err:', err);
      return;
    }
  });

  Utils.openFile(newFile.path, 'File creation from {N} for Capacitor.');
};

Example: Ionic/Capacitor <--> NativeScript Event Communication

When needing to communicate events back and forth between Ionic/Capacitor and your NativeScript utilities you can use:

  • notifyEvent: (name: string, data?: any) => void: Notify event name with optional data.
  • onEvent: (name: string, callback: Function) => void: Listen to event name with callback.
  • removeEvent: (name: string, callback?: Function) => void: Remove event listeners.

For example using the zip example above you can use notifyEvent in your NativeScript utilities to emit events:

  • src/nativescript/examples/zip.ts
// ...
import { notifyEvent } from "@nativescript/capacitor/bridge";

native.fileZip = function (options: ZipOptions) {
    // ...

    Zip.zip({
      directory,
      archive,
      onProgress: (progress) => {
        notifyEvent('zipProgress', progress);
      },
    }).then((filePath) => {
      notifyEvent('zipComplete', filePath);
    });
  }
};

You can then listen to those events in your Ionic components:

  • src/app/explore-container.component.ts:
import { native } from '@nativescript/capacitor';

// ...
export class ExploreContainerComponent implements OnInit {
  ngOnInit() {
    native.onEvent('zipComplete', (filepath) => {
      console.log('ionic zip complete:', filepath);
    });
    native.onEvent('zipProgress', (progress) => {
      console.log('ionic zip progress:', progress);
    });
  }
}

OpenJS Presentation and Other Video Learning

Using NativeScript with Capacitor can be further explored from these additional resources:

https://www.youtube.com/watch?v=OInjFvniccU

https://www.youtube.com/watch?v=AHcL_UbnnPY

Help shape the future

You can contribute in a number of helpful ways:

Celebrate the wonders and joys of JavaScript development with us anytime.