PeterStaev / NativeScript-Drop-Down

A NativeScript DropDown widget.
Apache License 2.0
105 stars 65 forks source link

iOS only: _onSelectedIndexPropertyChanged together with ValueKeyList may result app being in corrupted state (NS 2.5.0) #65

Closed creambyemute closed 7 years ago

creambyemute commented 7 years ago

Hey there, We were already using this plugin for some time now and I just found something odd on the iOS side after updating to NS 2.5 (with Angular 2). Let me show how we implemented the DropDown:

We have a settings page which has a DropDown: <DropDown #dd [items]="calendarListItems" [selectedIndex]="selectedIndex" (selectedIndexChange)="onSelectedIndexChange(dd.selectedIndex)" row="0" colSpan="2"></DropDown>

In the corresponding settings.component.ts: Imports import {ValueKeyList, IValueItem} from "../../shared/objects/ValueKeyList"; import { registerElement } from "nativescript-angular/element-registry"; registerElement("DropDown", () => require("nativescript-drop-down/drop-down").DropDown);

Variable Definitions (they are initialized in the constructor of the component) public selectedIndex; public calendarListItems: ValueKeyList; public calendarItems: Array<IValueItem>; public calendarEventEntries: Array<ICalendarEvent>;

ngAfterViewInit() {
    this.requestCalendarPermissionIfNeeded();
    const id = timer.setInterval(() => {
        Calendar.listCalendars().then(
            (calendars) => {
                calendars.forEach((calendar) => {
                    this.calendarItems.push({ValueMember: calendar.id, DisplayMember: calendar.name});
                });
                if (calendars.length > 0) {
                    this.selectedIndex = 0;
                }
                timer.clearInterval(id);
                this.calendarListItems = new ValueKeyList(this.calendarItems);
            },
            function (error) {
                console.log("Error while listing Calendars: " + error);
            }
        )
    }, 1000);
}

onSelectedIndexChange(selectedi) {
    this.selectedIndex = selectedi;
}

As soon as I open/enter the page I see an error logged in the terminal: CONSOLE ERROR file:///app/tns_modules/nativescript-angular/zone-js/dist/zone-nativescript.js:342:26: Unhandled Promise rejection: undefined is not an object (evaluating 'this._array[index].DisplayMember') ; Zone: angular ; Task: Promise.then ; Value: TypeError: undefined is not an object (evaluating 'this._array[index].DisplayMember') getText@file:///app/shared/objects/ValueKeyList.js:18:34

fired up by

_onSelectedIndexPropertyChanged@file:///app/tns_modules/nativescript-drop-down/drop-down.js:108:82 onSelectedIndexPropertyChanged@file:///app/tns_modules/nativescript-drop-down/drop-down-common.js:24:43

When navigating away from the page, all other pages do not work properly anymore (ListView never stops loading, Two-Way Binding on LoginPage not working anymore and therefore can't login without removing app from memory and restart it from scratch).

Do you have any idea what is going on here :)?

PeterStaev commented 7 years ago

Hey @creambyemute , it is really difficult to say, and it would be best if you just debug and see what is undefined exactly and why it is undefined and what is the sequence of calls/events. But couple of points:

It is best if you can provide a sample repo with the problem simulated.

creambyemute commented 7 years ago

Hey @PeterStaev

I already did some debugging and apparently the index is undefined when this occurs. I'll try your above mentioned tipps and report back the results.

If this doesn't work I'll create an sample app so you can reproduce it as well.

Edit: I'm still on it but was occupied with releasing other work related stuff so I'll update as soon as I know more

creambyemute commented 7 years ago

Hey @PeterStaev

I created a sample project where the above mentioned problems is reproducible.

On Android everything is working fine and you can logout and relogin again. On iOS as soon as you log in, logout and try to log in again it is broken (it gets in some weird state after logging in) and the above mentioned error is displayed. When you remove the DropDown from the html layout everything works fine.

You do not need any special login credentials, the fields just have to be filled with something.

Here's the sample project: mySampleDropDownApp.zip

magar91 commented 7 years ago

I'm having the same issue, with the same error and the same behaviour that @creambyemute described in my app. The selectedIndex is being set after the item source has been initialized. this is my .ts class

import {Component, OnInit, ViewChild, ElementRef, AfterViewInit, OnDestroy} from '@angular/core';
import { RouterExtensions } from 'nativescript-angular/router';
import { ValueList, IValueItem } from '../../shared/value-list/value-list.model';
import { Config } from '../../shared/config';
import * as dialogs from 'ui/dialogs';
import * as appSettings from 'application-settings';
import * as app from 'application';
@Component({
  selector: 'settings',
  templateUrl: 'pages/settings/settings.component.html',
  styleUrls: ['pages/settings/settings-common.css', 'pages/settings/settings.css'],
})
export class SettingsComponent implements OnInit, AfterViewInit, OnDestroy {

    public APIList : ValueList;
    public APIIndex : number;
    private config : Config;

    //settings helper variables
    private newApi : string;
    private defaultApi: string;
    private defaultIndex: number;

    ngOnInit(){
        this.config = new Config();
        this.APIList = new ValueList([
        {DisplayMember: "item1", ValueMember: "onslaught"},
        {DisplayMember: "item2", ValueMember: "ramhorn"}
    ]);
        let temp = appSettings.getString(this.config.API_KEY,"onslaught");
        console.info("temp"+temp);
        this.APIIndex = this.APIList.getIndex(temp);
        this.defaultIndex = this.APIIndex;
        console.info("apiIndex"+this.APIIndex);
        this.defaultApi = this.APIList.getText(this.APIIndex);
        console.info("defaultApi"+this.defaultApi);
        this.newApi = this.defaultApi;
        appSettings.remove(this.config.API_CHANGED_KEY);
    }

public ApiChanged(args)
    {
        if(app.ios)
        {
            if(args.newIndex !== args.oldIndex)
            {
                this.newApi = this.APIList.getText(args.newIndex);
                this.APIIndex = args.newIndex;
                console.info("args index: "+args.newIndex);
                console.info("apiIndex: "+this.APIIndex);
                this.APIsettingsChanged();
                this.showDialog();
            }
        }
        else if(args.newIndex !== args.oldIndex)
        {
            this.newApi = this.APIList.getText(args.newIndex);
            this.APIIndex = args.newIndex;
            console.info("args index: "+args.newIndex);
            console.info("apiIndex: "+this.APIIndex);
            this.APIsettingsChanged();
        }
    }
PeterStaev commented 7 years ago

Hey guys, take a look at the readme, I've just changed the code in for the ValueList that includes checks for null/undefined.

@creambyemute , as for your application there is another problem as well - like I already explained in my comment above and as written in the readme, you should use selectedIndexChanged event and not the built in angular one. So your drop down should look like:

<DropDown *ngIf="!!calendarListItems.length" #dd [items]="calendarListItems" [selectedIndex]="selectedIndex" (selectedIndexChanged)="onSelectedIndexChange($event)" row="0" colSpan="2"></DropDown>

and then in ts:

    onSelectedIndexChange(e) {
        this.selectedIndex = e.newIndex;
    }

Also as explained in the readme you should really register the drop down in the app.module file and not in the view itself. This way it will be available in all views and you wont have to worry about double registration.

Hope this helps.

magar91 commented 7 years ago

@PeterStaev thanks for your help that seems to fix the issue, I'm not getting the error anymore and the app seems to behave normally. If something comes up I'll write back here