Nativescript-ui-feedback: RadListView header and footer not reactive when bound value is changed

Created on 24 May 2019  Â·  23Comments  Â·  Source: ProgressNS/nativescript-ui-feedback

Tell us about the problem

RadListView headers won't refresh reactive objects by itself.

Which platform(s) does your issue occur on?

Both

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

  • Progress NativeScript UI plugin version: 6.3.0
  • Cross-platform modules: 5.0.2
  • Runtime(s): Android 5.3.1

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

  1. Create a simple component with RadListView
  2. Add a RadListView header
  3. Put some reactive variable in the header, like a Label with a reactive :text="reactiveTitle"
  4. Mutate your reactiveTitle

==> The Label should update itself, but it does not until I .refresh() the RadListView manually... Even doing so gives funky delay for updating the Label

Is there code involved? If so, please share the minimal amount of code needed to recreate the problem.

In vue:

<Button text="Change Title" @tap="changeTitle" />

<RadListView ref="scrollView" for="item in itemList" 
    pullToRefresh="true" top="50"
    @pullToRefreshInitiated="onPullToRefreshInitiated" @scrolled="onScroll">

          <v-template name="header">
            <StackLayout>
                <Label :text="reactiveTitle"/>
            </StackLayout>
          </v-template>
//...
export default {
   computed:{
       reactiveTitle(){
             return this.$store.state.reactiveTitle
       }
   },
   methods:{
       changeTitle(){
            this.$store.commit('CHANGE_TITLE') // mutates this.$store.state.reactiveTitle with random value
      },
   },
}

==> On Button click, nothing happens

bug listview medium vue

All 23 comments

any update on this issue?????

Hi @Jonarod @adetayoadeyemi ,

The described scenario is an expected one due to the current implementation of the header and footer support in RadListView. By design those additional views are not updated automatically for performance reasons and there is a dedicated API (updateheaderfooter()) similar to refresh() but with additional functionality that will "store: the current state of the list and not cause it to lose it scroll position etc. In order to refresh the header and/or footer you can call that API. We are always doing our best to resolve and improve this plugin so stay tuned. If anyone is interested in contributing to our project we do provide and NDA that would give you access to 100% of the plugin's source code, you can find more details here. We are always welcoming any contributions :) .

Thanks for the response, but, updateheaderfooter() is not working here, I am using NativeScript vue

Just found out the method only works on ios, not on Android, any option for Android??

Sorry I don't get the screenshot: it actually show it's not working...
"Enabled" should turn to "Disabled" after clicking the button, right?!

@Jonarod right! it's not working because, as @VladimirAmiorkov said, we need to explicitly call the updateHeaderFooter() method. So, we can workaround the issue with this code:

<template>
    <Page class="page">
        <ActionBar class="action-bar">
            <Label class="action-bar-title" text="Home"></Label>
        </ActionBar>

        <StackLayout>
            <Label :text="isEnabled" />
            <Button text="toggle flag" @tap="toggle" />
            <RadListView ref="listview" for="item in itemList">
                <v-template>
                    <StackLayout orientation="vertical">
                        <Label :text="item"></Label>
                    </StackLayout>
                </v-template>
                <v-template name="header">
                    <StackLayout>
                        <Label v-if="isEnabled" text="enabled" fontSize="30"></Label>
                        <Label v-else text="disabled" fontSize="30"></Label>
                    </StackLayout>
                </v-template>
                <v-template name="footer">
                    <Label text="Footer" fontSize="30"></Label>
                </v-template>
            </RadListView>
        </StackLayout>
    </Page>
</template>

<script>
    export default {
        data () {
            return {
                itemList: ['orange', 'apple', 'lemon'],
                isEnabled: true,
            }
        },
        methods: {
            toggle () {
                this.isEnabled = !this.isEnabled
            },
        },
        watch: {
            isEnabled (val) {
                this.$refs.listview.nativeView.updateHeaderFooter()
            }
        },
    };
</script>

workaround mov

@Jonarod I will expose the updateHeaderFooter() method to the Vue component so in the next release we may call it like this:

        watch: {
            isEnabled (val) {
                this.$refs.listview.updateHeaderFooter()
            }
        },

Hi @Jonarod ,

The above screen shot was added to better illustrate the issue as it is showing it. I am currently looking into the updateHeaderFooter() not working on Android as you mentioned. Stay tuned for more info.

As the API states that method is only applicable for iOS and it was never necessary for Android.

/**

  • Refreshes the view/template set to the header and footer elements of {@link RadListView} by rebinding it to the source, triggering new measuring and layout circle.
  • Note: This method is only supported in iOS, calling it on Android does nothing.
    */
    updateHeaderFooter(): void;

Oh got it.
Thank you very much for this input, it will be of great help to use
ListView.
Thanks again, looking forward to test this !!
:)

On Mon, Jul 15, 2019, 09:01 Vladimir Amiorkov notifications@github.com
wrote:

As the API states that method is only applicable for iOS and it was never
necessary for Android.

/**

  • Refreshes the view/template set to the header and footer elements of
    {@link https://github.com/link RadListView} by rebinding it to the
    source, triggering new measuring and layout circle.
  • Note: This method is only supported in iOS, calling it on Android
    does nothing.
    */
    updateHeaderFooter(): void;

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/NativeScript/nativescript-ui-feedback/issues/1130?email_source=notifications&email_token=ABOWZT23FQLC24OLTEMD5QLP7RRKVA5CNFSM4HPNGIY2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODZ5PF5A#issuecomment-511374068,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABOWZT4ZAZPZBTH6FBL4BZLP7RRKVANCNFSM4HPNGIYQ
.

