In application-settings, iterate the key values - nativescript

In Nativescript application-settings module, is there a way to iterate through all the key values?

There is no appropriate way to get all keys from the application-settings and to iterate through them. You will still need to save somewhere the keys- for example in array, to be able to get the values. For your convenience I am attaching example, however the given code is not the best practise.
Example:
import { EventData } from 'data/observable';
import { Page } from 'ui/page';
import { HelloWorldModel } from './main-view-model';
import {setString, hasKey, getString, setNumber, getNumber} from "application-settings"
// Event handler for Page "navigatingTo" event attached in main-page.xml
export function navigatingTo(args: EventData) {
// Get the event sender
let page = <Page>args.object;
var arrayKeys=[{value:"string1", type:"string"}, {value:"string2", type:"string"}, {value:"string3", type:"string"}, {value:"number1", type:"number"}];
setString("string1", "stringValue1");
setString("string2", "stringValue2");
setString("string3", "stringValue3");
setNumber("number1", 1);
console.log("--------Get Values----------")
for(var i=0; i<arrayKeys.length;i++){
if(hasKey(arrayKeys[i].value)){
switch (arrayKeys[i].type) {
case "number":
console.log("Number "+getNumber(arrayKeys[i].value))
break;
case "string":
console.log("String "+getString(arrayKeys[i].value))
break;
default:
break;
}
}
}
}

Related

How do I initialise a NativeScript app fully programmatically (without XML)?

Here's what I have so far. The background goes green (the colour of the Page), but I'd expect a purple ContentView with some text inside to fill the page, too.
Is there anything further I'm missing?
import { on, run, launchEvent } from "tns-core-modules/application";
import { Frame } from "tns-core-modules/ui/frame/frame";
import { ContentView } from "tns-core-modules/ui/content-view/content-view";
import { TextBase } from "tns-core-modules/ui/text-base/text-base";
import { Page } from "tns-core-modules/ui/page/page";
on(launchEvent, (data) => {
const frame = new Frame();
const page = new Page();
page.backgroundColor = "green";
const contentView = new ContentView();
const textBase = new TextBase();
contentView.height = 100;
contentView.width = 100;
contentView.backgroundColor = "purple";
textBase.text = "Hello, world!";
contentView._addView(textBase);
page.bindingContext = contentView;
frame.navigate({ create: () => page });
data.root = page; // Incidentally, should this be the frame or the page?
});
run();
You are almost on track, you just need slight modification on your code.
import { on, run, launchEvent } from 'tns-core-modules/application';
import { Frame } from 'tns-core-modules/ui/frame/frame';
import { ContentView } from 'tns-core-modules/ui/content-view/content-view';
import { TextField } from 'tns-core-modules/ui/text-field';
import { Page } from 'tns-core-modules/ui/page/page';
run({
create: () => {
const frame = new Frame();
frame.navigate({
create: () => {
const page = new Page();
page.backgroundColor = "green";
const contentView = new ContentView();
const textField = new TextField();
contentView.height = 100;
contentView.width = 100;
contentView.backgroundColor = "purple";
textField.text = "Hello, world!";
contentView.content = textField;
page.content = contentView;
return page;
}
});
return frame;
}
});
You don't have to wait for launch event, you could set the root frame in run method itself.
In your code, you were creating the frame but never adding it to root UI element or mark the frame itself as root element
It's recommended to use .content to add child for a ContentView / Page as they are originally designed to hold one child element only.
Use TextField / TextView for input text, TextBase is just a base class.
It seems to me that you try to overcomplicate. You can replace XML with code just by implementing createPage method - Create a page via code.
I just modified default NS + TypeScript Playground template to operate without XML - NS + TypeScript template without XML.
I think you can't leave run as empty as it is expecting an entry to start the app. From {NS} website,
You can use this file to perform app-level initializations, but the
primary purpose of the file is to pass control to the app's root
module. To do this, you need to call the application.run() method and
pass a NavigationEntry with the desired moduleName as the path to the
root module relative to your /app folder.
if you look for run code in "tns-core-modules/application"
function run(entry) {
createRootFrame.value = false;
start(entry);
}
exports.run = run;
and
function start(entry) {
if (started) {
throw new Error("Application is already started.");
}
started = true;
mainEntry = typeof entry === "string" ? { moduleName: entry } : entry;
if (!androidApp.nativeApp) {
var nativeApp = getNativeApplication();
androidApp.init(nativeApp);
}
}

