NativeScript Angular2 - ListPicker not working on Android - nativescript

I am using NativeScript Angular2 and on my page, I have added a ListPicker for user to select an option. The following is the code snippet for the html file:
<ListPicker #languagePicker id="languagePicker" [visibility]="langSelectStatus()" [items]="languages" class="mek-select-field" (selectedIndexChange)="selectedIndexChanged(languagePicker)"
>
</ListPicker>
The following is the css:
.mek-select-field {
height: 70px;
border-color: lightblue;
border-width: 1px;
margin: 0;
}
I found the code runs without any issues on iOS, the following is the screenshot:
ListPicker on iOS
However, on Android, found that the ListPicker was not working. It display the list, but cannot scroll between the options defined. The following is the screenshot:
ListPicker on Android
The following is the environment info:
NativeScript version: 2.4.0
NativeScript-Angular version: 1.1.3
NativeScript Android Runtime version: 2.4.1
Android emulator: API 25 Nexus 6
I am new to NativeScript and not sure if it's related to my environment or not.
Any advice will be much welcome.
Thanks in advance
[Updated on 2016 Nov 26]:
By exploring the sample as advised by Niko and more testing, I found that the behavior only appears when the option values are fetch from a backend via Http service. For example, in the example's creating-listpicker.component.ts class, if I change the option list to retrieve from Http backend like below code:
export class CreatingListPickerComponent {
public pokemons: Array<string>;
public picked: string;
constructor(private http: Http) {
this.pokemons = [];
this.http.get('http://192.168.31.120:3000/pokemons').subscribe(
res => {
let list = res.json();
console.log(`Pokemon list: ${list}`);
for (var i = 0; i < list.length; i++) {
this.pokemons.push(pokemonList[i]);
}
}
);
/* for (var i = 0; i < pokemonList.length; i++) {
this.pokemons.push(pokemonList[i]);
}*/
}
public selectedIndexChanged(picker) {
console.log('picker selection: ' + picker.selectedIndex);
this.picked = this.pokemons[picker.selectedIndex];
}
}
Where the endpoint will response with the exact same value as the hardcoded value. When I run the above code in Android (both emulator and device), I found that the ListPicker will not able to show any options (or only the first) option from time to time. It's very easy to re-produce. iOS don't have this problem.
I believe there exist some issues when the options for ListPicker is coming from a Http backend, where some delay will be present.
Kindly advice
Clarence

To be able to add ListPicker items after your HTTP request you should create new array after receiving the data and to make the old array to point to the new one. You could review the below-attached sample.
HTML
<FlexboxLayout flexDirection="column" exampleTitle toggleNavButton>
<Label class="h3 p-15 text-left" text="Pick a pokemon" textWrap="true"></Label>
<!-- >> creating-listpicker-html -->
<ListPicker #picker id="pickerid" class="p-15"
[items]="pokemons"
[selectedIndex]="selectedIndex"
(selectedIndexChange)="selectedIndexChanged(picker)">
</ListPicker>
<!-- << creating-listpicker-html -->
<Label [text]="'Selected pokemon: ' + picked" class="p-15" textWrap="true"></Label>
</FlexboxLayout>
TypeScript
import { Component , NgZone} from "#angular/core";
import { getFile, getImage, getJSON, getString, request } from "http";
var pokemonList = ["Bulbasaur", "Parasect", "Venonat", "Venomoth", "Diglett",
"Dugtrio", "Meowth", "Persian", "Psyduck", "Arcanine", "Poliwrath", "Machoke"];
#Component({
selector: "creating-listpicker",
templateUrl: "ui-category/listpicker/creating-listpicker/creating-listpicker.component.html"
})
export class CreatingListPickerComponent {
public pokemons: Array<string>;
public picked: string;
constructor(private zone:NgZone) {
this.pokemons = [];
}
ngOnInit(){
var that =this;
getJSON("https://httpbin.org/get?item1=item1&item2=item2&item3=item3")
.then((r) => {
console.log((<any>r).args.item1);
let args = (<any>r).args;
var arr = [args.item1, args.item2, args.item3]
this.pokemons = arr;
}, (e) => {
alert("GetJSON: " + e)
});
}
public selectedIndexChanged(picker) {
console.log('picker selection: ' + picker.selectedIndex);
this.picked = this.pokemons[picker.selectedIndex];
}
}

Related

