simple_observable 0.2.0

  • README.md
  • CHANGELOG.md
  • Example
  • Installing
  • Versions
  • new68

simple_observable

A minimal observable and debouncer that works with callbacks, Futures, Streams, or any combination of the three.

Usage

import 'package:simple_observable/simple_observable.dart';

void main() {
  // Without a callback
  final observable = SimpleObservable<String>();
  // With a callback
  final observable2 = SimpleObservable<String>(print);

  // Using a Future
  observable.nextValue.then((value) => print('Future: $value'));

  // Using a Stream
  observable.values.listen((value) => print('Stream: $value'));

  // Changing its value
  observable.value = 'foo';
  observable.value = 'bar';


  // Debouncing
  final debouncer = Debouncer<String>(Duration(milliseconds: 250));

  // Everything works the same as above.

  // Using a Future
  debouncer.nextValue.then((value) => print('Future: $value'));

  // Using a Stream
  debouncer.values.listen((value) => print('Stream: $value'));

  // Changing its value
  debouncer.value = 'foo';
  debouncer.value = 'bar';
}

Try the example on Dartpad.

Source code

import 'dart:async';
import 'package:meta/meta.dart';

/// A simple class that allows being notified of changes [value] via the
/// [onValue] callback, the [nextValue] Future, or the [values] Stream.
///
/// Any combination of [onValue], [nextValue], and [values] can be used to
/// listen for changes.
///
/// Once canceled, it cannot be reused. Instead, create another instance.
class SimpleObservable<T> {
  SimpleObservable([this.onValue]);

  final void Function(T value) onValue;

  var _completer = Completer<T>();

  bool _canceled = false;
  bool get canceled => _canceled;

  T _value;

  /// The current value of this observable.
  T get value => _value;
  set value(T val) {
    if (!canceled) {
      _value = val;
      // Delaying notify() allows the Future and Stream to update correctly.
      Future.delayed(Duration(microseconds: 1), () => _notify(val));
    }
  }

  /// Alias for [value] setter. Good for passing to a Future or Stream.
  void setValue(T val) => value = val;

  void _notify(T val) {
    if (onValue != null) onValue(val);
    // Completing with a microtask allows a new completer to be constructed
    // before listeners of [nextValue] are called, allowing them to listen to
    // nextValue again if desired.
    _completer.complete(Future.microtask(() => val));
    _completer = Completer<T>();
  }

  Future<T> get nextValue => _completer.future;
  Stream<T> get values async* {
    while (!canceled) yield await nextValue;
  }

  /// Permanently disables this observable. Further changes to [value] will be
  /// ignored, the outputs [onValue], [nextValue], and [values] will not be
  /// called again.
  @mustCallSuper
  void cancel() => _canceled = true;
}

/// Debounces value changes by updating [onValue], [nextValue], and [values]
/// only after [duration] has elapsed without additional changes.
class Debouncer<T> extends SimpleObservable<T> {
  Debouncer(this.duration, [void Function(T value) onValue]) : super(onValue);
  final Duration duration;
  Timer _timer;

  /// The most recent value, without waiting for the debounce timer to expire.
  @override
  T get value => super.value;

  set value(T val) {
    if (!canceled) {
      _value = val;
      _timer?.cancel();
      _timer = Timer(duration, () {
        if (!canceled) {
          _notify(value);
        }
      });
    }
  }

  @override
  @mustCallSuper
  void cancel() {
    super.cancel();
    _timer?.cancel();
  }
}

[0.2.0] - November 5, 2018

  • breaking: once canceled the observable/debouncer is no longer usable.
  • Moved cancel() to base class.
  • Return type of callback function is now void instead of dynamic.
  • Add more to ./example/main.dart.

[0.1.1] - October 22, 2018

  • Add setValue().
  • Put source code in readme.

[0.1.0] - October 22, 2018

  • First release.

example/main.dart

import 'dart:async';
import 'dart:math';
import 'package:simple_observable/simple_observable.dart';

void main() {
  /// Use of the SimpleObservable base class.
  final observable = SimpleObservable<String>(printCallback);
  observable.values.listen(printStream);

  /// Recursively listens to [nextValue] and prints changes.
  printFuture(observable);

  observable.value = 'a';
  observable.value = 'b';
  observable.value = 'c';

  /// Use of the Debouncer class.

  final debouncer =
      Debouncer<String>(Duration(milliseconds: 250), printCallback);
  debouncer.values.listen(printStream);
  printFuture(debouncer);

  /// Change the value multiple times before the debounce timer runs out.
  debouncer.value = '';
  final timer = Timer.periodic(Duration(milliseconds: 200), (_) {
    debouncer.value += 'x';
  });

  Future.delayed(Duration(milliseconds: 1000)).then((_) async {
    /// Cancels the above timer.
    timer.cancel();

    /// Make another change after the debouncer emits its value.
    await Future.delayed(Duration(milliseconds: 500));
    debouncer.value = 'hi';
  });

  // Multiple listeners are supported.
  debouncer.values.listen((value) => print('Stream2: $value'));

  // Transforming the stream.
  debouncer.values
      .transform<List<String>>(StreamTransformer.fromHandlers(
          handleData: (value, sink) => sink.add(['Transformed', value])))
      .listen(print);

  funWithRandom();
}

void printCallback(String value) => print('Callback: $value');
void printStream(String value) => print('Stream: $value');
void printFuture(SimpleObservable obs) => obs.nextValue.then((value) {
      print('Future: $value');
      printFuture(obs);
    });

void funWithRandom() async {
  final d = Debouncer<int>(Duration(milliseconds: 250));
  final rng = Random();
  int i = 0;
  int count = 0;
  d.values.listen((value) {
    double fraction = ((++count / value) * 1000).round() / 1000;
    print('count: $count, current: $value, pct: $fraction');
    if ((count >= 25 && (fraction > 0.27 || fraction < 0.23)) || count >= 100)
      d.cancel();
  });
  while (!d.canceled) {
    await Future.delayed(Duration(milliseconds: 100 + rng.nextInt(200)));
    d.value = ++i;
  }
  print('Done.');
}

// Output:
//
// Callback: a
// Future: a
// Stream: a
// Callback: b
// Future: b
// Stream: b
// Callback: c
// Future: c
// Stream: c
// Callback: xxxxx
// Future: xxxxx
// Stream: xxxxx
// Stream2: xxxxx
// [Transformed, xxxxx]
// Callback: hi
// Future: hi
// Stream: hi
// Stream2: hi
// [Transformed, hi]
//
// Random output...

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  simple_observable: ^0.2.0

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter packages get

Alternatively, your editor might support pub get or flutter packages get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:simple_observable/simple_observable.dart';
  
Version Uploaded Documentation Archive
0.2.0 Nov 5, 2018 Go to the documentation of simple_observable 0.2.0 Download simple_observable 0.2.0 archive
0.1.1 Oct 23, 2018 Go to the documentation of simple_observable 0.1.1 Download simple_observable 0.1.1 archive
0.1.0 Oct 22, 2018 Go to the documentation of simple_observable 0.1.0 Download simple_observable 0.1.0 archive
Popularity:
Describes how popular the package is relative to other packages. [more]
36
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
68
Learn more about scoring.

We analyzed this package on Nov 14, 2018, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.0.0
  • pana: 0.12.6

Platforms

Detected platforms: Flutter, web, other

No platform restriction found in primary library package:simple_observable/simple_observable.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.0.0 <3.0.0
meta ^1.0.0 1.1.6
Dev dependencies
test ^1.0.0