route push in custom saga does not cause re-render

i have a custom saga, which responds to a simple search user action, if it finds a single user, it should show it otherwise go to the user list.
Problem is that when i push the new route, i can see that state changes but the app is not rendering the new route. I looked through the source and cant find any update-blockers.
If i do the push action twice, for some reason, only causing the location key to change again, the app re-renders correctly. Don't know where to keep looking, so any ideas of what the problem is?
import { put, takeEvery, all, call } from "redux-saga/effects";
import { searchUser } from "../services/userService";
import { SEARCH_USER } from "../actions/searchUser";
import { showNotification } from "admin-on-rest";
import { push, replace } from "react-router-redux";
function* handleSearchUser(action) {
try {
const { searchParam } = action.payload;
const filter = {};
if (isNaN(searchParam)) {
filter.Email = searchParam;
} else {
filter.UserId = parseInt(searchParam);
}
const result = yield call(searchUser, 0, 25, filter);
if (result && result.length === 1) {
// Duplicate this row and the page re-renders the correct page
yield put(push(`/User/${result[0].UserId}/show`));
} else {
yield put(
push(
`/User?filter=${JSON.stringify(
filter
)}&order=DESC&sort=id&page=1&perPage=25`
)
);
}
} catch (error) {
console.error(error);
yield put(showNotification("Error: error when searching", "warning"));
}
}
export default function* searchUserSaga() {
yield all([takeEvery(SEARCH_USER, handleSearchUser)]);
}
Was using gatsbyjs. I switched to Create React App for building and all problems went away.

Execute method only once after all changes have happened in Observable in Angular ReplaySubject

I have created a service which checks for changes in parent component and send notifications to the child component.
Below is the simple service.
import { Injectable } from '#angular/core';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { CloudDate } from './cloudDate';
import { Clouding } from './clouding';
#Injectable()
export class CloudingService {
// Observable string sources
private cloudingAnnouncedSource = new ReplaySubject<Clouding>(1);
private cloudingConfirmedSource = new ReplaySubject<Clouding>(1);
// Observable string streams
cloudingAnnounced$ = this.cloudingAnnouncedSource.asObservable();
cloudingConfirmed$ = this.cloudingConfirmedSource.asObservable();
// Service message commands
announceClouding(clouding: Clouding) {
this.cloudingAnnouncedSource.next(clouding);
}
confirmClouding(clouding: Clouding) {
this.cloudingConfirmedSource.next(clouding);
}
}
Clouding class looks like this:
export class Clouding {
cameraName: string;
cloudDate: string;
cameraType: string;
}
Now in the parent component, this class is initialized in the constructor and its variables will change depending on different methods.
Example:
// In constructor
this.clouding = new Clouding();
// A method
getCameras(): void {
this.clouding.cameraName = this.currentCloudName;
}
//Another method
getCloudDates(): void {
this.clouding.cloudDate = this.currentCloudDate.cloudDate;
}
The variables this.currentCloudName and this.currentCloudDate.cloudDate will change dynamically depending on button clicks.
When the buttons are clicked, I do:
this.cloudingService.announceClouding(this.clouding);
In child component, I do this to get the new value of clouding.
import { Component, OnDestroy, Input, OnInit } from '#angular/core';
import { Clouding} from './clouding';
import { CloudingService } from './clouding.service';
import { Subscription } from 'rxjs/Subscription';
#Component({
selector: 'app-images',
templateUrl: './images.component.html',
styleUrls: ['./images.component.css']
})
export class ImagesComponent implements OnDestroy, OnInit {
title = 'Images';
#Input()
clouding: Clouding;
subscription: Subscription;
constructor(
private cloudingService: CloudingService
) {
}
ngOnInit(): void {
this.subscription =
this.cloudingService.cloudingAnnounced$.subscribe(
clouding => {
this.clouding = clouding;
console.log(this.clouding);
},
// The 2nd callback handles errors.
(err) => console.error(err),
// The 3rd callback handles the "complete" event.
() => {
}
);
this.cloudingService.confirmClouding(this.clouding);
}
ngOnDestroy() {
// prevent memory leak when component destroyed
this.subscription.unsubscribe();
}
}
Now on the console, I get the below when I click on a button in the parent:
Clouding {cameraName: "Benjamin2", cloudDate: "2017-08-26"}
Clouding {cameraName: "Benjamin2", cloudDate: "2017-08-24"}
My question is, is there a way to make console print only the last change i.e.
Clouding {cameraName: "Benjamin2", cloudDate: "2017-08-24"}
and ignore the first change that occurred. I dont want to do execute a method everytime the object changes, just execute after all changes have been subscribed.
Hope the question is clear.
It sounds like you need a third button to trigger the announce, take this off the name-setting button and date-setting button and put it on the new button.
this.cloudingService.announceClouding(this.clouding);