Angular i18n : How to make language change using URL routing /Switcher Buttons?

I am building a simple app to learn internationalisation using Angular 11. The tutorial I am following is: https://lokalise.com/blog/angular-i18n/
I followed the tutorial and tried to create an app with 2 configurations - an En (english) one and an Ru (russian). When I run the app with
ng serve --configuration=ru --open
I can see the russian translation
when I use:
ng serve
It shows me the English version of the app.
Both these configurations run in different ports. I tried to create a language switcher to be able to switch between the english and russian version of the app here is the code for that in
app.component.ts & app.component.html
import { Component } from '#angular/core';
import {registerLocaleData} from '#angular/common';
import localeRu from '#angular/common/locales/ru';
registerLocaleData(localeRu, 'ru');
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
company = "Ash B.";
created_by = $localize`Created by ${this.company}`
today: number = Date.now();
localesList = [
{ code: "en-US", label: 'English' },
{ code: "ru", label: 'Русский' }
];
tasksCount = 201;
genderCode = 0;
// tslint:disable-next-line:typedef
male() { this.genderCode = 0; }
// tslint:disable-next-line:typedef
female() { this.genderCode = 1; }
// tslint:disable-next-line:typedef
other() { this.genderCode = 2; }
}
<ul>
<li *ngFor="let locale of localesList">
<a href="/{{locale.code}}/">
{{locale.label}}
</a>
</li>
</ul>
When I click on the buttons it changes the url from
localhost:4200/ru to localhost:4200/en-Us
but the content of the app is not translated. In order to see the translation I must run 2 different versions of the app. How can I make the language switch when I click on the buttons ?
to switch between versions you need to serve the build instead of serving the development configuration, each language will be compiled into a separate bundle, watch your dist folder after you run ng build --localize=true to make sure everything went ok.

Parse iOS Universal Links with Nativescript Angular?

