Observing an object containing a list of observables?

I am using watch package .

Consider the following example:

class Product extends Object with ChangeNotifier {

  double _price = 0.0;

  @reflectable double get price => _price;
  @reflectable void set price(double value) {
    if (value == null) throw new ArgumentError();
    _price = notifyPropertyChange(#price, price, value);
  }
}

class Order extends Object with ChangeNotifier {

  final ObservableList<Product> products = new ObservableList<Product>();

  double get total {
    double sum = 0.0;
    for (var item in products) {
      sum += item.price;
    }
    return sum;
  }
}

// Synchronizes the view total with the order total.
// Or rather, I'd like it to do that.
var order = new Order();
order.changes.listen((records) {
  view.total = order.total;
});

      

How should I rewrite this example to make it work?

I would like to be notified of any changes to the state of an object, even if they happen to a list or list items.

Do I need to manage change subscriptions for all items and the list itself? Inside or outside the classroom Order

? Through what property can I notify about this change? Seems messy anyway.

+3


source to share


2 answers


Items in ObservableList

do not propagate notice to the list containing them. They can't because they don't have a link to the list. Also, the list does not forward notifications to the class it refers to.

Not very satisfying, but the best I could think of.

import 'dart:async' as async;
import 'package:observe/observe.dart';

class Product extends Object with ChangeNotifier {

  double _price = 0.0;

  @reflectable double get price => _price;
  @reflectable void set price(double value) {
    if (value == null) throw new ArgumentError();
    _price = notifyPropertyChange(#price, price, value);
  }

  @override
  String toString() => 'Product - price: $price';
}

class Order extends Object with ChangeNotifier {

  final ObservableList<Product> products = new ObservableList<Product>();

  // keep listeners to be able to cancel them
  final List<async.StreamSubscription> subscriptions = [];

  Order() {
    products.changes.listen((cr) {
      // only react to length changes (isEmpty, isNotempty changes are redundant)
      var lengthChanges = cr.where((c) => c.name == #length);
      if(lengthChanges.isNotEmpty) {
        lengthChanges.forEach((lc) =>
        notifyChange(lc));
        // we can't know if only additions/removals were done therefore we
        // cancel all existing listeners and set up new ones for all items
        // after each length change
        _updateProductsListeners();
      }
    });
    // initial setup
    _updateProductsListeners();
  }

  // cancel all product change listeners and create new ones 
  void _updateProductsListeners() {
    print('updateListeners');
    subscriptions.forEach((s) => s.cancel());
    subscriptions.clear();
    products.forEach((p)
    => subscriptions.add(p.changes.listen((crs) =>
    crs.forEach((cr) =>
      notifyPropertyChange(cr.name, cr.oldValue, cr.newValue)))));
  }

  double get total {
    double sum = 0.0;
    for (var item in products) {
      sum += item.price;
    }
    return sum;
  }
}

void main() {
// Synchronizes the view total with the order total.
// Or rather, I'd like it to do that.
  var order = new Order();
  order.changes.listen((records) {
    //view.total = order.total;
    records.forEach(print);
  });

  // a PathObserver example but it doesn't seem to be more convenient
  var op = new PathObserver(order, 'products[3].price')..open((c) =>
    print(c));

  var prods = [new Product()..price = 1.0, new Product()..price = 2.0, new Product()..price = 3.0, new Product()..price= 4.0];
  var prods2 = [new Product()..price = 5.0, new Product()..price = 6.0];

  order.products.addAll(prods);

  // use Future to allow change notification propagate between changes
  new async.Future(() =>
  order.products..addAll(prods2)..removeWhere((p) => p.price < 3.0))
  .then((_) => new async.Future(() => order.products[3].price = 7.0));

  new async.Future.delayed(new Duration(seconds: 1), () => print('done'));
}

      



I suggest using something like an event bus for this, where objects that want / need to notify of something are just dispatching, and events and objects that are interested in something listen for that without knowing where the other object is.

For example https://pub.dartlang.org/packages/event_bus

+2


source


Another solution is to use ListPathObserver . The class is deprecated, but you can copy the code and reuse it. With this class, you can listen for specific changes in the contained elements. The field for viewing is indicated along the path.



0


source







All Articles