Angular 2 How to remove a list item from Storage

How to remove items from the storage (Storage) by clicking on the button. Elements are entered through the input and added to the page. New items are stored in the Storage. Now the situation is this - the elements are deleted on the page by clicking on the button, when I update the page, they still remain in place. They continue somewhere to be stored.
file home.html
<ion-list>
<ion-item *ngFor="let place of places ; let i = index"
(click)="onOpenPlace(place)">{{ place.title }}
</ion-item>
<button ion-button color="danger" (click)="deletePlace(i)">Delete</button>
</ion-list>
file home.ts
import { Component } from '#angular/core';
import { Storage } from '#ionic/storage'; /*** does not work ***/
import { ModalController, NavController } from 'ionic-angular';
import { NewPlacePage } from "../new-place/new-place";
import { PlacePage } from "../place/place";
import { PlacesService } from "../../services/places.service";
import { Place } from "../../model/place.model";
#Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
places: {title: string}[] = [];
constructor(
private storage: Storage,
public navCtrl: NavController,
private placesService: PlacesService,
private modalCtrl: ModalController) {
}
ionViewWillEnter() {
this.placesService.getPlaces()
.then(
(places) => this.places = places
);
}
onLoadNewPlace() {
this.navCtrl.push(NewPlacePage);
}
onOpenPlace(place: Place) {
this.modalCtrl.create(PlacePage, place).present();
}
deletePlace(i){ /*** does not work ***/
console.log('delete')
this.places.splice(i, 1);
}
}
file places.service.ts
import { Storage } from '#ionic/storage'; /*** does not work ***/
import { Injectable } from '#angular/core';
import { Place } from '../model/place.model';
#Injectable()
export class PlacesService {
private places: Place[] = [];
constructor ( private storage: Storage) {}
addPlace(place: Place) {
this.places.push(place);
this.storage.set('places', this.places);
}
deletePlace(place: Place){ /*** does not work ***/
this.storage.remove('places');
}
getPlaces() {
return this.storage.get('places')
.then(
(places) => {
this.places = places == null ? [] : places;
return this.places.slice();
}
);
}
}
The problem is that in your deletePlace(i) method, you're removing the item from your in memory array of places, but you're not updating the storage.
The deletePlace(...) method from your service won't work because you're saving the places in the storage as an array, so you can't remove a specific place.
deletePlace(place: Place) {
this.storage.remove('places');
}
I would fix it like this:
In your service, create a new method in order to update the storage with the changes you make to the in memory array:
saveChanges(places: Array<Place>) {
this.places = places;
this.storage.set('places', this.places);
}
And then in your component code, call that method after removing a place:
deletePlace(i) {
console.log('delete');
// Delete the place from the in memory array
this.places.splice(i, 1);
// Update the storage with the new list of places
this.placesService.saveChanges(this.places);
}

