Skip to content

Commit

Permalink
added set
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobaraujo7 committed Jun 16, 2024
1 parent 06830b8 commit f29a602
Show file tree
Hide file tree
Showing 11 changed files with 283 additions and 157 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# .github/workflows/publish.yml
name: Publish to pub.dev

on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'

jobs:
publish:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: stable

- name: Install dependencies
run: flutter pub get

- name: Publish
run: flutter pub publish --force
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ app.*.symbols
# Obfuscation related
app.*.map.json

coverage/

4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 1.1.0

- ADD: Set callback for ValueSelectable classes;

# 1.0.0+2

- First Version
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,35 @@ void main() async {
```

## Advance Usage

As classes `ValueSelectable` trabalham devirando valores de outros `ValueListenable`, mas isso
também pode ser feito por meio de ações utilizando o setter do `value` de um `ValueSelectable`.
Note que ao alterar esse `value` nada acontecerá se a função `set` não for implementada no construtor de uma das classes do `ValueSelectable`.

Com isso em mente poderemos fazer derivações utilizando ações de uma forma parecida com um `Reducer`:

```dart
final counterState = ValueNotifier<int>(0);
final selectorState = ValueSelector<int>(
(get) => get(counterState) + 1,
(action) {
if (action == 'INCREMENT') counterState.value++;
if (action == 'DECREMENT') counterState.value--;
},
);
// directly change
counterState.value = 1;
// indirectly change
selectorState.value = 'INCREMENT';
selectorState.value = 'DECREMENT';
```


## Contributing

Contributions are welcome! Please open an issue or submit a pull request.
86 changes: 86 additions & 0 deletions lib/src/async_value_selector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
part of '../value_selectable.dart';

/// A selector that computes an asynchronous value based on a given scope.
class AsyncValueSelector<T> extends ValueSelectable<T> {
final FutureOr<T> Function(GetValue get) scope;
final FutureOr<void> Function(dynamic action)? _set;

final Queue<FutureOr Function()> _requestQueue = Queue();
bool _isProcessing = false;
bool _isInitialized = false;

late final _get = GetValue._(notifyListeners);
final _readyCompleter = Completer<bool>();

/// Future that completes when the selector is ready.
Future<bool> get isReady {
_initialize();
return _readyCompleter.future;
}

late T _value;

void _initialize() {
if (!_isInitialized) {
_isInitialized = true;
_requestQueue.add(_initializeSelector);
_processQueue();
}
}

@override
T get value {
_initialize();
return _value;
}

set value(dynamic newValue) {
_initialize();
_requestQueue.add(() => _set?.call(newValue));
_processQueue();
}

/// Constructs an AsyncValueSelector with an initial value and a scope function.
AsyncValueSelector(this._value, this.scope, [this._set]);

/// Processes the request queue, ensuring only one request is processed at a time.
Future<void> _processQueue() async {
if (_isProcessing || _requestQueue.isEmpty) return;

_isProcessing = true;
try {
while (_requestQueue.isNotEmpty) {
final request = _requestQueue.removeFirst();
await request();
}
} catch (e) {
rethrow;
} finally {
_isProcessing = false;
}
}

@override
void notifyListeners() {
_requestQueue.add(() async {
_value = await scope(_get);
if (_get._isDisposed) return;
super.notifyListeners();
});
_processQueue();
}

/// Initializes the selector by computing the initial value.
Future<void> _initializeSelector() async {
_value = await scope(_get);
super.notifyListeners();
_get._tracking = false;
_readyCompleter.complete(true);
}

@override
void dispose() {
_get.dispose();
super.dispose();
}
}
33 changes: 33 additions & 0 deletions lib/src/value_selector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
part of '../value_selectable.dart';

/// A selector that computes a synchronous value based on a given scope.
class ValueSelector<T> extends ValueSelectable<T> {
final T Function(GetValue get) scope;
final FutureOr<void> Function(dynamic action)? _set;
late T _value;

@override
T get value => _value;

set value(dynamic newValue) => _set?.call(newValue);

late final _get = GetValue._(notifyListeners);

/// Constructs a ValueSelector with an initial value and a scope function.
ValueSelector(this.scope, [this._set]) {
_value = scope(_get);
_get._tracking = false;
}

@override
void notifyListeners() {
_value = scope(_get);
super.notifyListeners();
}

@override
void dispose() {
_get.dispose();
super.dispose();
}
}
107 changes: 8 additions & 99 deletions lib/value_selectable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,115 +5,23 @@ import 'dart:collection';

import 'package:flutter/foundation.dart';

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

/// Abstract class for a selectable value, extending ChangeNotifier.
abstract class ValueSelectable<T> extends ChangeNotifier
implements ValueListenable<T> {}

/// A selector that computes a synchronous value based on a given scope.
class ValueSelector<T> extends ValueSelectable<T> {
final T Function(ValueRegistrator get) scope;
late T _value;

@override
T get value => _value;

late final _get = ValueRegistrator._(notifyListeners);

/// Constructs a ValueSelector with an initial value and a scope function.
ValueSelector(this.scope) {
_value = scope(_get);
_get._tracking = false;
}

@override
void notifyListeners() {
_value = scope(_get);
super.notifyListeners();
}

@override
void dispose() {
_get.dispose();
super.dispose();
}
}

/// A selector that computes an asynchronous value based on a given scope.
class AsyncValueSelector<T> extends ValueSelectable<T> {
final FutureOr<T> Function(ValueRegistrator get) scope;
final Queue<FutureOr Function()> _requestQueue = Queue();
bool _isProcessing = false;

late final _get = ValueRegistrator._(notifyListeners);
final _readyCompleter = Completer<bool>();

/// Future that completes when the selector is ready.
Future<bool> get isReady => _readyCompleter.future;

late T _value;

@override
T get value => _value;

/// Constructs an AsyncValueSelector with an initial value and a scope function.
AsyncValueSelector(this._value, this.scope) {
_requestQueue.add(_initializeSelector);
_processQueue();
}

/// Processes the request queue, ensuring only one request is processed at a time.
Future<void> _processQueue() async {
if (_isProcessing || _requestQueue.isEmpty) return;

_isProcessing = true;
try {
while (_requestQueue.isNotEmpty) {
final request = _requestQueue.removeFirst();
await request();
}
} catch (e) {
rethrow;
} finally {
_isProcessing = false;
}
}

@override
void notifyListeners() {
_requestQueue.add(() async {
_value = await scope(_get);
super.notifyListeners();
});
_processQueue();
}

/// Initializes the selector by computing the initial value.
Future<void> _initializeSelector() async {
try {
_value = await scope(_get);
notifyListeners();
_get._tracking = false;
_readyCompleter.complete(true);
} catch (e) {
_readyCompleter.completeError(e);
rethrow;
}
}

@override
void dispose() {
_get.dispose();
super.dispose();
}
}
typedef SetValue<T> = void Function(T value);

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

ValueRegistrator._(this._selectorNotifyListeners);
GetValue._(this._selectorNotifyListeners);

/// Registers a notifier and returns its value.
R call<R>(ValueListenable<R> notifier) {
Expand All @@ -126,6 +34,7 @@ final class ValueRegistrator {

/// Disposes of all registered listeners.
void dispose() {
_isDisposed = true;
for (final disposer in _disposers) {
disposer();
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: value_selectable
description: "A Flutter package that provides computed values for `ValueNotifier`, inspired by the Selectors from Recoil."
repository: https://github.com/Flutterando/value_selectable

version: 1.0.0+2
version: 1.1.0

environment:
sdk: '>=3.0.0 <4.0.0'
Expand Down
Loading

0 comments on commit f29a602

Please sign in to comment.