--- sidebar_position: 19 title: iOS and Android Apps --- Many mobile app frameworks mix JavaScript / CSS / HTML5 concepts with native extensions and libraries to create a hybrid development experience. Developers well-versed in web technologies can now build actual mobile applications that run on iOS and Android! :::warning **The ecosystem has broken backwards-compatibility many times!** iOS and Android, as well as the underlying JavaScript frameworks, make breaking changes regularly. The demos were tested against emulators / real devices at some point in time. A framework or OS change can render the demos inoperable. Each demo section will mention test dates and platform versions. ::: The ["JavaScript Engines"](./engines) section includes samples for JavaScript engines used in the mobile app frameworks. SheetJS libraries have been tested in the relevant engines and should "just work" with some caveats. :::caution readFile and writeFile `XLSX.readFile` and `XLSX.writeFile` do not work in mobile apps! The demos include platform-specific details for fetching file data for `XLSX.read` and writing file data generated by `XLSX.write`. Some platforms provide this functionality as part of the standard library. Other platforms, including React Native, do not. When the platform does not provide, usually there are third-party modules to provide needed functionality. ::: ## NativeScript :::note This demo was tested on an Intel Mac on 2022 August 10. NativeScript version (as verified with `ns --version`) is `8.3.2`. The iOS simulator runs iOS 15.5 on an iPhone SE 3rd generation. ::: :::warning Binary Data issues NativeScript will not safely transmit binary or UTF8 strings. XLSB, NUMBERS, XLSX, XLS, ODS, SYLK, and DBF exports are known to be mangled. [This is a known NativeScript bug](https://github.com/NativeScript/NativeScript/issues/9586) This demo will focus on ASCII CSV files. Once the bug is resolved, XLSX and other formats will be supported. ::: The `@nativescript/core/file-system` package provides classes for file access. ### Integration Details Reading and writing data require a file handle. The following snippet searches typical document folders for a specified filename: ```ts import { File, Folder, knownFolders, path } from '@nativescript/core/file-system'; function get_handle_for_filename(filename: string): File { const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic(); const url: string = path.normalize(target.path + "///" + filename); return File.fromPath(url); } ``` The encoding `ISO_8859_1` spiritually resembles the `"binary"` SheetJS type **Reading data** `File#readText(encoding.ISO_8859_1)` returns strings compatible with `"binary"` ```ts /* get binary string */ const bstr: string = await file.readText(encoding.ISO_8859_1); /* read workbook */ const wb = read(bstr, { type: "binary" }); ``` **Writing data** `File#writeText` with the `ISO_8859_1` encoding accepts `"binary"` strings with the caveat listed in the warning at the top of this section: ```ts /* generate binary string */ const bstr: string = write(wb, { bookType: 'csv', type: 'binary' }); /* attempt to save binary string to file */ await file.writeText(bstr, encoding.ISO_8859_1); ``` ### Demo The demo builds off of the NativeScript + Angular example. Familiarity with with Angular and TypeScript is assumed.
Complete Example (click to show) 0) Follow the official Environment Setup instructions (tested with "macOS + iOS") 1) Create a skeleton NativeScript + Angular app: ```bash ns create SheetJSNS --ng ``` 2) Launch the app in the iOS simulator to verify that the demo built properly: ```bash cd SheetJSNS ns run ios ``` (this may take a while) Once the simulator launches and the test app is displayed, end the script by selecting the terminal and entering the key sequence `CTRL + C` 3) From the project folder, install the library: ```bash npm install --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz ``` 4) To confirm the library was loaded, change the title to show the version. The differences are highlighted. `src/app/item/items.component.ts` imports the version string to the component: ```ts title="src/app/item/items.component.ts" // highlight-next-line import { version } from 'xlsx'; import { Component, OnInit } from '@angular/core' import { Item } from './item' import { ItemService } from './item.service' @Component({ selector: 'ns-items', templateUrl: './items.component.html', }) export class ItemsComponent implements OnInit { items: Array // highlight-next-line version = `SheetJS - ${version}`; constructor(private itemService: ItemService) {} ngOnInit(): void { this.items = this.itemService.getItems() } } ``` `src/app/item/items.component.html` references the version in the title: ```xml title="src/app/item/items.component.html" ``` Relaunch the app with `ns run ios` and the title bar should show the version. ![NativeScript Step 4](pathname:///mobile/nativescript4.png) 5) Add the Import and Export buttons to the template: ```xml title="src/app/item/items.component.html" ``` ```ts title="src/app/item/items.component.ts" // highlight-start import { version, utils, read, write } from 'xlsx'; import { Dialogs } from '@nativescript/core'; import { encoding } from '@nativescript/core/text'; import { File, Folder, knownFolders, path } from '@nativescript/core/file-system'; // highlight-end import { Component, OnInit } from '@angular/core' import { Item } from './item' import { ItemService } from './item.service' // highlight-start function get_handle_for_filename(filename: string): [File, string] { const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic(); const url: string = path.normalize(target.path + "///" + filename); return [File.fromPath(url), url]; } // highlight-end @Component({ selector: 'ns-items', templateUrl: './items.component.html', }) export class ItemsComponent implements OnInit { items: Array 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 } ``` Restart the app process and two buttons should show up at the top: ![NativeScript Step 5](pathname:///mobile/nativescript5.png) 6) Implement import and export: ```ts title="src/app/item/items.component.ts" import { version, utils, read, write } from 'xlsx'; import { Dialogs } from '@nativescript/core'; import { encoding } from '@nativescript/core/text'; import { File, Folder, knownFolders, path } from '@nativescript/core/file-system'; import { Component, OnInit } from '@angular/core' import { Item } from './item' import { ItemService } from './item.service' function get_handle_for_filename(filename: string): [File, string] { const target: Folder = knownFolders.documents() || knownFolders.ios.sharedPublic(); const url: string = path.normalize(target.path + "///" + filename); return [File.fromPath(url), url]; } @Component({ selector: 'ns-items', templateUrl: './items.component.html', }) export class ItemsComponent implements OnInit { items: Array version: string = `SheetJS - ${version}`; constructor(private itemService: ItemService) {} ngOnInit(): void { this.items = this.itemService.getItems() } /* Import button */ async import() { // highlight-start /* find appropriate path */ const [file, url] = get_handle_for_filename("SheetJSNS.csv"); try { /* get binary string */ const bstr: string = await file.readText(encoding.ISO_8859_1); /* read workbook */ const wb = read(bstr, { type: "binary" }); /* grab first sheet */ const wsname: string = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; /* update table */ this.items = utils.sheet_to_json(ws); Dialogs.alert(`Attempting to read to ${filename} in ${url}`); } catch(e) { Dialogs.alert(e.message); } // highlight-end } /* Export button */ async export() { // highlight-start /* find appropriate path */ const [file, url] = get_handle_for_filename("SheetJSNS.csv"); 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"); /* generate binary string */ const wbout: string = write(wb, { bookType: 'csv', type: 'binary' }); /* attempt to save binary string to file */ await file.writeText(wbout, encoding.ISO_8859_1); Dialogs.alert(`Wrote to ${filename} in ${url}`); } catch(e) { Dialogs.alert(e.message); } // highlight-end } } ``` Restart the app process. **Testing** The app can be tested with the following sequence in the simulator: - Hit "Export File". A dialog will print where the file was written - Open that file with a text editor. It will be a 3-column CSV: ```csv id,name,role 1,Ter Stegen,Goalkeeper 3,Piqué,Defender 4,I. Rakitic,Midfielder ... ``` After the header row, add the line `0,SheetJS,Library`: ```csv id,name,role 0,SheetJS,Library 1,Ter Stegen,Goalkeeper 3,Piqué,Defender ... ``` - Hit "Import File". A dialog will print the path of the file that was read. The first item in the list will change: ![NativeScript Step 7](pathname:///mobile/nativescript7.png)