Approach to filtering and validating return values with rxjs

So here is the scenario I am attempting to figure out how to implement using rxjs:
Load some set of metadata from a file/database/etc. Each element in the metadata has an id and additional information - like the location of the actual data. Currently, I am loading all of this metadata at the start of the application, asynchronously. After this data is loaded the Observable calls complete. Eventually I may add a refresh capability
At some later point in the application, I will need to load specific sets of data based upon what is available in the metadata. I am currently attempting to do this with a function like fetchData(ids:string[]):Observable. This is where I am unclear about how to proceed under the rxjs paradigm. I am equally unsure of what to do with requesting a single item using a function like fetchDatum(id:string):Observable
I can of course use filter to operate only on those IMetdata items emitted from the IMetadata Observable that match one of the names in the list - but I also need to confirm that ALL requested items are found in the IMetadata Observable emissions, and if not I need to error.
So if someone requests the IMetadata with id = "Bob" - but there is no such IMetadata emitted from the source Observable, then it needs to error. Or if they request { "Shirley", "Rex", "Samantha" } and there is no data for "Rex" then it should error.
I've considered using a Rx.Subject here, but from what I've read that is generally undesirable under the rxjs paradigm. Please advise on what approaches would work for this scenario under the rxjs paradigm. Thanks!
Here's the solution I came up with. This function creates an Observable that relies upon a IBufferEvaluator to tell it what to do with each item that is emitted by the source Observable. It can APPEND the item to the buffer, SKIP the emitted item, CLEAR the buffer, FLUSH the buffer to the subscriber, etc. Let me know if you find a better way to do this, especially if its an out-of-the-box rxjs solution. Thanks.
import Rx from 'rxjs/Rx';
export enum BufferAction {
APPEND, /** Append the current emission to the buffer and continue **/
SKIP, /** Do nothing, ignoring the current emission if applicable **/
FLUSH, /** This will ignore the current emission, if applicable, and flush the existing buffer contents */
CLEAR, /** Clear the buffer contents. Ignore the current emission, if applicable */
COMPLETE, /** Mark the Observable as Complete. The buffer will be cleared upon completion. **/
APPEND_THEN_FLUSH, /** Append the current emission to the buffer prior to flushing it **/
APPEND_THEN_COMPLETE, /** Append the current emission to the buffer and then complete **/
CLEAR_THEN_APPEND, /** Clear the buffer contents and then append the current emission to it */
FLUSH_THEN_APPEND, /** Flush the buffer contents and then append the current emission to it */
FLUSH_THEN_COMPLETE, /** Flush the buffer contents and then mark the Observable as complete */
APPEND_FLUSH_COMPLETE /** Append the current emission, flush the buffer, and then complete */
}
export function bufferActionToString(action: BufferAction):string
{
switch(action)
{
case BufferAction.APPEND: return "APPEND";
case BufferAction.SKIP: return "SKIP";
case BufferAction.FLUSH: return "FLUSH";
case BufferAction.CLEAR: return "CLEAR";
case BufferAction.COMPLETE: return "COMPLETE";
case BufferAction.APPEND_THEN_FLUSH: return "APPEND_THEN_FLUSH";
case BufferAction.APPEND_THEN_COMPLETE: return "APPEND_THEN_COMPLETE";
case BufferAction.CLEAR_THEN_APPEND: return "CLEAR_THEN_APPEND";
case BufferAction.FLUSH_THEN_APPEND: return "FLUSH_THEN_APPEND";
case BufferAction.FLUSH_THEN_COMPLETE: return "FLUSH_THEN_COMPLETE";
case BufferAction.APPEND_FLUSH_COMPLETE: return "APPEND_FLUSH_COMPLETE";
default: return "Unrecognized Buffer Action [" + action + "]";
}
}
export interface IBufferEvaluator<T>
{
evalOnNext(next:T, buffer: T[]):BufferAction;
evalOnComplete(buffer: T[]):BufferAction;
}
/** bufferWithEval.ts
* An Operator that buffers the emissions from the source Observable. As each emission is recieved,
* it and the buffered emissions are evaluated to determine what BufferAction to APPEND. You can APPEND
* the current emission value to the end of the buffered emissions, you can FLUSH the buffered emissions
* before or after appending the current emission value, you can SKIP the current emission value and then
* (optionally) FLUSH the buffer, and you can CLEAR the buffer before or after appending the current emission.
*
* The evalOnNext and evalOnComplete are expected to return a BufferAction to indicate
* which action to take. If no evalOnNext is supplied, it will default to APPENDing each emission. The evalOnComplete
* will default to FLUSH_THEN_COMPLETE. If evalOnNext or evalOnComplete throw an exception, the Observable will emit
* the exception and cease.
*/
export function bufferWithEval<T>
( source: Rx.Observable<T>,
evaluatorFactory?: () => IBufferEvaluator<T>
) : Rx.Observable<T[]>
{
/** if no evaluatorFactory supplied, use the default evaluatorFactory **/
if(!evaluatorFactory)
{
evaluatorFactory = () => {
return {
evalOnNext : function(next: T, buffer: T[]) { return BufferAction.APPEND; },
evalOnComplete : function(buffer: T[]) { return BufferAction.FLUSH; }
};
}
}
return new Rx.Observable<T[]>((subscriber: Rx.Subscriber<T[]>) =>
{
var _buffer = new Array<T>();
var _evaluator = evaluatorFactory();
var _subscription: Rx.Subscription = null;
function append(next: T)
{
_buffer.push(next);
}
function flush()
{
try
{
subscriber.next(_buffer);
}
finally
{
// Ignore any exceptions that come from subscriber.next()
clear();
}
}
function clear()
{
_buffer = new Array<T>();
}
function next(next: T)
{
try
{
var action = _evaluator.evalOnNext(next, _buffer.slice(0));
switch(action)
{
case BufferAction.APPEND: { append(next); break; }
case BufferAction.SKIP: { break; }
case BufferAction.FLUSH: { flush(); break; }
case BufferAction.CLEAR: { clear(); break; }
case BufferAction.COMPLETE: { complete(); break; }
case BufferAction.APPEND_THEN_FLUSH: { append(next); flush(); break; }
case BufferAction.APPEND_THEN_COMPLETE: { append(next); complete(); break; }
case BufferAction.APPEND_FLUSH_COMPLETE: { append(next); flush(); complete(); break; }
case BufferAction.CLEAR_THEN_APPEND: { clear(); append(next); break; }
case BufferAction.FLUSH_THEN_APPEND: { flush(); append(next); break; }
case BufferAction.FLUSH_THEN_COMPLETE: { flush(); complete(); break; }
default: throw new Error("next(): Invalid BufferAction '" + bufferActionToString(action) + "'");
}
}
catch(e)
{
error(e);
}
}
function complete()
{
try
{
var action = _evaluator.evalOnComplete(_buffer.slice(0));
switch(action)
{
case BufferAction.FLUSH_THEN_COMPLETE:
case BufferAction.FLUSH: { flush(); }
case BufferAction.CLEAR:
case BufferAction.COMPLETE: { break; }
case BufferAction.APPEND:
case BufferAction.APPEND_THEN_FLUSH:
case BufferAction.APPEND_THEN_COMPLETE:
case BufferAction.APPEND_FLUSH_COMPLETE:
case BufferAction.SKIP:
case BufferAction.CLEAR_THEN_APPEND:
case BufferAction.FLUSH_THEN_APPEND:
default: throw new Error("complete(): Invalid BufferAction '" + bufferActionToString(action) + "'");
}
clear();
subscriber.complete();
_subscription.unsubscribe();
}
catch(e)
{
error(e);
}
}
function error(err: any)
{
try
{
subscriber.error(err);
}
finally
{
_subscription.unsubscribe();
}
}
_subscription = source.subscribe(next, error, complete);
return _subscription;
});
}

Resources