Skip to content

Commit

Permalink
fix:
Browse files Browse the repository at this point in the history
- Listen other notifiers after the first value;
- Do not emmit repeated values;
  • Loading branch information
davidsdearaujo committed Jun 30, 2024
1 parent 4f5085f commit c27e94b
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 226 deletions.
96 changes: 0 additions & 96 deletions lib/src/async_value_selector.dart

This file was deleted.

55 changes: 37 additions & 18 deletions lib/src/value_selector.dart
Original file line number Diff line number Diff line change
@@ -1,44 +1,63 @@
part of '../value_selectable.dart';
import 'package:flutter/foundation.dart';

/// A selector that computes a synchronous value based on a given scope.
class ValueSelector<T> extends ValueSelectable<T> {
class ValueSelector<T> extends ChangeNotifier implements ValueListenable<T> {
final T Function(GetValue get) scope;
final Function? _set;
late final bool kActionWithArguments;
late T _value;
final _listenables = <Listenable>{};

@override
T get value => _value;

set value(dynamic newValue) {
if (_set != null) {
Function.apply(_set!, kActionWithArguments ? [newValue] : []);
late final _get = GetValue._(onListenableIdentified);

void onListenableIdentified(Listenable listenable) {
if (_listenables.add(listenable)) {
listenable.addListener(notifyListeners);
}
}

late final _get = GetValue._(notifyListeners);

/// Constructs a ValueSelector with an initial value and a scope function.
ValueSelector(this.scope, [this._set]) {
if (_set != null) {
var funcText = _set.runtimeType.toString();
assert(!funcText.contains(','), 'Function must have one argument.');
kActionWithArguments = !funcText.startsWith('() =>');
}

ValueSelector(this.scope) {
_value = scope(_get);
_get._tracking = false;
}

@override
void notifyListeners() {
_value = scope(_get);
_listenables.clear();
final newValue = scope(_get);

if (newValue == _value) return;
_value = newValue;
super.notifyListeners();
}

@override
void dispose() {
_get.dispose();
for (var listenable in _listenables) {
listenable.removeListener(notifyListeners);
}
super.dispose();
}
}

/// Helper class to manage value dependencies and tracking for selectors.
final class GetValue {
final void Function(Listenable listenable) _selectorIdentifyListenable;
GetValue._(this._selectorIdentifyListenable);

bool _isDisposed = false;

/// Registers a notifier and returns its value.
R call<R>(ValueListenable<R> notifier) {
if (_isDisposed) throw Exception('It is disposed');
_selectorIdentifyListenable.call(notifier);
return notifier.value;
}

/// Disposes of all registered listeners.
void dispose() {
_isDisposed = true;
}
}
23 changes: 6 additions & 17 deletions lib/value_selectable.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
library value_selectable;

import 'dart:async';
import 'dart:collection';

import 'package:flutter/foundation.dart';

part 'src/async_value_selector.dart';
part 'src/value_selector.dart';
export 'src/value_selector.dart';

/// Abstract class for a selectable value, extending ChangeNotifier.
abstract class ValueSelectable<T> extends ChangeNotifier
Expand All @@ -16,27 +12,20 @@ typedef SetValue<T> = void Function(T value);

/// Helper class to manage value dependencies and tracking for selectors.
final class GetValue {
final void Function() _selectorNotifyListeners;
final List<void Function()> _disposers = [];
var _tracking = true;
bool _isDisposed = false;
final void Function(Listenable listenable) _selectorIdentifyListenable;
GetValue._(this._selectorIdentifyListenable);

GetValue._(this._selectorNotifyListeners);
bool _isDisposed = false;

/// Registers a notifier and returns its value.
R call<R>(ValueListenable<R> notifier) {
if (_tracking) {
notifier.addListener(_selectorNotifyListeners);
_disposers.add(() => notifier.removeListener(_selectorNotifyListeners));
}
if (_isDisposed) throw Exception('It is disposed');
_selectorIdentifyListenable.call(notifier);
return notifier.value;
}

/// Disposes of all registered listeners.
void dispose() {
_isDisposed = true;
for (final disposer in _disposers) {
disposer();
}
}
}
64 changes: 0 additions & 64 deletions test/src/async_value_selector_test.dart

This file was deleted.

74 changes: 43 additions & 31 deletions test/src/value_selector_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,62 @@ import 'package:value_selectable/value_selectable.dart';
void main() {
test('ValueSelectable', () async {
final counterState = ValueNotifier<int>(0);

final selectorState = ValueSelector<int>(
(get) => get(counterState) + 1,
(String action) {
if (action == 'INCREMENT') counterState.value++;
if (action == 'DECREMENT') counterState.value--;
},
);
final selectorState = ValueSelector<int>((get) => get(counterState) + 1);

selectorState.addListener(expectAsync0(() {
expect(selectorState.value, anyOf([2, 3]));
}, count: 3));
}, count: 2));

// directly change
counterState.value = 1;
counterState.value = 2;

// indirectly change
selectorState.value = 'INCREMENT';
selectorState.value = 'DECREMENT';

await Future.delayed(const Duration(seconds: 1));
// await Future.delayed(const Duration(seconds: 1));
selectorState.dispose();
});

test('ValueSelectable throw assert if reducer has more one arguments', () async {
expect(() {
return ValueSelector<int>(
(get) => 1,
(String action, int id) {},
);
}, throwsAssertionError);
});

test('ValueSelectable action zero arguments', () async {
ValueSelector<int>(
(get) => 1,
() {},
);
ValueSelector<int>((get) => 1);
});

test('ValueSelectable action one arguments', () async {
ValueSelector<int>(
(get) => 1,
(String action) {},
);
final counterState = ValueNotifier<int>(0);
ValueSelector<int>((get) => get(counterState));
});

test('Listen other notifiers after the first value', () {
final nameState = ValueNotifier('Deivão');
final ageState = ValueNotifier(15);

final canAccess = ValueSelector((get) {
if (get(nameState) == 'Deivão') return true;
if (get(ageState) >= 18) return true;
return false;
});

canAccess.addListener(() => print('Can access: ${canAccess.value}'));

// Return false
nameState.value = "Jacob";

// Return true (doesn't work)
ageState.value = 18;
});

test('Do not emmit repeated values', () {
final nameState = ValueNotifier('Jacob');
final ageState = ValueNotifier(17);

final canAccess = ValueSelector((get) {
print('scope');
if (get(nameState) == 'Deivão') return true;
if (get(ageState) >= 18) return true;
return false;
});

canAccess.addListener(() => print('Can access: ${canAccess.value}'));

nameState.value = "Deivão";
ageState.value = 17;
});
}

0 comments on commit c27e94b

Please sign in to comment.