Following the apple documentation and Branch's documentation here, I have set up a working universal link in my Nativescript Angular (iOS) app. But, how do I parse the link when the app opens?
For example, when someone opens the app from the link, I want to have my app read the link so it can go to the correct page of the app.
There is some helpful code in this answer, but I keep getting errors with it. This could be bc the code is written in vanilla JS and I am not translating it into Angular correctly. The use of "_extends" and "routeUrL" both cause errors for me.
And the Nativescript url-handler plugin does not seem to work without further code.
So, after setting up the universal link, and installing the nativescript url-handler plugin, I have entered the following in app.module.ts:
const Application = require("tns-core-modules/application");
import { handleOpenURL, AppURL } from 'nativescript-urlhandler';
declare var NSUserActivityTypeBrowsingWeb
if (Application.ios) {
const MyDelegate = (function (_super) {
_extends(MyDelegate, _super);
function MyDelegate() {
_super.apply(this, arguments);
}
MyDelegate.prototype.applicationContinueUserActivityRestorationHandler = function (application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
this.routeUrl(userActivity.webpageURL);
}
return true;
};
MyDelegate.ObjCProtocols = [UIApplicationDelegate];
return MyDelegate;
})(UIResponder);
Application.ios.delegate = MyDelegate;
}
...
export class AppModule {
ngOnInit(){
handleOpenURL((appURL: AppURL) => {
console.log('Got the following appURL = ' + appURL);
});
}
}
The trouble seems to be mostly with "_extends" and "_super.apply". For example, I get this error:
'NativeScript encountered a fatal error: TypeError: undefined is not an object (evaluating '_extends')
EDIT: Note that the nativescript-urlhandler plugin is no longer being updated. Does anyone know how to parse universal links with Nativescript?
I have figured out a method to get this working:
The general idea is to use the iOS App Delegate method: applicationContinueUserActivityRestorationHandler.
The syntax in the Nativescript documentation on app delegates did not work for me. You can view that documentation here.
This appears to work:
--once you have a universal link set up, following documentation like here, and now you want your app to read ("handle") the details of the link that was tapped to open the app:
EDIT: This code sample puts everything in one spot in app.module.ts. However, most of the time its better to move things out of app.module and into separate services. There is sample code for doing that in the discussion here. So the below has working code, but keep in mind it is better to put this code in a separate service.
app.module.ts
declare var UIResponder, UIApplicationDelegate
if (app.ios) {
app.ios.delegate = UIResponder.extend({
applicationContinueUserActivityRestorationHandler: function(application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
let tappedUniversalLink = userActivity.webpageURL
console.log('the universal link url was = ' + tappedUniversalLink)
}
return true;
}
},
{
name: "CustomAppDelegate",
protocols: [UIApplicationDelegate]
});
}
NOTE: to get the NSUserActivity/Application Delegate stuff to work with typescript, I also needed to download the tns-platforms-declarations plugin, and configure the app. So:
$ npm i tns-platforms-declarations
and
references.d.ts
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />
The above code works for me to be able to read the details of the tapped universal link when the link opens the app.
From there, you can determine what you want to do with that information. For example, if you want to navigate to a specific page of your app depending on the details of the universal link, then I have found this to work:
app.module.ts
import { ios, resumeEvent, on as applicationOn, run as applicationRun, ApplicationEventData } from "tns-core-modules/application";
import { Router } from "#angular/router";
let univeralLinkUrl = ''
let hasLinkBeenTapped = false
if (app.ios) {
//code from above, to get value of the universal link
applicationContinueUserActivityRestorationHandler: function(application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
hasLinkBeenTapped = true
universalLinkUrl = userActivity.webpageURL
}
return true;
},
{
name: "CustomAppDelegate",
protocols: [UIApplicationDelegate]
});
}
#ngModule({...})
export class AppModule {
constructor(private router: Router) {
applicationOn(resumeEvent, (args) => {
if (hasLinkBeenTapped === true){
hasLinkBeenTapped = false //set back to false bc if you don't app will save setting of true, and always assume from here out that the universal link has been tapped whenever the app opens
let pageToOpen = //parse universalLinkUrl to get the details of the page you want to go to
this.router.navigate(["pageToOpen"])
} else {
universalLinkUrl = '' //set back to blank
console.log('app is resuming, but universal Link has not been tapped')
}
})
}
}
You can use the nativescript-plugin-universal-links plugin to do just that.
It has support for dealing with an existing app delegate so if you do have another plugin that implements an app delegate, both of them will work.
Here's the usage example from the docs:
import { Component, OnInit } from "#angular/core";
import { registerUniversalLinkCallback } from "nativescript-plugin-universal-links";
#Component({
selector: "my-app",
template: "<page-router-outlet></page-router-outlet>"
})
export class AppComponent {
constructor() {}
ngOnInit() {
registerUniversalLinkCallback(ul => {
// use the router to navigate to the screen
});
}
}
And the callback will receive a ul (universal link) param that looks like this
{
"href": "https://www.example.com/blog?title=welcome",
"origin": "https://www.example.com",
"pathname": "/blog",
"query": {
"title": "welcome"
}
}
Disclaimer: I'm the author of the plugin.

How to debug an import binding name that is not found

