NativeScript: ListView not updating on Android

Tell us about the problem

The listview on Android behaves oddly when updating elements on particular rows, using Angular and TS, Observable and ObservableArray

Which platform(s) does your issue occur on?

Android Only

Please provide the following version numbers that your issue occurs with:

{ “description”: “NativeScript Application”, “license”: “SEE LICENSE IN <your-license-filename>”, “readme”: “NativeScript Application”, “repository”: “<fill-your-repository-here>”, “nativescript”: { “id”: “org.nativescript.nativescriptexperimentts”, “tns-android”: { “version”: “2.3.0” }, “tns-ios”: { “version”: “2.3.0” } }, “scripts”: { “build”: “tns run android --device 1 --watch”, “livesync”: “tns livesync android --device 1 --watch” }, “dependencies”: { “@angular/common”: “2.1.2”, “@angular/compiler”: “2.1.2”, “@angular/core”: “2.1.2”, “@angular/forms”: “2.1.2”, “@angular/http”: “2.1.2”, “@angular/platform-browser”: “2.1.2”, “@angular/platform-browser-dynamic”: “2.1.2”, “@angular/router”: “3.1.2”, “nativescript-angular”: “1.1.3”, “nativescript-theme-core”: “^0.2.1”, “reflect-metadata”: “~0.1.8”, “rxjs”: “5.0.0-beta.12”, “tns-core-modules”: “2.4.0”, “typed-event-emitter”: “^1.0.3”, “typescript-collections”: “^1.2.3” }, “devDependencies”: { “babel-traverse”: “6.19.0”, “babel-types”: “6.19.0”, “babylon”: “6.14.1”, “lazy”: “1.0.11”, “nativescript-dev-android-snapshot”: “^0..”, “nativescript-dev-typescript”: “^0.3.2”, “typescript”: “^2.0.10”, “zone.js”: “~0.6.21” } }

Please tell us how to recreate the issue in as much detail as possible.

  1. Clone repo https://github.com/johnnyzen/NativeScript-Issue
  2. Run the project on Android
  3. Click the Add button to add a row
  4. You should see a counter on the row just added increment by 1
  5. Add another row using the Add button. Again this newly added row should also have a counter incrementing by 1.
  6. Now keep pressing the “Press Me” on the first row until the counter reaches 0
  7. When the first row counter reaches zero, the rest of the list items stop updating

Expected Behaviour

The rest of the items should continue to update as usual. THIS WORKS FINE ON IOS

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 15 (3 by maintainers)

Most upvoted comments

Hi all,

I’ve investigated this issue with the project from here and the problem is visible with the latest version (if you remove GridLayout that wraps the ListView control). However the problem comes from the fact that native Android ListView control gives a visual items in an unpredictable order (when placed in a container with an unspecified height like StackLayout). I’ll try to explain:

With the first tap on a button Add, one DataModel is added to the collection. The interesting part is what happens on the background. Since ListView.items collection is changed ListView should update its UI - therefore creates a new item (counter.component) for added DataModel instance. This view is new and its angular representation has not been checked and ngOnInit method is called after first check. Here code subscribes for its DataModel changes and update counter. So far so good. With the second tap, another DataModel is added to collection. Here ListView again is trying to update UI, so it is trying to create views for both items. Unfortunately due to (a little bit weird logic (imo)) inside native Android ListView recycling logic it creates a new view for DataModel at 0 (first) inside data.collection and this view is clean and angular calls ngOnInit for it again, so code again subscribes for changes of data.collection[0]. Most interesting part is that when creating view for data.collection[1] item Android ListView returns the view that was previously used for data.collection[0], but this view has been checked for changes and its ngOnInit is not called.

The solution is very simple just to be strict on MVVM pattern. As it turns out ngOnInit is part of the view part. I’ve made a fix for the problem - making subscription when input property model is changed. A sample code is worth a thousand words:

// counter.component.ts
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from "@angular/core";
import DataModel from '../../models/data.model';

@Component({
    moduleId: module.id,
    selector: "counter",
    templateUrl: "counter.component.html",
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {

    private _model: DataModel;
    private modelChangeHandler: (args) => void = (args) => {
        this.cd.markForCheck();
    };

    @Input()
    set model(model: DataModel) {
        if (this._model) {
            this._model.off(DataModel.propertyChangeEvent, this.modelChangeHandler);
        }
        this._model = model;
        this._model.on(DataModel.propertyChangeEvent, this.modelChangeHandler);
    }
    get model() { return this._model; }
    
    constructor(private cd: ChangeDetectorRef) {
    }

    onTap() {
        this.model.increaseCounter();
    }
}