Update to pkg/observable 0.17.0 (#92)

* Revert "Revert "Update to pkg/observable 0.17.0""

This reverts commit 77f5f61154c21aecf16fbf1242115efea5382d33.

* Perf tweak
diff --git a/benchmark/index.dart b/benchmark/index.dart
deleted file mode 100644
index 49e7cad..0000000
--- a/benchmark/index.dart
+++ /dev/null
@@ -1,171 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.index;
-
-import 'dart:async';
-import 'dart:html';
-import 'package:benchmark_harness/benchmark_harness.dart';
-import 'package:chart/chart.dart';
-import 'package:observe/mirrors_used.dart' as mu; // Makes output smaller.
-import 'package:smoke/mirrors.dart';
-import 'object_benchmark.dart';
-import 'setup_object_benchmark.dart';
-import 'observable_list_benchmark.dart';
-import 'setup_observable_list_benchmark.dart';
-import 'path_benchmark.dart';
-import 'setup_path_benchmark.dart';
-
-/// Benchmark names to factory functions.
-/// Uses [mu].
-typedef BenchmarkBase BenchmarkFactory(
-    int objectCount, int mutationCount, String config);
-final Map<String, BenchmarkFactory> benchmarkFactories = {
-  'ObjectBenchmark': (int o, int m, String c) => new ObjectBenchmark(o, m, c),
-  'SetupObjectBenchmark': (int o, int m, String c) =>
-      new SetupObjectBenchmark(o, c),
-  'ObservableListBenchmark': (int o, int m, String c) =>
-      new ObservableListBenchmark(o, m, c),
-  'SetupObservableListBenchmark': (int o, int m, String c) =>
-      new SetupObservableListBenchmark(o, c),
-  'PathBenchmark': (int o, int m, String c) => new PathBenchmark(o, m, c),
-  'SetupPathBenchmark': (int o, int m, String c) =>
-      new SetupPathBenchmark(o, c),
-};
-
-/// Benchmark names to possible configs.
-final Map<String, List<String>> benchmarkConfigs = {
-  'ObjectBenchmark': [],
-  'SetupObjectBenchmark': [],
-  'ObservableListBenchmark': ['splice', 'update', 'push/pop', 'shift/unshift'],
-  'SetupObservableListBenchmark': [],
-  'PathBenchmark': ['leaf', 'root'],
-  'SetupPathBenchmark': [],
-};
-
-Iterable<int> objectCounts;
-Iterable<int> mutationCounts;
-
-final ButtonElement goButton = querySelector('#go');
-final InputElement objectCountInput = querySelector('#objectCountInput');
-final InputElement mutationCountInput = querySelector('#mutationCountInput');
-final SpanElement mutationCountWrapper = querySelector('#mutationCountWrapper');
-final SpanElement statusSpan = querySelector('#status');
-final DivElement canvasWrapper = querySelector('#canvasWrapper');
-final SelectElement benchmarkSelect = querySelector('#benchmarkSelect');
-final SelectElement configSelect = querySelector('#configSelect');
-final UListElement legendList = querySelector('#legendList');
-final List<String> colors = [
-  [0, 0, 255],
-  [138, 43, 226],
-  [165, 42, 42],
-  [100, 149, 237],
-  [220, 20, 60],
-  [184, 134, 11]
-].map((rgb) => 'rgba(' + rgb.join(',') + ',.7)').toList();
-
-main() {
-  // TODO(jakemac): Use a transformer to generate the smoke config so we can see
-  // how that affects the benchmark.
-  useMirrors();
-
-  benchmarkSelect.onChange.listen((_) => changeBenchmark());
-  changeBenchmark();
-
-  goButton.onClick.listen((_) async {
-    canvasWrapper.children.clear();
-    goButton.disabled = true;
-    goButton.text = 'Running...';
-    legendList.text = '';
-    objectCounts =
-        objectCountInput.value.split(',').map((val) => int.parse(val));
-
-    if (benchmarkSelect.value.startsWith('Setup')) {
-      mutationCounts = [0];
-    } else {
-      mutationCounts =
-          mutationCountInput.value.split(',').map((val) => int.parse(val));
-    }
-
-    var i = 0;
-    mutationCounts.forEach((count) {
-      var li = document.createElement('li');
-      li.text = '$count mutations.';
-      li.style.color = colors[i % colors.length];
-      legendList.append(li);
-      i++;
-    });
-
-    var results = <List<double>>[];
-    for (int objectCount in objectCounts) {
-      int x = 0;
-      for (int mutationCount in mutationCounts) {
-        statusSpan.text =
-            'Testing: $objectCount objects with $mutationCount mutations';
-        // Let the status text render before running the next benchmark.
-        await new Future(() {});
-        var factory = benchmarkFactories[benchmarkSelect.value];
-        var benchmark = factory(objectCount, mutationCount, configSelect.value);
-        // Divide by 10 because benchmark_harness returns the amount of time it
-        // took to run 10 times, not once :(.
-        var resultMicros = benchmark.measure() / 10;
-
-        if (results.length <= x) results.add([]);
-        results[x].add(resultMicros / 1000);
-        x++;
-      }
-    }
-
-    drawBenchmarks(results);
-  });
-}
-
-void drawBenchmarks(List<List<double>> results) {
-  var datasets = [];
-  for (int i = 0; i < results.length; i++) {
-    datasets.add({
-      'fillColor': 'rgba(255, 255, 255, 0)',
-      'strokeColor': colors[i % colors.length],
-      'pointColor': colors[i % colors.length],
-      'pointStrokeColor': "#fff",
-      'data': results[i],
-    });
-  }
-  var data = {
-    'labels': objectCounts.map((c) => '$c').toList(),
-    'datasets': datasets,
-  };
-
-  new Line(data, {
-    'bezierCurve': false,
-  }).show(canvasWrapper);
-  goButton.disabled = false;
-  goButton.text = 'Run Benchmarks';
-  statusSpan.text = '';
-}
-
-void changeBenchmark() {
-  var configs = benchmarkConfigs[benchmarkSelect.value];
-  configSelect.text = '';
-  configs.forEach((config) {
-    var option = document.createElement('option');
-    option.text = config;
-    configSelect.append(option);
-  });
-
-  document.title = benchmarkSelect.value;
-
-  // Don't show the configSelect if there are no configs.
-  if (configs.isEmpty) {
-    configSelect.style.display = 'none';
-  } else {
-    configSelect.style.display = 'inline';
-  }
-
-  // Don't show the mutation counts box if running a Setup* benchmark.
-  if (benchmarkSelect.value.startsWith('Setup')) {
-    mutationCountWrapper.style.display = 'none';
-  } else {
-    mutationCountWrapper.style.display = 'inline';
-  }
-}
diff --git a/benchmark/index.html b/benchmark/index.html
deleted file mode 100644
index ce2bf9f..0000000
--- a/benchmark/index.html
+++ /dev/null
@@ -1,73 +0,0 @@
-<html>
-<!--
-Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
-This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
-The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
-The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
-Code distributed by Google as part of the polymer project is also
-subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
--->
-<head>
-  <title>Observation Benchmarks</title>
-  <meta charset="utf-8">
-  <style>
-    * {
-      font-family: arial, helvetica, sans-serif;
-      font-weight: 400;
-    }
-    body {
-      margin: 0;
-      padding: 0;
-      background-color: rgba(0, 0, 0, .1);
-    }
-    #canvasWrapper {
-      width: 800px;
-      height: 400px;
-    }
-  </style>
-</head>
-<body>
-<h1>Observation Benchmarks</h1>
-
-<select id="benchmarkSelect">
-  <option>ObjectBenchmark</option>
-  <option>SetupObjectBenchmark</option>
-  <option>ObservableListBenchmark</option>
-  <option>SetupObservableListBenchmark</option>
-  <option>PathBenchmark</option>
-  <option>SetupPathBenchmark</option>
-</select>
-<select id="configSelect">
-</select>
-
-<button id="go">Run Benchmarks</button>
-
-<span>Object Count: </span>
-<input id="objectCountInput" style="width: 200px" value="4000, 8000, 16000, 32000">
-<span id="mutationCountWrapper">
-  <span>Mutation Count: </span>
-  <input id="mutationCountInput" style="width: 200px"
-         value="0, 100, 200, 400, 800, 1600"><br>
-</span>
-<br>
-<span id="status"></span>
-
-<section style="width: 100%">
-  <article>
-    <div style="display:inline-block; padding-bottom: 20px">
-      Times in ms
-    </div>
-    <div id="canvasWrapper" style="display:inline-block">
-    </div>
-    <div style="display:inline-block">
-      <ul id="legendList">
-      </ul>
-    </div>
-  </article>
-</section>
-<h3 style="margin-left: 440px">Object Set Size</h3>
-<script type="application/dart" src="index.dart"></script>
-<script src="packages/browser/dart.js">
-</script>
-</body>
-</html>
diff --git a/benchmark/object_benchmark.dart b/benchmark/object_benchmark.dart
deleted file mode 100644
index 91282be..0000000
--- a/benchmark/object_benchmark.dart
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.object_benchmark;
-
-import 'observation_benchmark_base.dart';
-import 'test_observable.dart';
-
-class ObjectBenchmark extends ObservationBenchmarkBase<TestObservable> {
-  ObjectBenchmark(int objectCount, int mutationCount, String config)
-      : super('ObjectBenchmark:$objectCount:$mutationCount:$config',
-            objectCount, mutationCount, config);
-
-  @override
-  int mutateObject(TestObservable obj) {
-    // Modify the first 5 properties.
-    obj.a++;
-    obj.b++;
-    obj.c++;
-    obj.d++;
-    obj.e++;
-    // Return # of modifications.
-    return 5;
-  }
-
-  @override
-  TestObservable newObject() => new TestObservable();
-}
diff --git a/benchmark/observable_list_benchmark.dart b/benchmark/observable_list_benchmark.dart
deleted file mode 100644
index d0f8ad9..0000000
--- a/benchmark/observable_list_benchmark.dart
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.observable_list_benchmark;
-
-import 'package:observable/observable.dart';
-import 'observation_benchmark_base.dart';
-
-class ObservableListBenchmark extends ObservationBenchmarkBase<ObservableList> {
-  final int elementCount = 100;
-
-  ObservableListBenchmark(int objectCount, int mutationCount, String config)
-      : super('ObservableListBenchmark:$objectCount:$mutationCount:$config',
-            objectCount, mutationCount, config);
-
-  @override
-  int mutateObject(ObservableList obj) {
-    switch (config) {
-      case 'update':
-        var size = (elementCount / 10).floor();
-        for (var j = 0; j < size; j++) {
-          obj[j * size]++;
-        }
-        return size;
-
-      case 'splice':
-        var size = (elementCount / 5).floor();
-        // No splice equivalent in List, so we hardcode it.
-        var removed = obj.sublist(size, size * 2);
-        obj.removeRange(size, size * 2);
-        obj.insertAll(size * 2, removed);
-        return size * 2;
-
-      case 'push/pop':
-        var val = obj.removeLast();
-        obj.add(val + 1);
-        return 2;
-
-      case 'shift/unshift':
-        var val = obj.removeAt(0);
-        obj.insert(0, val + 1);
-        return 2;
-
-      default:
-        throw new ArgumentError(
-            'Invalid config for ObservableListBenchmark: $config');
-    }
-  }
-
-  @override
-  ObservableList newObject() {
-    var list = new ObservableList();
-    for (int i = 0; i < elementCount; i++) {
-      list.add(i);
-    }
-    return list;
-  }
-}
diff --git a/benchmark/observation_benchmark_base.dart b/benchmark/observation_benchmark_base.dart
deleted file mode 100644
index f8fc983..0000000
--- a/benchmark/observation_benchmark_base.dart
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.observation_benchmark_base;
-
-import 'dart:async';
-import 'dart:html';
-
-import 'package:observable/observable.dart';
-import 'package:observe/observe.dart';
-import 'package:benchmark_harness/benchmark_harness.dart';
-
-abstract class ObservationBenchmarkBase<T extends Observable>
-    extends BenchmarkBase {
-  /// The number of objects to create and observe.
-  final int objectCount;
-
-  /// The number of mutations to perform.
-  final int mutationCount;
-
-  /// The current configuration.
-  final String config;
-
-  /// The number of pending mutations left to observe.
-  int mutations;
-
-  /// The objects we want to observe.
-  List<T> objects;
-
-  /// The change listeners on all of our objects.
-  List observers;
-
-  /// The current object being mutated.
-  int objectIndex;
-
-  ObservationBenchmarkBase(
-      String name, this.objectCount, this.mutationCount, this.config)
-      : super(name);
-
-  /// Subclasses should use this method to perform mutations on an object. The
-  /// return value indicates how many mutations were performed on the object.
-  int mutateObject(T obj);
-
-  /// Subclasses should use this method to return an observable object to be
-  /// benchmarked.
-  T newObject();
-
-  /// Subclasses should override this to do anything other than a default change
-  /// listener. It must return either a StreamSubscription or a PathObserver.
-  /// If overridden this observer should decrement [mutations] each time a
-  /// change is observed.
-  newObserver(T obj) {
-    decrement(_) => mutations--;
-    if (obj is ObservableList) return obj.listChanges.listen(decrement);
-    return obj.changes.listen(decrement);
-  }
-
-  /// Set up each benchmark by creating all the objects and listeners.
-  @override
-  void setup() {
-    mutations = 0;
-
-    objects = [];
-    observers = [];
-    objectIndex = 0;
-
-    while (objects.length < objectCount) {
-      var obj = newObject();
-      objects.add(obj);
-      observers.add(newObserver(obj));
-    }
-  }
-
-  /// Tear down each benchmark and make sure that [mutations] is 0.
-  @override
-  void teardown() {
-    if (mutations != 0) {
-      window.alert('$mutations mutation sets were not observed!');
-    }
-    mutations = 0;
-
-    while (observers.isNotEmpty) {
-      var observer = observers.removeLast();
-      if (observer is StreamSubscription) {
-        observer.cancel();
-      } else if (observer is PathObserver) {
-        observer.close();
-      } else {
-        throw 'Unknown observer type ${observer.runtimeType}. Only '
-            '[PathObserver] and [StreamSubscription] are supported.';
-      }
-    }
-    observers = null;
-
-    bool leakedObservers = false;
-    while (objects.isNotEmpty) {
-      leakedObservers = objects.removeLast().hasObservers || leakedObservers;
-    }
-    if (leakedObservers) window.alert('Observers leaked!');
-    objects = null;
-  }
-
-  /// Run the benchmark
-  @override
-  void run() {
-    var mutationsLeft = mutationCount;
-    while (mutationsLeft > 0) {
-      var obj = objects[objectIndex];
-      mutationsLeft -= mutateObject(obj);
-      this.mutations++;
-      this.objectIndex++;
-      if (this.objectIndex == this.objects.length) {
-        this.objectIndex = 0;
-      }
-      obj.deliverChanges();
-      if (obj is ObservableList) obj.deliverListChanges();
-    }
-    AutoObservable.dirtyCheck();
-  }
-}
diff --git a/benchmark/path_benchmark.dart b/benchmark/path_benchmark.dart
deleted file mode 100644
index a612c55..0000000
--- a/benchmark/path_benchmark.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.path_benchmark;
-
-import 'package:observe/observe.dart';
-import 'observation_benchmark_base.dart';
-import 'test_path_observable.dart';
-
-class PathBenchmark extends ObservationBenchmarkBase<TestPathObservable> {
-  final PropertyPath path = new PropertyPath('foo.bar.baz');
-
-  PathBenchmark(int objectCount, int mutationCount, String config)
-      : super('PathBenchmark:$objectCount:$mutationCount:$config', objectCount,
-            mutationCount, config);
-
-  @override
-  int mutateObject(TestPathObservable obj) {
-    switch (config) {
-      case 'leaf':
-        obj.foo.bar.baz += 1;
-        // Make sure [obj.foo.bar] delivers its changes synchronously. The base
-        // class already handles this for [obj].
-        obj.foo.bar.deliverChanges();
-        return 1;
-
-      case 'root':
-        obj.foo = new Foo(obj.foo.bar.baz + 1);
-        return 1;
-
-      default:
-        throw new ArgumentError('Invalid config for PathBenchmark: $config');
-    }
-  }
-
-  @override
-  TestPathObservable newObject() => new TestPathObservable(1);
-
-  @override
-  PathObserver newObserver(TestPathObservable obj) =>
-      new PathObserver(obj, path)..open((_) => mutations--);
-}
diff --git a/benchmark/setup_object_benchmark.dart b/benchmark/setup_object_benchmark.dart
deleted file mode 100644
index 6fda9fc..0000000
--- a/benchmark/setup_object_benchmark.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.setup_object_benchmark;
-
-import 'setup_observation_benchmark_base.dart';
-import 'test_observable.dart';
-
-class SetupObjectBenchmark extends SetupObservationBenchmarkBase {
-  SetupObjectBenchmark(int objectCount, String config)
-      : super('SetupObjectBenchmark:$objectCount:$config', objectCount, config);
-
-  @override
-  TestObservable newObject() => new TestObservable();
-}
diff --git a/benchmark/setup_observable_list_benchmark.dart b/benchmark/setup_observable_list_benchmark.dart
deleted file mode 100644
index b40aced..0000000
--- a/benchmark/setup_observable_list_benchmark.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.setup_observable_list_benchmark;
-
-import 'package:observable/observable.dart';
-import 'setup_observation_benchmark_base.dart';
-
-class SetupObservableListBenchmark extends SetupObservationBenchmarkBase {
-  final int elementCount = 100;
-
-  SetupObservableListBenchmark(int objectCount, String config)
-      : super('SetupObservableListBenchmark:$objectCount:$config', objectCount,
-            config);
-
-  @override
-  ObservableList newObject() {
-    var list = new ObservableList();
-    for (int i = 0; i < elementCount; i++) {
-      list.add(i);
-    }
-    list.deliverChanges();
-    list.deliverListChanges();
-    return list;
-  }
-}
diff --git a/benchmark/setup_observation_benchmark_base.dart b/benchmark/setup_observation_benchmark_base.dart
deleted file mode 100644
index 2b26b72..0000000
--- a/benchmark/setup_observation_benchmark_base.dart
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.setup_observation_benchmark_base;
-
-import 'dart:async';
-import 'dart:html';
-import 'package:observable/observable.dart';
-import 'package:observe/observe.dart';
-import 'package:benchmark_harness/benchmark_harness.dart';
-
-abstract class SetupObservationBenchmarkBase<T extends Observable>
-    extends BenchmarkBase {
-  /// The number of objects to create and observe.
-  final int objectCount;
-
-  /// The current configuration.
-  final String config;
-
-  /// The objects we want to observe.
-  List<T> objects;
-
-  SetupObservationBenchmarkBase(String name, this.objectCount, this.config)
-      : super(name);
-
-  /// Subclasses should use this method to return an observable object to be
-  /// benchmarked.
-  T newObject();
-
-  /// Subclasses should override this to do anything other than a default change
-  /// listener. It must return either a StreamSubscription or a PathObserver.
-  newObserver(T obj) => obj.changes.listen((_) {});
-
-  /// Set up each benchmark by creating all the objects.
-  @override
-  void setup() {
-    objects = [];
-    while (objects.length < objectCount) {
-      objects.add(newObject());
-    }
-  }
-
-  /// Tear down each the benchmark and remove all listeners.
-  @override
-  void teardown() {
-    while (objects.isNotEmpty) {
-      var obj = objects.removeLast();
-      if (obj.hasObservers || (obj is ObservableList && obj.hasListObservers)) {
-        window.alert('Observers leaked!');
-      }
-    }
-    objects = null;
-  }
-
-  /// Run the benchmark by creating a listener on each object.
-  @override
-  void run() {
-    for (var object in objects) {
-      var observer = newObserver(object);
-
-      // **Note:** This is different than the JS implementation. Since run can
-      // be called an arbitrary number of times between [setup] and [teardown],
-      // we clean up all observers as we go. This means we are measuring both
-      // the setup and teardown of observers, versus the setup only in the
-      // JS benchmark. Not cleaning these up ends up giving `oh snap` errors.
-      if (observer is StreamSubscription) {
-        observer.cancel();
-      } else if (observer is PathObserver) {
-        observer.close();
-      } else {
-        throw 'Unknown observer type ${observer.runtimeType}. Only '
-            '[PathObserver] and [StreamSubscription] are supported.';
-      }
-    }
-  }
-}
diff --git a/benchmark/setup_path_benchmark.dart b/benchmark/setup_path_benchmark.dart
deleted file mode 100644
index 72bba82..0000000
--- a/benchmark/setup_path_benchmark.dart
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.setup_path_benchmark;
-
-import 'package:observe/observe.dart';
-import 'setup_observation_benchmark_base.dart';
-import 'test_path_observable.dart';
-
-class SetupPathBenchmark
-    extends SetupObservationBenchmarkBase<TestPathObservable> {
-  final PropertyPath path = new PropertyPath('foo.bar.baz');
-
-  SetupPathBenchmark(int objectCount, String config)
-      : super('SetupPathBenchmark:$objectCount:$config', objectCount, config);
-
-  @override
-  TestPathObservable newObject() => new TestPathObservable(1);
-
-  @override
-  PathObserver newObserver(TestPathObservable obj) =>
-      new PathObserver(obj, path)..open(() {});
-}
diff --git a/benchmark/test_observable.dart b/benchmark/test_observable.dart
deleted file mode 100644
index 15ef2d7..0000000
--- a/benchmark/test_observable.dart
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.test_observable;
-
-import 'package:observe/observe.dart';
-
-class TestObservable extends AutoObservable {
-  @observable
-  int a = 0;
-  @observable
-  int b = 0;
-  @observable
-  int c = 0;
-  @observable
-  int d = 0;
-  @observable
-  int e = 0;
-  @observable
-  int f = 0;
-  @observable
-  int g = 0;
-  @observable
-  int h = 0;
-  @observable
-  int i = 0;
-  @observable
-  int j = 0;
-  @observable
-  int k = 0;
-  @observable
-  int l = 0;
-  @observable
-  int m = 0;
-  @observable
-  int n = 0;
-  @observable
-  int o = 0;
-}
diff --git a/benchmark/test_path_observable.dart b/benchmark/test_path_observable.dart
deleted file mode 100644
index 6675822..0000000
--- a/benchmark/test_path_observable.dart
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-library observe.test.benchmark.test_observable;
-
-import 'package:observe/observe.dart';
-
-class Bar extends AutoObservable {
-  @observable
-  int baz;
-
-  Bar(this.baz);
-}
-
-class Foo extends AutoObservable {
-  @observable
-  Bar bar;
-
-  Foo(int value) : bar = new Bar(value);
-}
-
-class TestPathObservable extends AutoObservable {
-  @observable
-  Foo foo;
-
-  TestPathObservable(int value) : foo = new Foo(value);
-}
diff --git a/lib/html.dart b/lib/html.dart
index 5000a78..e29b0db 100644
--- a/lib/html.dart
+++ b/lib/html.dart
@@ -19,7 +19,7 @@
 /// An observable version of [window.location.hash].
 final ObservableLocationHash windowLocation = new ObservableLocationHash._();
 