I have a NativeScript application that I'm trying to add iBeacon support to using the iBeacon plugin. The application builds successfully and is synced to my phone (I'm using SideKick). When the app runs, it has a fatal javascript exception. The javascript error is reported at:
file:///app/tns_modules/tns-core-modules/ui/builder/builder.js:244:56: JS ERROR Error: Building UI from XML. #file:///app/app-root.xml:18:9
That line is where the page that attempts to access the iBeacon code is defined:
<Frame defaultPage="views/search/search-page"></Frame>
and the specific error is:
Importing binding name 'BeaconLocationOptions' is not found.
I'm assuming this occurs as part of the following import statement:
import {NativescriptIbeacon, BeaconCallback, BeaconLocationOptions, BeaconLocationOptionsIOSAuthType, BeaconLocationOptionsAndroidAuthType, BeaconRegion, Beacon } from 'nativescript-ibeacon';
The above import statement is what is documented as part of the iBeacon documentation.
There is a nativescript-ibeacon directory under node_modules in my project. The specific ios file seems to be there:
/Users/edscott/NativeScript/beacon-test/node_modules/nativescript-ibeacon/nativescript-ibeacon.ios.js
I'm not sure if it is a problem in my code or a problem with configuration - maybe something missing that stops the ibeacon files from being deployed properly to the device.
My code is in javascript, but I have installed the typescript plugin. It looks like this iBeacon plugin assumes the app is written in typescript.
I'm looking for help in determining what to try next.
FYI...I've tried pulling the source files out of the node_modules and incorporating them directly into my project. After resolving many issues with this approach, I eventually hit the same wall - a problem importing the code when running on the device.
Below is the code that is using the iBeacon plugin:
const observableModule = require("tns-core-modules/data/observable");
import {NativescriptIbeacon, BeaconCallback, BeaconLocationOptions, BeaconLocationOptionsIOSAuthType, BeaconLocationOptionsAndroidAuthType, BeaconRegion, Beacon } from 'nativescript-ibeacon';
function SearchViewModel() {
let callback = {
onBeaconManagerReady() {
// start ranging and/or monitoring only when the beacon manager is ready
this.nativescriptIbeacon.startRanging(this.region);
this.nativescriptIbeacon.startMonitoring(this.region);
},
didRangeBeaconsInRegion: function(region, beacons) {
console.log("didRangeBeaconsInRegion");
},
didFailRangingBeaconsInRegion: function(region, errorCode, errorDescription) {
console.log("didFailRangingBeaconsInRegion");
}
};
let options = {
iOSAuthorisationType: BeaconLocationOptionsIOSAuthType.Always,
androidAuthorisationType: BeaconLocationOptionsAndroidAuthType.Coarse,
androidAuthorisationDescription: "Location permission needed"
};
let nativescriptIbeacon = new NativescriptIbeacon(callback, options);
let region = new BeaconRegion("HelloID", "2f234454-cf6d-4a0f-adf2-f4911ba9ffa6");
const viewModel = observableModule.fromObject({
"beaconData": "not set yet",
"onTapStart": function() {
this.set("beaconData", "started");
console.log("tapped start");
if (!nativescriptIbeacon.isAuthorised()) {
console.log("NOT Authorised");
nativescriptIbeacon.requestAuthorization()
.then(() => {
console.log("Authorised by the user");
nativescriptIbeacon.bind();
}, (e) => {
console.log("Authorisation denied by the user");
})
} else {
console.log("Already authorised");
nativescriptIbeacon.bind();
}
},
"onTapStop": function() {
this.set("beaconData", "stopped");
console.log("tapped stop");
nativescriptIbeacon.stopRanging(region);
nativescriptIbeacon.stopMonitoring(region);
nativescriptIbeacon.unbind();
}
});
return viewModel;
}
module.exports = SearchViewModel;
I have created a playground for you here.
If you look into example, I am importing NativescriptIbeacon from the main folder and rest from the common folder.
P.S. This plugin has dependency on nativescript-permission
import { NativescriptIbeacon } from '../nativescript-ibeacon';
import {
BeaconRegion, Beacon, BeaconCallback,
BeaconLocationOptions, BeaconLocationOptionsIOSAuthType, BeaconLocationOptionsAndroidAuthType
} from "../nativescript-ibeacon/nativescript-ibeacon.common";
This answer solved my problem along with another modification. After splitting the import up I still had the same error. Then I read the following page about modules:
https://docs.nativescript.org/core-concepts/android-runtime/getting-started/modules
Based on this statement:
If the module identifier passed to require(moduleName) does not begin
with '/', '../', or './', then NativeScript will lookup the module
within the tns_modules folder
I assumed that maybe only require does the proper lookup into tns_modules.
I refactored the import to use require instead, and that worked. My changes are below. There may be a more efficient way to do this, but it worked for me.
const nsb = require("nativescript-ibeacon/nativescript-ibeacon.js");
const nsbc = require("nativescript-ibeacon/nativescript-ibeacon.common.js");
const NativescriptIbeacon = nsb.NativescriptIbeacon;
const BeaconCallback = nsbc.BeaconCallback;
const BeaconLocationOptions = nsbc.BeaconLocationOptions;
const BeaconLocationOptionsIOSAuthType = nsbc.BeaconLocationOptionsIOSAuthType;
const BeaconLocationOptionsAndroidAuthType = nsbc.BeaconLocationOptionsAndroidAuthType
const BeaconRegion = nsbc.BeaconRegion;
const Beacon = nsbc.Beacon;

`page_objects_path` field doesn't work in nightwatch.config.js

