Skip to content

Commit

Permalink
Merge pull request #6 from jinyus/dev
Browse files Browse the repository at this point in the history
Feat: Add Beacon.asFuture
  • Loading branch information
jinyus authored Dec 8, 2023
2 parents 7a930b7 + dbd4556 commit 3c24948
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 10 deletions.
1 change: 1 addition & 0 deletions state_beacon/lib/src/base_beacon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ part 'beacons/list.dart';
part 'beacons/derived.dart';
part 'beacons/derived_future.dart';
part 'beacons/value_notifier.dart';
part 'beacons/awaited.dart';

abstract class BaseBeacon<T> implements ValueListenable<T> {
BaseBeacon([T? initialValue]) {
Expand Down
34 changes: 34 additions & 0 deletions state_beacon/lib/src/beacons/awaited.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
part of '../base_beacon.dart';

class Awaited<T> extends ReadableBeacon<Completer<T>> {
late final FutureBeacon<T> _futureBeacon;

Future<T> get future => value.future;

Awaited(this._futureBeacon, {String? debugName}) : super(Completer<T>()) {
_futureBeacon.subscribe((v) {
if (peek().isCompleted) {
_setValue(Completer<T>());
}
if (v case AsyncData<T>(:final value)) {
super._value.complete(value);
} else if (v case AsyncError(:final error, :final stackTrace)) {
super._value.completeError(error, stackTrace);
}
}, startNow: true);
}

static Awaited<T>? find<T>(FutureBeacon<T> beacon) {
final existing = _awaitedBeacons[beacon];
if (existing != null) {
return existing as Awaited<T>;
}
return null;
}

static void put<T>(FutureBeacon<T> beacon, Awaited<T> awaited) {
_awaitedBeacons[beacon] = awaited;
}
}

final _awaitedBeacons = <FutureBeacon, Awaited>{};
15 changes: 9 additions & 6 deletions state_beacon/lib/src/beacons/future.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ class FutureBeacon<T> extends ReadableBeacon<AsyncValue<T>> {

void start() {}

int startLoading() {
/// Internal method to start loading
@protected
int $startLoading() {
_setValue(AsyncLoading());
return ++_executionID;
}

void setAsyncValue(int exeID, AsyncValue<T> value) {
/// Internal method to set the value
@protected
void $setAsyncValue(int exeID, AsyncValue<T> value) {
// If the execution ID is not the same as the current one,
// then this is an old execution and we should ignore it
if (cancelRunning && exeID != _executionID) return;
Expand All @@ -39,7 +43,6 @@ class FutureBeacon<T> extends ReadableBeacon<AsyncValue<T>> {

_lastData = value.unwrapValue();
}

_setValue(value, force: true);
}

Expand Down Expand Up @@ -82,13 +85,13 @@ class DefaultFutureBeacon<T> extends FutureBeacon<T> {
}

Future<void> _init() async {
final currentExeID = startLoading();
final currentExeID = $startLoading();

try {
final result = await _operation();
return setAsyncValue(currentExeID, AsyncData(result));
return $setAsyncValue(currentExeID, AsyncData(result));
} catch (e, s) {
return setAsyncValue(currentExeID, AsyncError(e, s));
return $setAsyncValue(currentExeID, AsyncError(e, s));
}
}
}
35 changes: 31 additions & 4 deletions state_beacon/lib/src/state_beacon.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member

import 'async_value.dart';
import 'base_beacon.dart';
import 'common.dart';
Expand Down Expand Up @@ -371,13 +373,12 @@ abstract class Beacon {
if (beacon.status.value == DerivedFutureStatus.idle) return;

// start loading and get the execution ID
final exeID = beacon.startLoading();

final exeID = beacon.$startLoading();
try {
final result = await compute();
beacon.setAsyncValue(exeID, AsyncData(result));
beacon.$setAsyncValue(exeID, AsyncData(result));
} catch (e, s) {
beacon.setAsyncValue(exeID, AsyncError(e, s));
beacon.$setAsyncValue(exeID, AsyncError(e, s));
}
});

Expand Down Expand Up @@ -454,4 +455,30 @@ abstract class Beacon {
static void doBatchUpdate(VoidCallback callback) {
batch(callback);
}

/// Exposes a [FutureBeacon] as a [Future] that can be awaited inside another [FutureBeacon].
/// var count = Beacon.writable(0);
/// var firstName = Beacon.derivedFuture(() async => 'Sally ${count.value}');
///
/// var lastName = Beacon.derivedFuture(() async => 'Smith ${count.value}');
///
/// var fullName = Beacon.derivedFuture(() async {
///
/// // no need for a manual switch expression
/// final fname = await Beacon.asFuture(firstName);
/// final lname = await Beacon.asFuture(lastName);
///
/// return '$fname $lname';
/// });
static Future<T> asFuture<T>(FutureBeacon<T> beacon) {
final existing = Awaited.find(beacon);
if (existing != null) {
return existing.future;
}

final newAwaited = Awaited(beacon);
Awaited.put(beacon, newAwaited);

return newAwaited.future;
}
}
41 changes: 41 additions & 0 deletions state_beacon/test/async_beacon_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:state_beacon/state_beacon.dart';

void main() {
const k10ms = Duration(milliseconds: 10);

group('FutureBeacon Tests', () {
test('should change to AsyncData on successful future resolution',
() async {
Expand Down Expand Up @@ -100,6 +102,45 @@ void main() {
expect(ran, equals(2));
});

test('should await FutureBeacon exposed a future', () async {
var count = Beacon.writable(0);

var firstName = Beacon.derivedFuture(() async {
final val = count.value;
await Future.delayed(k10ms);
return 'Sally $val';
});

var lastName = Beacon.derivedFuture(() async {
final val = count.value + 1;
await Future.delayed(k10ms);
return 'Smith $val';
});

var fullName = Beacon.derivedFuture(() async {
final fname = await Beacon.asFuture(firstName);
final lname = await Beacon.asFuture(lastName);

final name = '$fname $lname';

return name;
});

expect(fullName.value, isA<AsyncLoading>());

await Future.delayed(k10ms * 2);

expect(fullName.value.unwrapValue(), 'Sally 0 Smith 1');

count.increment();

expect(fullName.value, isA<AsyncLoading>());

await Future.delayed(k10ms * 3);

expect(fullName.value.unwrapValue(), 'Sally 1 Smith 2');
});

test('should not execute until start() is called', () async {
var count = Beacon.writable(0);

Expand Down

0 comments on commit 3c24948

Please sign in to comment.