| 
									
										
										
										
											2022-08-10 22:11:05 +00:00
										 |  |  | --- | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-13 22:01:26 +00:00
										 |  |  | This demo was tested on an Intel Mac on 2022 August 10.  NativeScript version | 
					
						
							| 
									
										
										
										
											2022-08-10 22:11:05 +00:00
										 |  |  | (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. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-13 22:01:26 +00:00
										 |  |  | <details><summary><b>Complete Example</b> (click to show)</summary> | 
					
						
							| 
									
										
										
										
											2022-08-10 22:11:05 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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<Item> | 
					
						
							|  |  |  |   // 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" | 
					
						
							|  |  |  | <!-- highlight-next-line --> | 
					
						
							|  |  |  | <ActionBar [title]="version"></ActionBar> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | <GridLayout> | 
					
						
							|  |  |  |   <ListView [items]="items"> | 
					
						
							|  |  |  |     <ng-template let-item="item"> | 
					
						
							|  |  |  |       <StackLayout [nsRouterLink]="['/item', item.id]"> | 
					
						
							|  |  |  |         <Label [text]="item.name"></Label> | 
					
						
							|  |  |  |       </StackLayout> | 
					
						
							|  |  |  |     </ng-template> | 
					
						
							|  |  |  |   </ListView> | 
					
						
							|  |  |  | </GridLayout> | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Relaunch the app with `ns run ios` and the title bar should show the version. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 5) Add the Import and Export buttons to the template: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```xml title="src/app/item/items.component.html" | 
					
						
							|  |  |  | <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"> | 
					
						
							|  |  |  |     <ng-template let-item="item"> | 
					
						
							|  |  |  |       <StackLayout [nsRouterLink]="['/item', item.id]"> | 
					
						
							|  |  |  |         <Label [text]="item.name"></Label> | 
					
						
							|  |  |  |       </StackLayout> | 
					
						
							|  |  |  |     </ng-template> | 
					
						
							|  |  |  |   </ListView> | 
					
						
							|  |  |  | <!-- highlight-next-line --> | 
					
						
							|  |  |  | </StackLayout> | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ```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<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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | ``` | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Restart the app process and two buttons should show up at the top: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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<Item> | 
					
						
							|  |  |  |   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<Item>(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: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | </details> |