I use Page Objects in nightwatch, I config the page_objects_path in nightwatch.conf.js, but when I reference these elements defined, got error. I don't know why.I use vue-cli to build the project.
Here's my configuration:
nightwatch.conf.js
...
page_objects_path: 'test/e2e/pages',
...
The page object file register.js:
module.exports = {
elements: {
genderField: '.common-picker:first-child',
genderPicker: '.picker',
genderOptionLast: '.picker-item:last-child',
genderPickerConfirmButton: '.picker-toolbar span:last-child',
genderPickerCancelButton: '.picker-toolbar span:first-child'
}
}
Using the page object in test files:
'select a gender option': function (browser) {
browser
.assert.hidden('#genderPicker')
.click('#genderField')
.pause(1000)
.click('#genderOptionLast')
.click('#genderPickerConfirmButton')
.assert.containsText('#genderField', '女士')
.pause(1000)
.end()
}
Error info:
The Page Object File should have the keyword selector for every element.
module.exports = {
elements: {
genderField: {
selector: '.common-picker:first-child'
},
genderPicker: {
selector: '.picker'
},
genderOptionLast: {
selector: '.picker-item:last-child'
},
genderPickerConfirmButton: {
selector: '.picker-toolbar span:last-child'
},
genderPickerCancelButton: {
selector: '.picker-toolbar span:first-child'
}
}
}
You need to define your page object as a constant at the top of the test, and then call the elements from the registerPage object:
'select a gender option': function (browser) {
const registerPage = browser.page.register();
registerPage.assert.hidden('#genderPicker')
registerPage.click('#genderField')
browser.pause(1000)
registerPage.click('#genderOptionLast')
registerPage.click('#genderPickerConfirmButton')
registerPage.assert.containsText('#genderField', '女士')
browser.pause(1000)
browser.end()
}
You don't need selectors in your page objects if you're using CSS for everything (which it looks like you are).
Source: http://nightwatchjs.org/guide#page-objects

ionic 2 caching images

I am writing an ionic 2 application, and want to cache images.
After long searching on the web I found these references:
https://gist.github.com/ozexpert/d95677e1fe044e6173ef59840c9c484e
https://github.com/chrisben/imgcache.js/blob/master/js/imgcache.js
I implemented the given solution, but i see that the ImgCache module does not behave as expected - the ImgCache.isCached callback is never called.
Any idea or other good solution for caching images in ionic 2?
======== UPDATE ==========
Here is the directive code I use:
import { Directive, ElementRef, Input } from '#angular/core';
import ImgCache from 'imgcache.js';
#Directive({
selector: '[image-cache]'
})
export class ImageCacheDirective {
constructor (
private el: ElementRef
) {
// init
}
ngOnInit() {
// This message is shown in console
console.log('ImageCacheDirective *** ngOnInit: ', this.el.nativeElement.src);
this.el.nativeElement.crossOrigin = "Anonymous"; // CORS enabling
ImgCache.isCached(this.el.nativeElement.src, (path: string, success: any) => {
// These message are never printed
console.log('path - '+ path);
console.log('success - '+ success);
if (success) {
// already cached
console.log('already cached so using cached');
ImgCache.useCachedFile(this.el.nativeElement);
} else {
// not there, need to cache the image
console.log('not there, need to cache the image - ' + this.el.nativeElement.src);
ImgCache.cacheFile(this.el.nativeElement.src, () => {
console.log('cached file');
// ImgCache.useCachedFile(el.nativeElement);
});
}
});
}
}
In app.nodule.es I do:
import { ImageCacheDirective } from '../components/image-cache-directive/image-cache-directive';
and then in home.html:
<img src="http://localhost/ionic-test/img/timeimg.php" image-cache>
It's late but probably this is the solution:
1. Install cordova FileTransfer:
ionic plugin add cordova-plugin-file-transfer --save
2. Init ImgCache when the deviceready event of cordova fires. In src/app/app.component.ts add these methods (or integrate them with your initializeApp() method - this method comes up with a default project start):
initImgCache() {
// activated debug mode
ImgCache.options.debug = true;
ImgCache.options.chromeQuota = 100 * 1024 * 1024; // 100 MB
ImgCache.init(() => { },
() => { console.log('ImgCache init: error! Check the log for errors'); });
}
initializeApp() {
this.platform.ready().then(() => {
this.initImgCache();
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
StatusBar.styleDefault();
Splashscreen.hide();
});
}
Another option is to use a dedicated cache manager for ionic. instead of implementing everything on your own.
Here are 2 options :
1. A generic cache implementation :https://github.com/Nodonisko/ionic-cache
2. This one is better for images: https://github.com/BenBBear/ionic-cache-src
EDIT:
This is not a "link only" answer.. it tells the user to use a ready made implementations instead of trying to implement from scratch.

Resources