NativeScript Angular - txgz999/Mobile GitHub Wiki

App Name

App Name appears in the status bar when notification arrives. It also appears in the notification entry in the notification drawer. By default in a NativeScript project we don't need to set it explicitly. It is derived from the project folder name (e.g. ns-local-notification-test1 becomes nslocalnotificationtest1). We can set it explicitly be providing app_name value in /App_Resources/Android/values/strings.xml, see https://stackoverflow.com/questions/48172584/renaming-app-name-when-using-nativescript-advanced-seed

<string name="app_name">My Test App</string>

If we change the project folder name later after running the project, the app name would not change automatically. We can do the following to refresh it:

tns platform remove android
tns platform add android
tns run android --emulator

see https://github.com/NativeScript/nativescript-cli/issues/1836.

Theme

NativeScript provides a core theme, which contains 12 color themes. When we create a NativeScript project using tns create, the project template uses the default color theme. We can switch to a different color theme in src/app.css, e.g.

@import "~@nativescript/theme/css/core.css";
@import '~@nativescript/theme/css/blue.css';

Notice that that changes both the action bar background color, and the status bar background color.

NativeScript supports CSS. It also provides many class names that can be applied to various elements, similar to Bootstrap classes.

  • Headings: h1, ..., h6
  • Text: body, footnote, text-left/right/center, text-lowercase/uppercase/capitalize
  • Font: font-weight-normal/bold
  • Padding and Margin: m-t-2, valid values are 0, 2, 5, 10, 15, 20, 25, 30
  • Contextual Colors: text-primary/muted/danger, bg-primary/danger
  • Button: -primary, -outline, -rounded-sm/lg
  • Forms: nt-form, nt-input, nt-label, -border, -rounded (I guess nt means NativeScript Forms)
Button Width

There is no something similar to the wrap_content in setting button width, and by default it matches the parent width. One possible solution is to set a horizontalAlignment value, found in https://github.com/NativeScript/NativeScript/issues/5794

   <Button text="Schedule" horizontalAlignment="right"></Button>
Calling Native Android API

How do we use the features provided by Android API in NativeScript? One approach is to use NativeScript plugins, but what if we cannot find a plugin that provides a native feature we want? NativeScript allows to call native Android (and iOS) API directly. In the following I will discuss how to create a notification channel, a feature not exposed by the nativescript-local-notification plugin. For details, see

There are a few steps we need to do to add this support to call native API:

  • install tns-platform-declarations
npm install tns-platform-declarations -D
  • create reference.d.ts in the root project directory to contain the following:
/// <reference path="node_modules/tns-platform-declarations/android-26.d.ts" />

The reason I use 26 is notification channel added to Android API in version 26.

  • modify tsconfig.json to change in the compilerOptions section
"paths": {
  "~/*": [
    "src/*"
  ],
  "*": [
    "./node_modules/*"
  ]
}

to

"paths": {
  "~/*": [
    "src/*"
  ],
  "*": [
    "./node_modules/tns-core-modules/*",
    "./node_modules/*"
  ]
}

Now we can add code to create notification channel following the article by Renzo Castro Jurado: NativeScript: Push Notifications with Firebase Cloud Messaging

  • create utils/androidUtils.ts (look the code is almost identical to the native Android java code):
import * as utils from 'tns-core-modules/utils/utils';

export function createNotificationChannel(id: string, name: string) {
  const ANDROID_OREO_SDK_VERSION = 26;

  if (android.os.Build.VERSION.SDK_INT >= ANDROID_OREO_SDK_VERSION) {
    const notificationChannel = new android.app.NotificationChannel(id, name, android.app.NotificationManager.IMPORTANCE_HIGH);
    const context = utils.ad.getApplicationContext();
    const notificationManager = context.getSystemService(android.app.NotificationManager.class);
    notificationManager.createNotificationChannel(notificationChannel);
  }
}
  • call the function defined above in app.component.ts to create notification channel:
import { createNotificationChannel } from './utils/androidUtils';
...
createNotificationChannel("My Notification Channel", "Channel human readable title");
Data Binding

I encountered two issues on making screen to refresh as consequence of data change when using data binding in NativeScript Angular. These issues seems to happen only when the new item is added in an asynchronous call.

  1. my list view is bound to a data list. When a new item is added to the data list, the new item does not appear on the screen until I do something on the screen (e.g. clicking an item to expand it). Any way I fix the issue by calling the refresh method of the list view object manually
<ListView id="lv" [items]="notifications">
  <ng-template let-item="item" let-i="index">
  <StackLayout>
    <GridLayout columns="auto, auto, auto, *" orientation="horizontal" style="padding: 0;" (tap)="toggleDetailVisibility(item.id)">
      <Label col="0" [text]="i+1" style="margin: 0; font-size: 20px; font-weight: bold;"></Label>
