Chapter 24 Challenge 2: Add live statistics

I noticed that after following through with the challenge, the stats only update when toggling the check box on a task item. When creating a new task, or deleting one, the stats stay the same until a task is toggled, or the app is terminated and run again. The behaviour is the same when running the code from the challenges folder.

Is there a reason why the stats aren’t updated in these situations? I tried to figure out what was going on but couldn’t in the end :sweat:

To answer my own question:

The challenge suggests to use zip on the realm queries for due and done items. If I understand it right, the query for the done items will not generate a result when adding or removing an item. Because zip waits for both observables to emit a new value, it will never emit actually emit a value on adding or deleting an item.

In order to have true live stats, you’d want to use combineLatest instead of zip.

Using combineLatest does solve the problem of stats not updating on creation or deletion. That being said, I’m not 100% sure my reasoning is correct :sweat_smile:

@fpillet Do you have any feedback regarding this? Thank you - much appreciated! :]

Thank you for noticing this mistake. You are correct than no changing the number of checked items doesn’t produce updated statistics when you add a new TODO.

The change you suggest is a good solution. The reason for zip not working is an optimization provided by Realm: when you filter a Realm collection, you are not merely using the Swift filter operator. You are using Realm’s version of filter which applies a predicate to the changes then emits appropriate changesets.

Inserting a new item doesn’t change the number of checked items, which is what the filter is looking at. Therefore, and since you don’t get a change, the zip operator patiently waits for the todoTasks sequence to emit something, which happens only when an items is checked or unchecked (due to the aforementioned optimization).

Using combineLatest is a correct way to fix this problem. At least one of the sequences will emit on item insert / delete (the tasks sequence) and trigger the production of a statistics update:

      let tasks = realm.objects(TaskItem.self)
      let todoTasks = tasks.filter("checked != nil")
      return .combineLatest(
        Observable.collection(from: tasks)
          .map { $0.count },
        Observable.collection(from: todoTasks)
          .map { $0.count }) { all, done in
        (todo: all - done, done: done)
      }

Keeping zip is possible, if you base both sequences off the tasks sequence then further filter the results after tasks emits something:

      let tasks = realm.objects(TaskItem.self)
      return .zip(
        Observable.collection(from: tasks)
          .map { $0.count },
        Observable.collection(from: tasks)
          .map { $0.filter("checked != nil").count }) { all, done in
        (todo: all - done, done: done)
      }

The combineLatest solution is possible slightly more efficient since the other one has to further recreate filtered results, possibly eliminating internal optimizations that Realm may provide.

Congratulations on taking up the challenge so seriously as to beat the authors to their own challenges!

This topic was automatically closed after 166 days. New replies are no longer allowed.