Hi @Jonarod @msaelices ,

I managed to reproduce this issue and unfortunately this is a specific issue only for Vue projects. The same scenario works as expected in both Vanila and Angular projects as shown here.

The updateHeaderFooter() is an API that is only required for iOS due to the way the RadListView has bee implement on that platform. On Android this is handled by the OS itself and does not requre additional manual refresh. Unfortunately it looks like only in Vue projects even the automatic Android refresh is not working maybe because of the binding of Vue itself. Marking this as a bug and it needs further investigation.

I also observed that, if you have a RadListView connected to an Array of objects in Vue, if you update any property of one of the Items in the Array (for example, array[index].isFav = true) THE HEADER IS ALSO UPDATED WHEN THAT PARTICULAR OBJECT IS UPDATED (that is, previous updates made to the header, that did not reflect, will then reflect)

This update actually redraws the header completely (in my own case, I had an horizontal ScrollView in my header) when the update happens, the Horizontal ScrollView goes back to scroll index 0

@adetayoadeyemi Yes, when the items is set to an simple Array changes to any of its objects directly calls the refresh() API. This is due to the way VueJS observables are being propagated to the RadListView's custom Vue directive. We have investigated multiple difference approaches to handle this more gracefully but as of this moment there is no better implementation available. This is a limitation that that comes when the RadListView is bound to an simple Array, can you change that to an ObservableArray and see how that behaves?

When using ObservableArray, changes to A PROPERTY OF AN OBJECT does not show on the UI

After looking into this, it looks like this is not caused by the type of the bound collection but rather the way Vue handles its updates to the UI via its bindings. The same scenario works as expected on both Angular and Vanila NativeScript where there is not additional code to tell the UI that its context has been changed. This in VueJS is not the case, in it the bindings do not tell the UI to update and the observed issue is present.

In order to workaround this limitation you can do the following:

  • Use ObservableArray (RadListView has specific logic for that type of collection due to its behavior which allows us to handle "light weight" updates
  • In the code where you change one of the properties of the bound object (example the .name property). Instead of directly changing that items property make a copy of it and replace the entire object with the updated element.

Example code:

import { getItemList } from '../data';
import * as frameModule from "tns-core-modules/ui/frame";

const description = 'Getting Started';
export default {
  name: 'GettingStarted',
  description: description,
  template: `
  <Page>
    <ActionBar :title="title">
      <NavigationButton text="Back" android.systemIcon="ic_menu_back" @tap="onNavigationButtonTap"></NavigationButton>
    </ActionBar>
    <GridLayout rows="auto, *">
      <Button text="Update"  @tap="onUpdate"></Button>
      <RadListView ref="listView"
                    row="1"
                   for="item in itemList"
                   @itemTap="onItemTap">
        <v-template>
          <StackLayout class="item" orientation="vertical">
            <Label :text="item.name" class="nameLabel" ></Label>
            <Label :text="item.description" class="descriptionLabel"></Label>
          </StackLayout>
        </v-template>
      </RadListView>
    </GridLayout>
  </Page>
  `,
  data() {
    return {
      title: description,
      itemList: getItemList(30),
    };
  },
  methods: {
    onItemTap({ item }) {
      console.log(`Tapped on ${item.name}`);
    },
    onUpdate() {
      // Workaround to UI not being updated when directly changing '.name' of object at 'index' in 'itemList'
      let index = 20;
      let oldItem = this.itemList.getItem(index);
      oldItem.name = "New Name";
      this.itemList.setItem(index, oldItem);
    }
  }
};

@VladimirAmiorkov there is a simpler way to notify Vue that the object is changed, using Vue.set() or this.$set() as we are inside the Vue component. See https://vuejs.org/v2/guide/list.html#Caveats

BTW: I think this is not related to the current header and footer issue, which is a real bug.

Well on Android I think it is related because as I wrote above the scenario where the "bindingContext" of the Header/Footer is changed when working in Angular or Vanila projects it automatically updates the UI and this is handed outside of the RadListView itself, on iOS we need to do it manually which is why we got the API for it updateHeaderFooter(). This leads me to believe that either this is a limitation that comes from VueJS itself that we need to manually handle ourselves in our custom directive, unfortunately I am not that familiar with VueJS itself and its limitations so I cannot confirm or deny this statement. If you have any idea I would appreciate your help.

Basically the issue in the Header/Footer is that the when the bindingContext is changed that is not propagated to the UI. On Android this is done by the bindingContext of the nativeElement and it looks like the connection between VueJS and the native element's bindingContext is broken and is simply used initially to set the value. I am not sure if we can expose an API similar to updateHeaderFooter() for Android in order to refresh the header/footer views but this would be strange requirement that is only needed for VueJS.

@VladimirAmiorkov sure, I will work on it.

@VladimirAmiorkov I've studied the case and documented it here: https://github.com/nativescript-vue/nativescript-vue/issues/525

It's the same issue for the RadListView component and the regular ListView one.

@msaelices Thank for looking into this, I was suspecting this exact source of this issue. As of right now I am not sure how and where this issue should be handled.

Update to the issue status:
On iOS:

On Android:

  • There is an issue logged in nativescript-vue that affect all NativeScript components that use RecyclerView in order to implement a list on the Android platform. Please follow this issue for future updates.

I am closing this issue for now as we need to look into resolving the generic issue that is logged here first.

Was this page helpful?
0 / 5 - 0 ratings