Search code examples
dartpolymerdart-polymer

Why isn't my updated observable List reflected in the template?


I've got:

my-app
  community-list

On attached, my-app gets the user and loads the app.user. In the meantime, community-list is attached (even before app.user is loaded) and so I haven't been able to get the user's starred communities yet. Therefore, the solution I'm working on is as follows.

In community-list.attached():

app.changes.listen((List<ChangeRecord> records) {
  if (app.user != null) {
    getUserStarredCommunities();
  }
});

Elsewhere in community-list is said metho:

  // This is triggered by an app.changes.listen.
  void getUserStarredCommunities() {
    // Determine if this user has starred the community.
    communities.forEach((community) {
      var starredCommunityRef = new db.Firebase(firebaseLocation + '/users/' + app.user.username + '/communities/' + community['id']);
      starredCommunityRef.onValue.listen((e) {
        if (e.snapshot.val() == null) {
          community['userStarred'] = false;
        } else {
          community['userStarred'] = true;
        }
      });
    });
  }

Note that communities is an observable list in community-list:

@observable List communities = toObservable([]);

Which is initially populated in community-list.attached():

getCommunities() {
    var f = new db.Firebase(firebaseLocation + '/communities');

    var communityRef = f.limit(20);
    communityRef.onChildAdded.listen((e) {
      var community = e.snapshot.val();

      // If no updated date, use the created date.
      if (community['updatedDate'] == null) {
        community['updatedDate'] = DateTime.parse(community['createdDate']);
      }

      // snapshot.name is Firebase's ID, i.e. "the name of the Firebase location"
      // So we'll add that to our local item list.
      community['id'] = e.snapshot.name();

      // Insert each new community into the list.
      communities.add(community);

      // Sort the list by the item's updatedDate, then reverse it.
      communities.sort((m1, m2) => m1["updatedDate"].compareTo(m2["updatedDate"]));
      communities = communities.reversed.toList();
    });
  }

In summary, I load the list of communities even before I have a user, but once I have a user I want to update each community (Map) in the list of communities with the userStarred = true/false, which I then use in my community-list template.

  1. Alas, it doesn't seem like the List updates. How do I achieve this?
  2. This whole app.changes.listen business is expensive. What's the proper practice in a case like this, where an element is loaded before I load objects (like app.user) that will modify it in some way.

Solution

  • 1) toList() creates a copy of the list. You need to apply toObservable again to get an observable list.

    communities = toObservable(communities.reversed.toList());
    

    This also assigns a new list to communities which is covered by @observable. I think it should trigger anyway

    2) You update your communities explicitly. It shouldn't be necessary to listen for changes. You can call a method containing

    if (app.user != null) {
      getUserStarredCommunities();
    }
    

    explicitly each time you change the list.

    You also call Firebase for each community when a change in communities occurs. I don't know Firebase but it seems you send a request to a server each time which is of course expensive. You should remember for what user+community combination you already made the call and use the remembered result instead.

    With app.changes.listen you listen to any updated of any @observable field in your component. If you have other observable fields beside communities this method might be called too often. If you are only interested in changes to communities you should put this code into a method like

    communitiesChanged(oldVal, newVal) {
      if (app.user != null) {
        getUserStarredCommunities();
      }
    }
    

    but the better option is to not listen to changes and another method name and call it explicitly as state above anyways if possible.