-class ObservableLocationHash extends Observable {
+class ObservableLocationHash extends PropertyChangeNotifier {
   Object _currentHash;
 
   ObservableLocationHash._() {
diff --git a/lib/src/auto_observable.dart b/lib/src/auto_observable.dart
index bcb486a..2fae812 100644
--- a/lib/src/auto_observable.dart
+++ b/lib/src/auto_observable.dart
@@ -13,7 +13,7 @@
 import 'dirty_check.dart' show dirtyCheckObservables, registerObservable;
 import 'metadata.dart' show ObservableProperty;
 
-abstract class AutoObservable implements Observable {
+abstract class AutoObservable implements ChangeNotifier {
   /// Performs dirty checking of objects that inherit from [AutoObservable].
   /// This scans all observed objects using mirrors and determines if any fields
   /// have changed. If they have, it delivers the changes for the object.
@@ -147,9 +147,8 @@
   /// - Unlike [Observable] this will not schedule [deliverChanges]; use
   ///   [AutoObservable.dirtyCheck] instead.
   @override
-  void notifyChange(ChangeRecord record) {
-    if (!hasObservers) return;
-
+  void notifyChange([ChangeRecord record]) {
+    if (record == null || !hasObservers) return;
     if (_records == null) _records = [];
     _records.add(record);
   }
diff --git a/lib/src/list_path_observer.dart b/lib/src/list_path_observer.dart
index 09f1b6c..c329103 100644
--- a/lib/src/list_path_observer.dart
+++ b/lib/src/list_path_observer.dart
@@ -14,7 +14,7 @@
 
 /// Observes a path starting from each item in the list.
 @deprecated
-class ListPathObserver<E, P> extends Observable {
+class ListPathObserver<E, P> extends PropertyChangeNotifier {
   final ObservableList<E> list;
   final String _itemPath;
   final List<PathObserver> _observers = <PathObserver>[];
diff --git a/lib/src/metadata.dart b/lib/src/metadata.dart
index 6811332..15e7905 100644
--- a/lib/src/metadata.dart
+++ b/lib/src/metadata.dart
@@ -37,7 +37,7 @@
 /// it available to `PathObserver` at runtime. For example:
 ///
 ///     @reflectable
-///     class Monster extends Observable {
+///     class Monster extends AutoObservable {
 ///       int _health;
 ///       int get health => _health;
 ///       ...
diff --git a/lib/src/observable_box.dart b/lib/src/observable_box.dart
index 51c584a..5749890 100644
--- a/lib/src/observable_box.dart
+++ b/lib/src/observable_box.dart
@@ -13,7 +13,7 @@
 /// value. For other cases, it is better to use [AutoObservableList],
 /// [AutoObservableMap], or a custom [AutoObservable] implementation based on
 /// [AutoObservable]. The property name for changes is "value".
-class ObservableBox<T> extends Observable {
+class ObservableBox<T> extends PropertyChangeNotifier {
   T _value;
 
   ObservableBox([T initialValue]) : _value = initialValue;
diff --git a/pubspec.yaml b/pubspec.yaml
index e74974a..e9a4b36 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: observe
-version: 0.14.0
+version: 0.15.0
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 description: >
   Observable properties and objects for use in template_binding.
@@ -13,7 +13,7 @@
   barback: '>=0.14.2 <0.16.0'
   func: ^0.1.0
   logging: '>=0.9.0 <0.12.0'
-  observable: '^0.14.0'
+  observable: '^0.17.0'
   path: '>=0.9.0 <2.0.0'
   smoke: '>=0.1.0 <0.4.0'
   source_maps: '>=0.9.4 <0.11.0'
diff --git a/test/list_path_observer_test.dart b/test/list_path_observer_test.dart
index e3df022..f0e89b5 100644
--- a/test/list_path_observer_test.dart
+++ b/test/list_path_observer_test.dart
@@ -85,7 +85,7 @@
 _nextMicrotask(_) => new Future(() {});
 
 @reflectable
-class TestModel extends Observable {
+class TestModel extends PropertyChangeNotifier {
   var _a, _b;
   TestModel();
 
diff --git a/test/path_observer_test.dart b/test/path_observer_test.dart
index ba9cc22..bc7731a 100644
--- a/test/path_observer_test.dart
+++ b/test/path_observer_test.dart
@@ -175,7 +175,8 @@
   });
 
   test('get value at path ObservableBox', () {
-    var obj = new ObservableBox(new ObservableBox(new ObservableBox(1)));
+    var obj =
+        new ObservableBox<dynamic>(new ObservableBox(new ObservableBox(1)));
 
     expect(new PathObserver(obj, '').value, obj);
     expect(new PathObserver(obj, 'value').value, obj.value);
@@ -188,7 +189,7 @@
     obj.value.value = new ObservableBox(3);
     expect(new PathObserver(obj, 'value.value.value').value, 3);
 
-    obj.value = new ObservableBox(4);
+    obj.value = new ObservableBox<int>(4);
     expect(() => new PathObserver(obj, 'value.value.value').value,
         _throwsNSM('value'));
     expect(new PathObserver(obj, 'value.value').value, 4);
@@ -753,7 +754,7 @@
 }
 
 @reflectable
-class TestModel extends Observable implements WatcherModel {
+class TestModel extends ChangeNotifier implements WatcherModel {
   var _a, _b, _c;
 
   TestModel([this._a, this._b, this._c]);
@@ -775,6 +776,15 @@
   void set c(newValue) {
     _c = notifyPropertyChange(#c, _c, newValue);
   }
+
+  @override
+  /*=T*/ notifyPropertyChange/*<T>*/(
+      Symbol field, /*=T*/ oldValue, /*=T*/ newValue) {
+    if (hasObservers && oldValue != newValue) {
+      notifyChange(new PropertyChangeRecord(this, field, oldValue, newValue));
+    }
+    return newValue;
+  }
 }
 
 class WatcherModel extends AutoObservable {