docs.sheetjs.com/docz/docs/03-demos/17-mobile/02-nativescript.md

817 lines
24 KiB
Markdown
Raw Normal View History

2023-01-05 23:33:49 +00:00
---
2023-10-13 10:04:30 +00:00
title: Native Sheets in NativeScript
sidebar_label: NativeScript
2023-02-28 11:40:44 +00:00
pagination_prev: demos/static/index
2023-01-05 23:33:49 +00:00
pagination_next: demos/desktop/index
sidebar_position: 2
sidebar_custom_props:
summary: JS + Native Elements
---
2023-04-27 09:12:19 +00:00
import current from '/version.js';
2023-05-07 13:58:36 +00:00
import CodeBlock from '@theme/CodeBlock';
2023-04-27 09:12:19 +00:00
2023-10-13 10:04:30 +00:00
export const g = {style: {color:"green"}};
export const r = {style: {color:"red"}};
2024-04-08 03:55:10 +00:00
export const y = {style: {color:"gold"}};
2023-10-13 10:04:30 +00:00
[NativeScript](https://nativescript.org/) is a mobile app framework. It builds
iOS and Android apps that use JavaScript for describing layouts and events.
[SheetJS](https://sheetjs.com) is a JavaScript library for reading and writing
data from spreadsheets.
This demo uses NativeScript and SheetJS to process and generate spreadsheets.
We'll explore how to load SheetJS in a NativeScript app; parse and generate
spreadsheets stored on the device; and fetch and parse remote files.
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
The ["Complete Example"](#complete-example) creates an app that looks like the
screenshots below:
2023-01-05 23:33:49 +00:00
2023-04-03 09:09:59 +00:00
<table><thead><tr>
2023-10-13 10:04:30 +00:00
<th><a href="#complete-example">iOS</a></th>
<th><a href="#complete-example">Android</a></th>
2023-04-03 09:09:59 +00:00
</tr></thead><tbody><tr><td>
2023-01-05 23:33:49 +00:00
2023-05-21 07:03:46 +00:00
![iOS screenshot](pathname:///nativescript/ios.png)
2023-01-05 23:33:49 +00:00
2023-05-07 17:43:21 +00:00
</td><td>
2023-05-21 07:03:46 +00:00
![Android screenshot](pathname:///nativescript/and.png)
2023-05-07 17:43:21 +00:00
2023-04-03 09:09:59 +00:00
</td></tr></tbody></table>
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
:::info pass
2023-01-05 23:33:49 +00:00
2023-04-03 09:09:59 +00:00
The discussion covers the NativeScript + Angular integration. Familiarity with
Angular and TypeScript is assumed.
2023-10-13 10:04:30 +00:00
:::
2023-12-05 03:46:54 +00:00
:::note Tested Deployments
This demo was tested in the following environments:
2024-04-08 03:55:10 +00:00
**Simulators**
| OS | Device | NS | Dev Platform | Date |
|:-----------|:--------------------|:---------|:-------------|:-----------|
| Android 34 | Pixel 3a | `8.6.1` | `darwin-x64` | 2023-12-04 |
| iOS 17.0.1 | iPhone SE (3rd gen) | `8.6.1` | `darwin-x64` | 2023-12-04 |
| Android 34 | Pixel 3a | `8.6.5` | `win10-x64` | 2024-04-07 |
**Real Devices**
| OS | Device | NS | Date |
|:-----------|:--------------------|:---------|:-----------|
| Android 30 | NVIDIA Shield | `8.6.5` | 2024-04-07 |
| iOS 15.1 | iPad Pro | `8.6.1` | 2023-12-04 |
2023-12-05 03:46:54 +00:00
:::
2023-10-13 10:04:30 +00:00
:::warning Telemetry
Before starting this demo, manually disable telemetry.
2023-12-05 03:46:54 +00:00
NativeScript 8.6.1 split the telemetry into two parts: "usage" and "error". Both
2023-10-13 10:04:30 +00:00
must be disabled separately:
```bash
npx -p nativescript ns usage-reporting disable
npx -p nativescript ns error-reporting disable
```
To verify telemetry was disabled:
```bash
npx -p nativescript ns usage-reporting status
npx -p nativescript ns error-reporting status
```
:::
## Integration Details
The [SheetJS NodeJS Module](/docs/getting-started/installation/nodejs) can be
imported from any component or script in the app.
2023-01-05 23:33:49 +00:00
The `@nativescript/core/file-system` package provides classes for file access.
2023-04-03 09:09:59 +00:00
The `File` class does not support binary data, but the file access singleton
2024-04-08 03:55:10 +00:00
from `@nativescript/core` does support reading and writing `ArrayBuffer` data.
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
Reading and writing data require a URL. The following snippet searches typical
2023-04-03 09:09:59 +00:00
document folders for a specified filename:
2023-01-05 23:33:49 +00:00
```ts
2023-04-03 09:09:59 +00:00
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
2023-01-05 23:33:49 +00:00
2023-04-03 09:09:59 +00:00
function get_url_for_filename(filename: string): string {
2023-01-05 23:33:49 +00:00
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
2023-04-03 09:09:59 +00:00
return path.normalize(target.path + "///" + filename);
2023-01-05 23:33:49 +00:00
}
```
2024-04-08 03:55:10 +00:00
### App Configuration
Due to privacy concerns, apps must request file access. There are special APIs
for accessing data and are subject to change in future platform versions.
<details>
<summary><b>Technical Details</b> (click to show)</summary>
2024-04-08 03:55:10 +00:00
**Android**
Android security has evolved over the years. In newer Android versions, the
following workarounds were required:
- `READ_EXTERNAL_STORAGE` and `WRITE_EXTERNAL_STORAGE` allow apps to access
files outside of the app scope. These are required for scoped storage access.
When the demo was last tested, this option was enabled by default.
- `android:requestLegacyExternalStorage="true"` enabled legacy behavior in some
older releases.
The manifest is saved to `App_Resources/Android/src/main/AndroidManifest.xml`:
```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted lines)"
<application
<!-- highlight-next-line -->
android:requestLegacyExternalStorage="true"
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true">
```
- Permissions must be explicitly requested.
`@nativescript-community/perms` is a community module for managing permissions:
```ts title="App script or component"
import { request } from '@nativescript-community/perms';
import { File } from '@nativescript/core/file-system';
```
Storage access must be requested before writing data:
```ts title="App script or component (before writing file)"
/* request permissions */
const res = await request('storage');
```
The external paths can be resolved using the low-level APIs:
```ts title="App script or component (writing to downloads folder)"
/* find Downloads folder */
const dl_dir = android.os.Environment.DIRECTORY_DOWNLOADS;
const dl = android.os.Environment.getExternalStoragePublicDirectory(dl_dir).getAbsolutePath();
/* write to file */
File.fromPath(dl + "/SheetJSNS.xls").writeSync(data);
```
</details>
2023-05-21 07:03:46 +00:00
### Reading Local Files
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
`getFileAccess().readBufferAsync` can read data into an `ArrayBuffer` object.
The SheetJS `read` method[^1] can parse this data into a workbook object.[^2]
2023-01-05 23:33:49 +00:00
```ts
2023-04-03 09:09:59 +00:00
import { getFileAccess } from '@nativescript/core';
2023-05-21 07:03:46 +00:00
import { read } from 'xlsx';
2023-04-03 09:09:59 +00:00
/* find appropriate path */
const url = get_url_for_filename("SheetJSNS.xls");
/* get data */
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url);
2023-01-05 23:33:49 +00:00
/* read workbook */
2023-04-03 09:09:59 +00:00
const wb = read(ab);
2023-01-05 23:33:49 +00:00
```
2023-10-13 10:04:30 +00:00
After parsing into a workbook, the `sheet_to_json`[^3] method can generate row
data objects:
```ts
import { utils } from 'xlsx';
/* grab first sheet */
const wsname: string = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
/* generate array of row objects */
const data = utils.sheet_to_json(ws);
```
2023-05-21 07:03:46 +00:00
### Writing Local Files
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
The SheetJS `write` method[^4] with the option `type: "binary"` will generate
`Uint8Array` objects. `getFileAccess().writeBufferAsync` can write data from a
`Uint8Array` object to the device.
iOS supports `Uint8Array` directly but Android requires a true array of numbers:
2023-01-05 23:33:49 +00:00
```ts
2023-04-03 09:09:59 +00:00
import { getFileAccess } from '@nativescript/core';
2023-05-21 07:03:46 +00:00
import { write } from 'xlsx';
2023-01-05 23:33:49 +00:00
2023-04-03 09:09:59 +00:00
/* find appropriate path */
const url = get_url_for_filename("SheetJSNS.xls");
/* generate Uint8Array */
const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'binary' });
/* attempt to save Uint8Array to file */
2023-05-07 17:43:21 +00:00
await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
2023-01-05 23:33:49 +00:00
```
2023-10-13 10:04:30 +00:00
A worksheet can be generated from an array of row objects with the SheetJS
`json_to_sheet` method[^5]. After generating an array, the `book_new` and
`book_append_sheet` methods[^6] can create the workbook.
2023-05-21 07:03:46 +00:00
### Fetching Remote Files
`getFile` from `@nativescript/core/http` can download files. After storing the
2023-10-13 10:04:30 +00:00
file in a temporary folder, `getFileAccess().readBufferAsync` can read the data
and the SheetJS `read` method[^7] can parse the file:
2023-05-21 07:03:46 +00:00
```ts
import { knownFolders, path, getFileAccess } from '@nativescript/core'
import { getFile } from '@nativescript/core/http';
import { read } from 'xlsx';
/* generate temporary path for the new file */
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
/* download file */
const file = await getFile("https://sheetjs.com/pres.xlsx", temp)
/* get data */
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(file.path);
/* read workbook */
const wb = read(ab);
```
2023-10-13 10:04:30 +00:00
## Complete Example
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
### Platform Configuration
0) Disable telemetry:
```bash
npx -p nativescript ns usage-reporting disable
npx -p nativescript ns error-reporting disable
```
1) Follow the official Environment Setup instructions[^8].
:::caution pass
When the demo was last tested, the latest version of the Android API was 34.
NativeScript did not support that API level. The exact error message from
`npx -p nativescript ns doctor ios` clearly stated supported versions:
2024-04-08 03:55:10 +00:00
<pre>
<span {...r}></span> No compatible version of the Android SDK Build-tools are installed on your system. You can install any version in the following range: '&gt;=23 &lt;=33'.
</pre>
2023-10-13 10:04:30 +00:00
The SDK Platform `Android 13.0 ("Tiramisu")` was compatible with NativeScript.
Until NativeScript properly supports API level 34, "Tiramisu" must be used.
This requires installing the following packages from Android Studio:
- `Android 13.0 ("Tiramisu")` API Level `33`
- `Android SDK Build-Tools` Version `33.0.2`
:::
2) Test the local system configuration for Android development:
```bash
npx -p nativescript ns doctor android
```
In the last macOS test, the following output was displayed:
<details open>
<summary><b>Expected output</b> (click to hide)</summary>
2023-10-13 10:04:30 +00:00
<pre>
<span {...g}></span> Getting environment information{'\n'}
{'\n'}
<b>No issues were detected.</b>{'\n'}
<span {...g}></span> Your ANDROID_HOME environment variable is set and points to correct directory.{'\n'}
<span {...g}></span> Your adb from the Android SDK is correctly installed.{'\n'}
<span {...g}></span> The Android SDK is installed.{'\n'}
<span {...g}></span> A compatible Android SDK for compilation is found.{'\n'}
<span {...g}></span> Javac is installed and is configured properly.{'\n'}
<span {...g}></span> The Java Development Kit (JDK) is installed and is configured properly.{'\n'}
<span {...g}></span> Getting NativeScript components versions information...{'\n'}
2023-12-05 03:46:54 +00:00
<span {...g}></span> Component nativescript has 8.6.1 version and is up to date.
2023-10-13 10:04:30 +00:00
</pre>
</details>
3) Test the local system configuration for iOS development (macOS only):
```bash
npx -p nativescript ns doctor ios
```
In the last macOS test, the following output was displayed:
<details open>
<summary><b>Expected output</b> (click to hide)</summary>
2023-10-13 10:04:30 +00:00
<pre>
<span {...g}></span> Getting environment information{'\n'}
{'\n'}
2024-04-08 03:55:10 +00:00
<b>No issues were detected.</b>{'\n'}
2023-10-13 10:04:30 +00:00
<span {...g}></span> Xcode is installed and is configured properly.{'\n'}
<span {...g}></span> xcodeproj is installed and is configured properly.{'\n'}
<span {...g}></span> CocoaPods are installed.{'\n'}
<span {...g}></span> CocoaPods update is not required.{'\n'}
<span {...g}></span> CocoaPods are configured properly.{'\n'}
<span {...g}></span> Your current CocoaPods version is newer than 1.0.0.{'\n'}
<span {...g}></span> Python installed and configured correctly.{'\n'}
<span {...g}></span> The Python 'six' package is found.{'\n'}
2023-12-05 03:46:54 +00:00
<span {...g}></span> Xcode version 15.0.1 satisfies minimum required version 10.{'\n'}
2023-10-13 10:04:30 +00:00
<span {...g}></span> Getting NativeScript components versions information...{'\n'}
2023-12-05 03:46:54 +00:00
<span {...g}></span> Component nativescript has 8.6.1 version and is up to date.
2023-10-13 10:04:30 +00:00
</pre>
</details>
2023-01-05 23:33:49 +00:00
2023-05-07 17:43:21 +00:00
### Base Project
2023-10-13 10:04:30 +00:00
4) Create a skeleton NativeScript + Angular app:
2023-01-05 23:33:49 +00:00
```bash
2023-10-13 10:04:30 +00:00
npx -p nativescript ns create SheetJSNS --ng
2023-01-05 23:33:49 +00:00
```
2023-10-13 10:04:30 +00:00
5) Launch the app in the android simulator to verify the app:
2023-01-05 23:33:49 +00:00
```bash
cd SheetJSNS
2023-10-13 10:04:30 +00:00
npx -p nativescript ns run android
2023-01-05 23:33:49 +00:00
```
(this may take a while)
Once the simulator launches and the test app is displayed, end the script by
2024-04-08 03:55:10 +00:00
selecting the terminal and entering the key sequence `CTRL + C`. On Windows,
if prompted to `Terminate batch job`, type `y` and press Enter.
2023-01-05 23:33:49 +00:00
2023-12-05 03:46:54 +00:00
:::note pass
If the emulator is not running, `nativescript` may fail with the message:
```
Emulator start failed with: No emulator image available for device identifier 'undefined'.
```
2024-01-17 20:22:38 +00:00
:::
2023-10-13 10:04:30 +00:00
### Add SheetJS
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
:::note pass
The goal of this section is to display the SheetJS library version number.
:::
2024-04-08 03:55:10 +00:00
6) From the project folder, install the SheetJS NodeJS module:
<CodeBlock language="bash">{`\
npm i --save https://cdn.sheetjs.com/xlsx-${current}/xlsx-${current}.tgz`}
</CodeBlock>
2023-10-13 10:04:30 +00:00
7) Edit `src/app/item/items.component.ts` so that the component imports the
SheetJS version string and adds it to a `version` variable in the component:
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
```ts title="src/app/item/items.component.ts (add highlighted lines)"
2023-01-05 23:33:49 +00:00
// highlight-next-line
import { version } from 'xlsx';
import { Component, OnInit } from '@angular/core'
2023-04-03 09:09:59 +00:00
// ...
2023-01-05 23:33:49 +00:00
export class ItemsComponent implements OnInit {
items: Array<Item>
// highlight-next-line
version = `SheetJS - ${version}`;
constructor(private itemService: ItemService) {}
2023-04-03 09:09:59 +00:00
// ...
2023-01-05 23:33:49 +00:00
```
2023-10-13 10:04:30 +00:00
8) Edit the template `src/app/item/items.component.html` to reference `version`
in the title of the action bar:
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
```xml title="src/app/item/items.component.html (edit highlighted line)"
2023-01-05 23:33:49 +00:00
<!-- highlight-next-line -->
<ActionBar [title]="version"></ActionBar>
<GridLayout>
2023-04-03 09:09:59 +00:00
<!-- ... -->
2023-01-05 23:33:49 +00:00
```
2024-04-08 03:55:10 +00:00
9) End the script and relaunch the app in the Android simulator:
2023-10-13 10:04:30 +00:00
```bash
npx -p nativescript ns run android
```
The title bar should show the version.
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
![NativeScript Step 4](pathname:///nativescript/step4.png)
2023-01-05 23:33:49 +00:00
2023-05-21 07:03:46 +00:00
### Local Files
2023-10-13 10:04:30 +00:00
10) Add the Import and Export buttons to the template:
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
```xml title="src/app/item/items.component.html (add highlighted lines)"
2023-01-05 23:33:49 +00:00
<ActionBar [title]="version"></ActionBar>
<!-- highlight-start -->
<StackLayout>
<StackLayout orientation="horizontal">
<Button text="Import File" (tap)="import()" style="padding: 10px"></Button>
<Button text="Export File" (tap)="export()" style="padding: 10px"></Button>
</StackLayout>
<!-- highlight-end -->
<ListView [items]="items">
2023-04-03 09:09:59 +00:00
<!-- ... -->
2023-01-05 23:33:49 +00:00
</ListView>
<!-- highlight-next-line -->
</StackLayout>
```
2023-10-13 10:04:30 +00:00
11) Add the `import` and `export` methods in the component script:
2023-01-05 23:33:49 +00:00
```ts title="src/app/item/items.component.ts"
// highlight-start
import { version, utils, read, write } from 'xlsx';
2023-04-03 09:09:59 +00:00
import { Dialogs, getFileAccess } from '@nativescript/core';
import { Folder, knownFolders, path } from '@nativescript/core/file-system';
2023-01-05 23:33:49 +00:00
// highlight-end
import { Component, OnInit } from '@angular/core'
import { Item } from './item'
import { ItemService } from './item.service'
// highlight-start
2023-04-03 09:09:59 +00:00
function get_url_for_filename(filename: string): string {
2023-01-05 23:33:49 +00:00
const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic();
2023-04-03 09:09:59 +00:00
return path.normalize(target.path + "///" + filename);
2023-01-05 23:33:49 +00:00
}
// highlight-end
@Component({
selector: 'ns-items',
templateUrl: './items.component.html',
})
export class ItemsComponent implements OnInit {
items: Array<Item>
version: string = `SheetJS - ${version}`;
constructor(private itemService: ItemService) {}
ngOnInit(): void {
this.items = this.itemService.getItems()
}
// highlight-start
/* Import button */
async import() {
}
/* Export button */
async export() {
}
// highlight-end
}
```
2024-04-08 03:55:10 +00:00
12) End the script and relaunch the app in the Android simulator:
```bash
npx -p nativescript ns run android
```
Two buttons should appear just below the header:
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
![NativeScript Step 5](pathname:///nativescript/step5.png)
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
13) Implement import and export by adding the highlighted lines:
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
```ts title="src/app/item/items.component.ts (add highlighted lines)"
2023-01-05 23:33:49 +00:00
/* Import button */
async import() {
// highlight-start
/* find appropriate path */
2023-04-03 09:09:59 +00:00
const url = get_url_for_filename("SheetJSNS.xls");
2023-01-05 23:33:49 +00:00
try {
2023-04-03 09:09:59 +00:00
await Dialogs.alert(`Attempting to read from SheetJSNS.xls at ${url}`);
/* get data */
const ab: ArrayBuffer = await getFileAccess().readBufferAsync(url);
2023-01-05 23:33:49 +00:00
/* read workbook */
2023-04-03 09:09:59 +00:00
const wb = read(ab);
2023-01-05 23:33:49 +00:00
/* grab first sheet */
const wsname: string = wb.SheetNames[0];
const ws = wb.Sheets[wsname];
/* update table */
this.items = utils.sheet_to_json<Item>(ws);
2023-04-03 09:09:59 +00:00
} catch(e) { await Dialogs.alert(e.message); }
2023-01-05 23:33:49 +00:00
// highlight-end
}
/* Export button */
async export() {
// highlight-start
/* find appropriate path */
2023-04-03 09:09:59 +00:00
const url = get_url_for_filename("SheetJSNS.xls");
2023-01-05 23:33:49 +00:00
try {
/* create worksheet from data */
const ws = utils.json_to_sheet(this.items);
/* create workbook from worksheet */
const wb = utils.book_new();
utils.book_append_sheet(wb, ws, "Sheet1");
2023-04-03 09:09:59 +00:00
/* generate Uint8Array */
const u8: Uint8Array = write(wb, { bookType: 'xls', type: 'buffer' });
2023-01-05 23:33:49 +00:00
2023-04-03 09:09:59 +00:00
/* attempt to save Uint8Array to file */
2023-05-07 17:43:21 +00:00
await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
2023-04-03 09:09:59 +00:00
await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`);
} catch(e) { await Dialogs.alert(e.message); }
2023-01-05 23:33:49 +00:00
// highlight-end
}
```
2023-10-13 10:04:30 +00:00
### Android
14) Launch the app in the Android Simulator:
```bash
npx -p nativescript ns run android
2024-04-08 03:55:10 +00:00
```
2023-10-13 10:04:30 +00:00
If the app does not automatically launch, manually open the `SheetJSNS` app.
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
15) Tap "Export File". A dialog will print where the file was written. Typically
the URL is `/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls`
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
16) Pull the file from the simulator. The following commands should be run in a
new terminal or PowerShell window:
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
```bash
adb root
adb pull /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls SheetJSNS.xls
```
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
If the emulator cannot be rooted, the following command works in macOS:
2023-10-13 10:04:30 +00:00
```bash
adb shell "run-as org.nativescript.SheetJSNS cat /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls" > SheetJSNS.xls
```
17) Open `SheetJSNS.xls` with a spreadsheet editor.
2023-01-05 23:33:49 +00:00
2023-04-03 09:09:59 +00:00
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
```text
2023-04-03 09:09:59 +00:00
id | name | role
2024-04-08 03:55:10 +00:00
# highlight-next-line
2023-04-03 09:09:59 +00:00
0 | SheetJS | Library
1 | Ter Stegen | Goalkeeper
3 | Piqué | Defender
2023-01-05 23:33:49 +00:00
...
```
2023-10-13 10:04:30 +00:00
18) Push the file back to the simulator:
2023-04-03 09:09:59 +00:00
2023-10-13 10:04:30 +00:00
```bash
adb push SheetJSNS.xls /data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls
```
2023-01-05 23:33:49 +00:00
2024-04-08 03:55:10 +00:00
If the emulator cannot be rooted, the following command works in macOS:
2023-01-05 23:33:49 +00:00
2023-10-13 10:04:30 +00:00
```bash
dd if=SheetJSNS.xls | adb shell "run-as org.nativescript.SheetJSNS dd of=/data/user/0/org.nativescript.SheetJSNS/files/SheetJSNS.xls"
```
2023-05-07 17:43:21 +00:00
2023-10-13 10:04:30 +00:00
19) Tap "Import File". A dialog will print the path of the file that was read.
The first item in the list will change.
2023-05-07 17:43:21 +00:00
2024-04-08 03:55:10 +00:00
![NativeScript Step 6](pathname:///nativescript/step6.png)
2023-10-13 10:04:30 +00:00
### iOS
2023-05-07 17:43:21 +00:00
2024-04-08 03:55:10 +00:00
:::warning pass
**iOS testing can only be performed on Apple hardware running macOS!**
Xcode and iOS simulators are not available on Windows or Linux.
Scroll down to ["Fetching Files"](#android-device) for Android device testing.
:::
2023-10-13 10:04:30 +00:00
20) Launch the app in the iOS Simulator:
2023-05-07 17:43:21 +00:00
```bash
2023-10-13 10:04:30 +00:00
npx -p nativescript ns run ios
2023-05-07 17:43:21 +00:00
```
2023-10-13 10:04:30 +00:00
21) Tap "Export File". A dialog will print where the file was written.
22) Open the file with a spreadsheet editor.
2023-05-07 17:43:21 +00:00
After the header row, insert a row with cell A2 = 0, B2 = SheetJS, C2 = Library:
2024-04-08 03:55:10 +00:00
```text
2023-05-07 17:43:21 +00:00
id | name | role
2024-04-08 03:55:10 +00:00
# highlight-next-line
2023-05-07 17:43:21 +00:00
0 | SheetJS | Library
1 | Ter Stegen | Goalkeeper
3 | Piqué | Defender
...
```
2023-10-13 10:04:30 +00:00
23) Restart the app after saving the file.
2023-05-07 17:43:21 +00:00
2023-10-13 10:04:30 +00:00
24) Tap "Import File". A dialog will print the path of the file that was read.
The first item in the list will change:
2023-05-07 17:43:21 +00:00
2023-10-13 10:04:30 +00:00
![NativeScript Step 7](pathname:///nativescript/step7.png)
2023-05-21 07:03:46 +00:00
### Fetching Files
2023-10-13 10:04:30 +00:00
25) In `src/app/item/items.component.ts`, make `ngOnInit` asynchronous:
2023-05-21 07:03:46 +00:00
2024-04-08 03:55:10 +00:00
```ts title="src/app/item/items.component.ts (replace existing function)"
2023-05-21 07:03:46 +00:00
async ngOnInit(): Promise<void> {
this.items = await this.itemService.getItems()
}
```
2023-10-13 10:04:30 +00:00
26) Replace `item.service.ts` with the following:
2023-05-21 07:03:46 +00:00
```ts title="src/app/item/item.service.ts"
import { Injectable } from '@angular/core'
import { knownFolders, path, getFileAccess } from '@nativescript/core'
import { getFile } from '@nativescript/core/http';
import { read, utils } from 'xlsx';
import { Item } from './item'
interface IPresident { Name: string; Index: number };
@Injectable({ providedIn: 'root' })
export class ItemService {
private items: Array<Item>;
async getItems(): Promise<Array<Item>> {
/* fetch https://sheetjs.com/pres.xlsx */
const temp: string = path.join(knownFolders.temp().path, "pres.xlsx");
const ab = await getFile("https://sheetjs.com/pres.xlsx", temp)
/* read the temporary file */
const wb = read(await getFileAccess().readBufferAsync(ab.path));
/* translate the first worksheet to the required Item type */
const data = utils.sheet_to_json<IPresident>(wb.Sheets[wb.SheetNames[0]]);
return this.items = data.map((pres, id) => ({id, name: pres.Name, role: ""+pres.Index} as Item));
}
getItem(id: number): Item {
return this.items.filter((item) => item.id === id)[0]
}
}
```
2024-04-08 03:55:10 +00:00
27) End the script and relaunch the app in the Android simulator:
2023-10-13 10:04:30 +00:00
```bash
npx -p nativescript ns run android
2024-04-08 03:55:10 +00:00
```
2023-10-13 10:04:30 +00:00
The app should show Presidential data.
2023-12-05 03:46:54 +00:00
### Android Device
28) Connect an Android device using a USB cable.
If the device asks to allow USB debugging, tap "Allow".
29) Close any Android / iOS emulators.
2024-04-08 03:55:10 +00:00
30) Enable "Legacy External Storage" in the Android app. The manifest is stored
at `App_Resources/Android/src/main/AndroidManifest.xml`:
```xml title="App_Resources/Android/src/main/AndroidManifest.xml (add highlighted line)"
<application
<!-- highlight-next-line -->
android:requestLegacyExternalStorage="true"
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true">
```
31) Install the `@nativescript-community/perms` dependency:
```bash
npm i --save @nativescript-community/perms
```
32) Add the highlighted lines to `items.component.ts`:
- Import `File` from NativeScript core and `request` from the new dependency:
```ts title="items.component.ts (add highlighted lines)"
import { Dialogs, getFileAccess, Utils } from '@nativescript/core';
// highlight-start
import { request } from '@nativescript-community/perms';
import { Folder, knownFolders, path, File } from '@nativescript/core/file-system';
// highlight-end
import { Component, OnInit } from '@angular/core'
// ...
```
- Add a new write operation to the `export` method:
```ts title="items.component.ts (add highlighted lines)"
/* attempt to save Uint8Array to file */
await getFileAccess().writeBufferAsync(url, global.isAndroid ? (Array.from(u8) as any) : u8);
await Dialogs.alert(`Wrote to SheetJSNS.xls at ${url}`);
/* highlight-start */
if(global.isAndroid) {
/* request permissions */
const res = await request('storage');
/* write to Downloads folder */
const dl = android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
File.fromPath(dl + "/SheetJSNS.xls").writeSync(Array.from(u8));
}
/* highlight-end */
} catch(e) { await Dialogs.alert(e.message); }
```
33) Build APK and run on device:
2023-12-05 03:46:54 +00:00
```bash
npx -p nativescript ns run android
```
If the Android emulators are closed and an Android device is connected, the last
command will build an APK and install on the device.
<details open>
<summary><b>Android Device Testing</b> (click to hide)</summary>
2024-04-08 03:55:10 +00:00
When the app launches, if the SheetJS library is loaded and if the device is
connected to the Internet, a list of Presidents should be displayed.
Tap "Export File". The app will show an alert. Tap "OK".
Switch to the "Files" app and open the "Downloads" folder. There should be a new
file named `SheetJSNS.xls`.
</details>
2023-12-05 03:46:54 +00:00
### iOS Device
2024-04-08 03:55:10 +00:00
34) Connect an iOS device using a USB cable
2023-12-05 03:46:54 +00:00
2024-04-08 03:55:10 +00:00
35) Close any Android / iOS emulators.
2023-12-05 03:46:54 +00:00
2024-04-08 03:55:10 +00:00
36) Enable developer code signing certificates[^9]
2023-12-05 03:46:54 +00:00
2024-04-08 03:55:10 +00:00
37) Run on device:
2023-12-05 03:46:54 +00:00
```bash
npx -p nativescript ns run ios
```
2023-10-13 10:04:30 +00:00
[^1]: See [`read` in "Reading Files"](/docs/api/parse-options)
[^2]: See ["Workbook Object"](/docs/csf/book)
[^3]: See [`sheet_to_json` in "Utilities"](/docs/api/utilities/array#array-output)
[^4]: See [`write` in "Writing Files"](/docs/api/write-options)
[^5]: See [`json_to_sheet` in "Utilities"](/docs/api/utilities/array#array-of-objects-input)
[^6]: See ["Workbook Helpers" in "Utilities"](/docs/api/utilities/wb) for details on `book_new` and `book_append_sheet`.
[^7]: See [`read` in "Reading Files"](/docs/api/parse-options)
2023-12-05 03:46:54 +00:00
[^8]: See ["Local setup"](https://docs.nativescript.org/setup/#local-setup) in the NativeScript documentation. For Windows and Linux, follow the "Android" instructions. For macOS, follow both the iOS and Android instructions.
[^9]: The [Flutter documentation](https://docs.flutter.dev/get-started/install/macos?tab=ios15#enable-developer-code-signing-certificates) covers the instructions in more detail. The correct workspace is `platforms/ios/SheetJSNS.xcodeproj/project.xcworkspace`