import { getViewById, Page } from 'tns-core-modules/ui/page/page';
import { ListView } from 'tns-core-modules/ui/list-view';
...
constructor(private repository: NotificationRepository, private page: Page) { }

ngOnInit(): void {
  this.repository.getRecords().then(r => {
    this.notifications = r;
  });
  this.subscription = this.repository.NewNotificationReceived$.subscribe((id: number) => {
    this.repository.getRecord(id).then(n => {
      if (n){
        this.notifications.unshift(n);
        (<ListView> this.page.getViewById('lv')).refresh();
      }
    });
  });    
}

See https://stackoverflow.com/questions/41139922/listview-not-refreshing-fully-in-nativescript-with-angular for more details.

  1. My page title should contain a fixed part (the app name) plus the number of items in the list. I first tried to bind the title to a get property:
<ActionBar [title]="title"></ActionBar>
get title(): string {
  var title = "Notifications";
  if (this.notifications) title += " (" +  this.notifications.length + ")";
  return title;
}

but that does not work well when new item comes through asynchronous call. So I finally manually update the title as follows:

<ActionBar id="title"></ActionBar>
import { ActionBar } from 'tns-core-modules/ui/action-bar';
...
ngOnInit(): void {
  this.repository.getRecords().then(r => {
    this.notifications = r;
    this.updateTitle();
  });
  this.subscription = this.repository.NewNotificationReceived$.subscribe((id: number) => {
    this.repository.getRecord(id).then(n => {
      if (n){
        this.notifications.unshift(n);
        (<ListView> this.page.getViewById('lv')).refresh();
        this.updateTitle();
      }
    });
  });    
}

updateTitle() {
  var title =  "Notifications";
  if (this.notifications) title += " (" +  this.notifications.length + ")";
  (<ActionBar> this.page.getViewById("title")).title = title;
}
SQLite

Just like we install a plugin to support Firebase messaging, there is also a plugin to support SQLite. According to https://docs.nativescript.org/core-concepts/plugins:

NativeScript plugins are npm packages with some added native functionality. Therefore, finding, installing, and removing NativeScript plugins works a lot like working with npm packages you might use in your Node.js or front-end web development.

Therefore we can expect to add NativeScript plugins frequently when developing in NativeScript. To support SQLite, use

tns plugin add nativescript-sqlite

For details, see https://www.thepolyglotdeveloper.com/2016/10/using-sqlite-in-a-nativescript-angular-2-mobile-app/. There are two versions of this plugin. The free version does not support Typescript directly, this requires some extra work to be sued in an Angular Typescript project.

Company Laptop Setup

I tried to setup NativeScript on my company laptop, after installing Android Studio. This time the setup is extremely smooth. I only need to do the following:

  • set proxy info for tns: tns proxy set
  • set the ANDROID_HOME and JAVA_HOME environment variables, both values can be copied from Android Studio menu Files->Project Structure (C:\Users\myusername\AppData\Local\Android\Sdk and C:\Program Files\Android\Android Studio\jre on my machine)

Then I start the emulator and execute tns run Android —-emulator and it completes in a few minutes. I don’t even need to set NO_UPDATE_NOTIFIER. But later the execution becomes much slower, and again I have to resolve the issue by setting NO_UPDATE_NOTIFIER.

When I set up Android Studio, I had to add proxy information to gradle.properties file, so I don’t need to do it when setting up NativeScript. I didn’t add proxy information to the gradle wrapper properties file, and I checked that the proxy information is not in the global and local versions of this file. Therefore it is likely not needed.

Then I start to create my first NativeScript Angular project there. I can only use ng g to create new elements. The command tns generate returns error "Invalid rule result: Instance of class Promise". Based on the information in https://docs.nativescript.org/angular/tooling/angular-cli, it seems fine to use ng g.

When build, got a complaint of the .spec.ts file that comes with the new component. That file is for unit testing only, and I simply deleted. There are ways to avoid creating this file, see https://github.com/angular/angular-cli/issues/1256.

Another error I got is the following:

Execution failed for task ':app:mergeDexDebug'. Error: null, Cannot fit requested classes in a single dex file (# methods: 103646 > 65536).

Command gradlew.bat failed with exit code 1.

Found the solution in https://stackoverflow.com/questions/51109159/nativescript-cannot-fit-requested-classes-in-a-single-dex-file: add

multiDexEnabled true

in the defaultConfig section in platforms/android/app/build.gradle file in the NativeScript project.

Batch

I write a small batch file ns.bat, to automate the process from creating the project to building and deploying to an emulator. The whole process lasts 5 minutes.

if ("%1"=="") exit 1
set pname=%1
call tns create %pname% --template tns-template-hello-world-ng
call cd %pname%
call code .
call tns run android --emulator

To use it, start an emulator, then in cmd run something like ns.bat ns'.

Deploy to device

The recommended way is to use Nativescript Sidekick, but I believe we can also download and install the apk file directly, see https://www.androidpit.com/android-for-beginners-what-is-an-apk-file. The apk file is in platforms\android\app\build\outputs\apk\debug\app-debug.apk.

Resolve the tns command slowness

Both tns create and tns run took quite a while to finish (often over half hour) and there was a long wait time before seeing any progress on screen. That happened only in my office machine. Finally found the discussion

The solution is to stop npm's internal logic for checking if there's a newer version by setting the environment variable NO_UPDATE_NOTIFIER

setx NO_UPDATE_NOTIFIER 1

Open a new window after executing the command above, now tns create and tns run show progress in one minute and finish in 3 minutes.

There is also an option to give more details of progress in cmd:

tns create ns1 --ng --log trace
tns run android --emulator --log trace
tns debug

When I run tns run android --emulator in cmd, the output of the call to consolog.log goes to the cmd, and I have no easily to copy. Then I find

tns debug android --emulator`

When I run this one, cmd displays an url e.g. chrome-devtools://devtools/bundled/inspector.html?experiments=true&ws=localhost:40000 that I can open in Chrome, then the output goes there as well.

Local Build Summary
  • Install Android SDK and set the ANDROID_HOME environment variable. Since I already have it installed from Visual Studio 2019, I simply need to set ANDROID_HOME ( do that from cmd because the change in control panel is not saved)
setx ANDROID_HOME "C:\Program Files (x86)\Android\android-sdk"
  • Install JDK and set the JAVA_HOME environment variable. The Visual Studio with Xamarin installation includes installation of a JDK since Xamarin needs it. We can find its path from "Java Development Kit Location" in Visual Studio menu Tools->Options->Xamarin->Android Settings, then simply needs to set JAVA_HOME:
setx JAVA_HOME "C:\Program Files\Android\Jdk\microsoft_dist_openjdk_1.8.0.25"

Alternatively we can download JDK 12 (jdk-12.0.2_windows-x64_bin.zip) from oracle web site, unzip it to a folder, then set JAVA_HOME to point to it. Notice that don't use jdk 13 since it is currently not compatible to gradle

  • add proxy info to the gradle wrapper setting in C:\Users\mynetworkusername\AppData\Roaming\npm\node_modules\nativescript\vendor\gradle-plugin\gradle\wrapper\gradle-wrapper.properties:
systemProp.http.proxyHost=myproxyservername
systemProp.http.proxyPort=myproxyserverport
systemProp.http.proxyUser=myproxyserverusername
systemProp.http.proxyPassword=myproxyserverpassword
systemProp.https.proxyHost=myproxyservername
systemProp.https.proxyPort=myproxyserverport
systemProp.https.proxyUser=myproxyserverusername
systemProp.https.proxyPassword=myproxyserverpassword
  • add proxy info to the gradle setting in C:\Users\mynetworkusername\.gradle\gradle.properties (create one if not existing):
org.gradle.daemon=true
org.gradle.parallel=true
systemProp.http.proxyHost=myproxyservername
systemProp.http.proxyPort=myproxyserverport
systemProp.http.proxyUser=myproxyserverusername
systemProp.http.proxyPassword=myproxyserverpassword
systemProp.https.proxyHost=myproxyservername
systemProp.https.proxyPort=myproxyserverport
systemProp.https.proxyUser=myproxyserverusername
systemProp.https.proxyPassword=myproxyserverpassword

Now I can create a nativescript project and run it in emulator as follows:

  • tns create myprojectname --ng
  • cd myprojectname
  • run an Android emulator
  • tns run android --emulator

The last command uses webpack to watch the source code change. So we can open the project in VS Code, develop there, and the emulator refreshes whenever we save code.

My understanding is tns converts typescript source code to java source code, then uses jdk to compile java source code to bytecode, which can run on top of jre on Android. Gradle is the build tool.

Explore Local Build

First of all, tns can use the Android emulators installed from Visual Studio 2019. tns device android lists all the running emulators, and tns device android --available-devices lists all the installed emulators. Notice that don’t use tns device which would prompt to install iTunes.

I tried to make tns run android —-emulator to work. The document https://docs.nativescript.org/start/ns-setup-win shows the requirements for the machine for local build. But that document requires first to install Chocolatey, and I could not make it to work in office. So I tried to explore based on error messages returned from executing tns run android --emulator.

  • first it complained about missing ANDROID_HOME, so I set it to the folder that contains those sdk’s installed from Visual Studio, which is C:\Program Files (x86)\Android\android-sdk on my machine. I have problem to change environment variables from control panel UI, so I set it in cmd using setx ANDROID_HOME

  • then it complained missing javac and JAVA_HOME. So I installed Java SDK, my machine already has a jre (Java runtime) but that is not enough. I downloaded the SDK as a zip file, unzip it then set JAVA_HOME

  • then it tried to download gradle from internet, https://services.gradle.org/distributions/gradle-5.4.1-all.zip to be exact, and got a connection timeout error.

  • then I got an error "General error during semantic analysis: Unsupported class file major version 57 Could not compile setting file settings.gradle". Found the reason in https://github.com/exercism/support/issues/73, which is the jdk I installed, version 13.0.2, is too new to gradle, I changed to jdk 12.0.2

  • then I got error "Could not resolve all artifacts for configuration ':classpath'", in detail, it is regarding the classpath set in platform/android/build.grade: could not resolve com.android.tools.build:gradle:3.5.1 and org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41. The internal error is Could not GET https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.5.1/gradle-3.5.1.pom and https://dl.google.com/dl/android/maven2//org/jetbrains/kotlin/kotlin-gradle-plugin-1.3.41.pom, probably still a proxy issue

  • finally I find the right place to add proxy setting for gradle, tanks to https://docs.kony.com/konylibrary/visualizer/visualizer_user_guide/Content/SUG_Android.htm. It is in C:\Users\mynetworkusername\.gradle\gradle.properties. I create this file with the following content:

    org.gradle.daemon=true
    org.gradle.parallel=true
    systemProp.http.proxyHost=myproxyservername
    systemProp.http.proxyPort=myproxyserverport
    systemProp.http.proxyUser=myproxyserverusername
    systemProp.http.proxyPassword=myproxyserverpassword
    systemProp.https.proxyHost=myproxyservername
    systemProp.https.proxyPort=myproxyserverport
    systemProp.https.proxyUser=myproxyserverusername
    systemProp.https.proxyPassword=myproxyserverpassword

    Finally my tns run android --emulator succeeds and the app runs on the emulator. I believe the proxy setting added to gradle-wrapper.properties are useless

  • now tns doctor indicates all requirements are met

  • NativeScript Advanced Setup: macOS

  • The Gradle Wrapper

NativeScript
npm install -g nativescript
tns create MyApp --ng
cd MyApp
tns preview

Or

  • tns create foo and then from prompt, choose Angular as style and "Hello World" as template; or enter directly tns create foo --template tns-template-blank-ng

Or

  • ng new --collection=@nativescript/schematics foo --shared

We still need to do the following in order to use ng-cli commands such as ng g component

  • npm i -D @angular/cli
  • npm i -D @nativescript/schematics

For details, see https://docs.nativescript.org/angular/tooling/angular-cli. Otherwise we would have the following error when execute ng g component

An unhandled exception occurred: Cannot find module '@schematics/angular/utility/parse-name'

On Windows and Linux systems you can only use the tns command to develop Android apps. This is because the NativeScript CLI uses Xcode to build iOS apps, which is only available on macOS.

nativescript provides a 'playground' website that allows developers to try it without installing anything on desktop, and there is a 16-section tutorial to follow there. That playground is like an IDE where you can add/delete your own files (instead of using 'ng g'), has intelli-sense support in coding, and every time you save, the url is updated with a new version number, so you can go back there anytime using the url. You can click the QR Code button to run your app on your phone then every time you save, the app is updated automatically on the phone.

The biggest benefit of using this technology to us, I believe, is we can easily have a web version and a mobile version of the same app, take a look at https://docs.nativescript.org/angular/code-sharing/intro.

The scanning of the QR Code on the nativescript playground works for me on both my home machine and office machine. But when I use nativescript locally via tns preview, the QE code only works on my home machine. I notice the QR code generated in my home from tns preview is quite simple, and after scanning opens nativescript preview app via safari, but the QR code generated in office is much more complex and after scanning opens the preview app directly (but the app is not really loaded).

Using online decoding of the QR code at https://blog.qr4.nl/Online-QR-Code-Decoder.aspx, I realized that the QR code generated at home looks like

https://play.nativescript.org/s/t9aoku1

but the one from office looks like

nsplay://boot?instanceId=__cJPL85k&pKey=pub-c-d7893276-cc78-4d18-8ab0-becba06e43de&sKey=sub-c-3dad1ebe-aaa3-11e8-8027-363023237e0b&template=play-ng&hmr=1

Found some information about tns preview in https://nstudio.io/blog/tns-preview

The preview app has some glaring limitations, however some of them can be worked around. The first is that you must have working internet as the link between the CLI to the preview app on the device is via cloud servers. Your app is transmitted via the internet to your device; and the device creates another channel that is transmitted back to the CLI the console so you can see any errors that occurred.

I then tried tns proxy set. After doing that, the generated QR code looks simpler and contains similar content as I got from home, but the app still does not run on preview app, I suspect the local tns has problem to push app to the cloud server.

Resources

⚠️ **GitHub.com Fallback** ⚠️