messages work

This commit is contained in:
Christien Rioux 2024-02-11 00:29:58 -05:00
parent 43dbf26cc0
commit 634543910b
47 changed files with 2206 additions and 123 deletions

7
packages/async_tools/.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

View file

@ -0,0 +1,15 @@
include: package:lint_hard/all.yaml
analyzer:
errors:
invalid_annotation_target: ignore
exclude:
- '**/*.g.dart'
- '**/*.freezed.dart'
- '**/*.pb.dart'
- '**/*.pbenum.dart'
- '**/*.pbjson.dart'
- '**/*.pbserver.dart'
linter:
rules:
unawaited_futures: true
avoid_positional_boolean_parameters: false

View file

@ -0,0 +1,6 @@
// import 'package:async_tools/async_tools.dart';
// void main() {
// var awesome = Awesome();
// print('awesome: ${awesome.isAwesome}');
// }

View file

@ -0,0 +1,6 @@
/// Async Tools
library;
export 'src/async_tag_lock.dart';
export 'src/async_value.dart';
export 'src/single_async.dart';

View file

@ -0,0 +1,64 @@
import 'package:mutex/mutex.dart';
class _AsyncTagLockEntry {
_AsyncTagLockEntry()
: mutex = Mutex.locked(),
waitingCount = 0;
//
Mutex mutex;
int waitingCount;
}
class AsyncTagLock<T> {
AsyncTagLock()
: _tableLock = Mutex(),
_locks = {};
Future<void> lockTag(T tag) async {
await _tableLock.protect(() async {
final lockEntry = _locks[tag];
if (lockEntry != null) {
lockEntry.waitingCount++;
await lockEntry.mutex.acquire();
lockEntry.waitingCount--;
} else {
_locks[tag] = _AsyncTagLockEntry();
}
});
}
bool isLocked(T tag) => _locks.containsKey(tag);
bool tryLock(T tag) {
final lockEntry = _locks[tag];
if (lockEntry != null) {
return false;
}
_locks[tag] = _AsyncTagLockEntry();
return true;
}
void unlockTag(T tag) {
final lockEntry = _locks[tag]!;
if (lockEntry.waitingCount == 0) {
// If nobody is waiting for the mutex we can just drop it
_locks.remove(tag);
} else {
// Someone's waiting for the tag lock so release the mutex for it
lockEntry.mutex.release();
}
}
Future<R> protect<R>(T tag, {required Future<R> Function() closure}) async {
await lockTag(tag);
try {
return await closure();
} finally {
unlockTag(tag);
}
}
//
final Mutex _tableLock;
final Map<T, _AsyncTagLockEntry> _locks;
}

View file

@ -0,0 +1,189 @@
// ignore_for_file: avoid_catches_without_on_clauses
import 'package:freezed_annotation/freezed_annotation.dart';
part 'async_value.freezed.dart';
/// An utility for safely manipulating asynchronous data.
///
/// By using [AsyncValue], you are guaranteed that you cannot forget to
/// handle the loading/error state of an asynchronous operation.
///
/// It also expose some utilities to nicely convert an [AsyncValue] to
/// a different object.
/// For example, a Flutter Widget may use [when] to convert an [AsyncValue]
/// into either a progress indicator, an error screen, or to show the data:
///
/// ```dart
/// /// A provider that asynchronously expose the current user
/// final userProvider = StreamProvider<User>((_) async* {
/// // fetch the user
/// });
///
/// class Example extends ConsumerWidget {
/// @override
/// Widget build(BuildContext context, ScopedReader watch) {
/// final AsyncValue<User> user = watch(userProvider);
///
/// return user.when(
/// loading: () => CircularProgressIndicator(),
/// error: (error, stack) => Text('Oops, something unexpected happened'),
/// data: (value) => Text('Hello ${user.name}'),
/// );
/// }
/// }
/// ```
///
/// If a consumer of an [AsyncValue] does not care about the loading/error
/// state, consider using [data] to read the state:
///
/// ```dart
/// Widget build(BuildContext context, ScopedReader watch) {
/// // reads the data state directly will be null during loading/error states
/// final User user = watch(userProvider).data?.value;
///
/// return Text('Hello ${user?.name}');
/// }
/// ```
///
/// See also:
///
/// - [AsyncValue.guard], to simplify transforming a [Future] into an
/// [AsyncValue].
/// - The package Freezed (https://github.com/rrousselgit/freezed), which have
/// generated this [AsyncValue] class and explains how [map]/[when] works.
@freezed
@sealed
abstract class AsyncValue<T> with _$AsyncValue<T> {
const AsyncValue._();
/// Creates an [AsyncValue] with a data.
///
/// The data can be `null`.
const factory AsyncValue.data(T value) = AsyncData<T>;
/// Creates an [AsyncValue] in loading state.
///
/// Prefer always using this constructor with the `const` keyword.
const factory AsyncValue.loading() = AsyncLoading<T>;
/// Creates an [AsyncValue] in error state.
///
/// The parameter [error] cannot be `null`.
factory AsyncValue.error(Object error, [StackTrace? stackTrace]) =
AsyncError<T>;
/// Transforms a [Future] that may fail into something that is safe to read.
///
/// This is useful to avoid having to do a tedious `try/catch`. Instead of:
///
/// ```dart
/// class MyNotifier extends StateNotifier<AsyncValue<MyData> {
/// MyNotifier(): super(const AsyncValue.loading()) {
/// _fetchData();
/// }
///
/// Future<void> _fetchData() async {
/// state = const AsyncValue.loading();
/// try {
/// final response = await dio.get('my_api/data');
/// final data = MyData.fromJson(response);
/// state = AsyncValue.data(data);
/// } catch (err, stack) {
/// state = AsyncValue.error(err, stack);
/// }
/// }
/// }
/// ```
///
/// which is redundant as the application grows and we need more and more of
/// this pattern we can use [guard] to simplify it:
///
///
/// ```dart
/// class MyNotifier extends StateNotifier<AsyncValue<MyData>> {
/// MyNotifier(): super(const AsyncValue.loading()) {
/// _fetchData();
/// }
///
/// Future<void> _fetchData() async {
/// state = const AsyncValue.loading();
/// // does the try/catch for us like previously
/// state = await AsyncValue.guard(() async {
/// final response = await dio.get('my_api/data');
/// return Data.fromJson(response);
/// });
/// }
/// }
/// ```
static Future<AsyncValue<T>> guard<T>(Future<T> Function() future) async {
try {
return AsyncValue.data(await future());
} catch (err, stack) {
return AsyncValue.error(err, stack);
}
}
/// The current data, or null if in loading/error.
///
/// This is safe to use, as Dart (will) have non-nullable types.
/// As such reading [data] still forces to handle the loading/error cases
/// by having to check `data != null`.
///
/// ## Why does [AsyncValue<T>.data] return [AsyncData<T>] instead of [T]?
///
/// The motivation behind this decision is to allow differentiating between:
///
/// - There is a data, and it is `null`.
/// ```dart
/// // There is a data, and it is "null"
/// AsyncValue<Configuration> configs = AsyncValue.data(null);
///
/// print(configs.data); // AsyncValue(value: null)
/// print(configs.data.value); // null
/// ```
///
/// - There is no data. [AsyncValue] is currently in loading/error state.
/// ```dart
/// // No data, currently loading
/// AsyncValue<Configuration> configs = AsyncValue.loading();
///
/// print(configs.data); // null, currently loading
/// print(configs.data.value); // throws null exception
/// ```
AsyncData<T>? get data => map(
data: (data) => data,
loading: (_) => null,
error: (_) => null,
);
/// Shorthand for [when] to handle only the `data` case.
AsyncValue<R> whenData<R>(R Function(T value) cb) => when(
data: (value) {
try {
return AsyncValue.data(cb(value));
} catch (err, stack) {
return AsyncValue.error(err, stack);
}
},
loading: () => const AsyncValue.loading(),
error: AsyncValue.error,
);
/// Check two AsyncData instances for equality
bool equalsData(AsyncValue<T> other,
{required bool Function(T a, T b) equals}) =>
other.when(
data: (nd) => when(
data: (d) => equals(d, nd),
loading: () => true,
error: (_e, _st) => true),
loading: () => when(
data: (_) => true,
loading: () => false,
error: (_e, _st) => true),
error: (ne, nst) => when(
data: (_) => true,
loading: () => true,
error: (e, st) => e != ne || st != nst));
}

View file

@ -0,0 +1,480 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'async_value.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
mixin _$AsyncValue<T> {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(T value) data,
required TResult Function() loading,
required TResult Function(Object error, StackTrace? stackTrace) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(T value)? data,
TResult? Function()? loading,
TResult? Function(Object error, StackTrace? stackTrace)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(T value)? data,
TResult Function()? loading,
TResult Function(Object error, StackTrace? stackTrace)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(AsyncData<T> value) data,
required TResult Function(AsyncLoading<T> value) loading,
required TResult Function(AsyncError<T> value) error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(AsyncData<T> value)? data,
TResult? Function(AsyncLoading<T> value)? loading,
TResult? Function(AsyncError<T> value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(AsyncData<T> value)? data,
TResult Function(AsyncLoading<T> value)? loading,
TResult Function(AsyncError<T> value)? error,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AsyncValueCopyWith<T, $Res> {
factory $AsyncValueCopyWith(
AsyncValue<T> value, $Res Function(AsyncValue<T>) then) =
_$AsyncValueCopyWithImpl<T, $Res, AsyncValue<T>>;
}
/// @nodoc
class _$AsyncValueCopyWithImpl<T, $Res, $Val extends AsyncValue<T>>
implements $AsyncValueCopyWith<T, $Res> {
_$AsyncValueCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
abstract class _$$AsyncDataImplCopyWith<T, $Res> {
factory _$$AsyncDataImplCopyWith(
_$AsyncDataImpl<T> value, $Res Function(_$AsyncDataImpl<T>) then) =
__$$AsyncDataImplCopyWithImpl<T, $Res>;
@useResult
$Res call({T value});
}
/// @nodoc
class __$$AsyncDataImplCopyWithImpl<T, $Res>
extends _$AsyncValueCopyWithImpl<T, $Res, _$AsyncDataImpl<T>>
implements _$$AsyncDataImplCopyWith<T, $Res> {
__$$AsyncDataImplCopyWithImpl(
_$AsyncDataImpl<T> _value, $Res Function(_$AsyncDataImpl<T>) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? value = freezed,
}) {
return _then(_$AsyncDataImpl<T>(
freezed == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as T,
));
}
}
/// @nodoc
class _$AsyncDataImpl<T> extends AsyncData<T> {
const _$AsyncDataImpl(this.value) : super._();
@override
final T value;
@override
String toString() {
return 'AsyncValue<$T>.data(value: $value)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AsyncDataImpl<T> &&
const DeepCollectionEquality().equals(other.value, value));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(value));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AsyncDataImplCopyWith<T, _$AsyncDataImpl<T>> get copyWith =>
__$$AsyncDataImplCopyWithImpl<T, _$AsyncDataImpl<T>>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(T value) data,
required TResult Function() loading,
required TResult Function(Object error, StackTrace? stackTrace) error,
}) {
return data(value);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(T value)? data,
TResult? Function()? loading,
TResult? Function(Object error, StackTrace? stackTrace)? error,
}) {
return data?.call(value);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(T value)? data,
TResult Function()? loading,
TResult Function(Object error, StackTrace? stackTrace)? error,
required TResult orElse(),
}) {
if (data != null) {
return data(value);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(AsyncData<T> value) data,
required TResult Function(AsyncLoading<T> value) loading,
required TResult Function(AsyncError<T> value) error,
}) {
return data(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(AsyncData<T> value)? data,
TResult? Function(AsyncLoading<T> value)? loading,
TResult? Function(AsyncError<T> value)? error,
}) {
return data?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(AsyncData<T> value)? data,
TResult Function(AsyncLoading<T> value)? loading,
TResult Function(AsyncError<T> value)? error,
required TResult orElse(),
}) {
if (data != null) {
return data(this);
}
return orElse();
}
}
abstract class AsyncData<T> extends AsyncValue<T> {
const factory AsyncData(final T value) = _$AsyncDataImpl<T>;
const AsyncData._() : super._();
T get value;
@JsonKey(ignore: true)
_$$AsyncDataImplCopyWith<T, _$AsyncDataImpl<T>> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$AsyncLoadingImplCopyWith<T, $Res> {
factory _$$AsyncLoadingImplCopyWith(_$AsyncLoadingImpl<T> value,
$Res Function(_$AsyncLoadingImpl<T>) then) =
__$$AsyncLoadingImplCopyWithImpl<T, $Res>;
}
/// @nodoc
class __$$AsyncLoadingImplCopyWithImpl<T, $Res>
extends _$AsyncValueCopyWithImpl<T, $Res, _$AsyncLoadingImpl<T>>
implements _$$AsyncLoadingImplCopyWith<T, $Res> {
__$$AsyncLoadingImplCopyWithImpl(
_$AsyncLoadingImpl<T> _value, $Res Function(_$AsyncLoadingImpl<T>) _then)
: super(_value, _then);
}
/// @nodoc
class _$AsyncLoadingImpl<T> extends AsyncLoading<T> {
const _$AsyncLoadingImpl() : super._();
@override
String toString() {
return 'AsyncValue<$T>.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$AsyncLoadingImpl<T>);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(T value) data,
required TResult Function() loading,
required TResult Function(Object error, StackTrace? stackTrace) error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(T value)? data,
TResult? Function()? loading,
TResult? Function(Object error, StackTrace? stackTrace)? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(T value)? data,
TResult Function()? loading,
TResult Function(Object error, StackTrace? stackTrace)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(AsyncData<T> value) data,
required TResult Function(AsyncLoading<T> value) loading,
required TResult Function(AsyncError<T> value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(AsyncData<T> value)? data,
TResult? Function(AsyncLoading<T> value)? loading,
TResult? Function(AsyncError<T> value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(AsyncData<T> value)? data,
TResult Function(AsyncLoading<T> value)? loading,
TResult Function(AsyncError<T> value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class AsyncLoading<T> extends AsyncValue<T> {
const factory AsyncLoading() = _$AsyncLoadingImpl<T>;
const AsyncLoading._() : super._();
}
/// @nodoc
abstract class _$$AsyncErrorImplCopyWith<T, $Res> {
factory _$$AsyncErrorImplCopyWith(
_$AsyncErrorImpl<T> value, $Res Function(_$AsyncErrorImpl<T>) then) =
__$$AsyncErrorImplCopyWithImpl<T, $Res>;
@useResult
$Res call({Object error, StackTrace? stackTrace});
}
/// @nodoc
class __$$AsyncErrorImplCopyWithImpl<T, $Res>
extends _$AsyncValueCopyWithImpl<T, $Res, _$AsyncErrorImpl<T>>
implements _$$AsyncErrorImplCopyWith<T, $Res> {
__$$AsyncErrorImplCopyWithImpl(
_$AsyncErrorImpl<T> _value, $Res Function(_$AsyncErrorImpl<T>) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? error = null,
Object? stackTrace = freezed,
}) {
return _then(_$AsyncErrorImpl<T>(
null == error ? _value.error : error,
freezed == stackTrace
? _value.stackTrace
: stackTrace // ignore: cast_nullable_to_non_nullable
as StackTrace?,
));
}
}
/// @nodoc
class _$AsyncErrorImpl<T> extends AsyncError<T> {
_$AsyncErrorImpl(this.error, [this.stackTrace]) : super._();
@override
final Object error;
@override
final StackTrace? stackTrace;
@override
String toString() {
return 'AsyncValue<$T>.error(error: $error, stackTrace: $stackTrace)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AsyncErrorImpl<T> &&
const DeepCollectionEquality().equals(other.error, error) &&
(identical(other.stackTrace, stackTrace) ||
other.stackTrace == stackTrace));
}
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(error), stackTrace);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AsyncErrorImplCopyWith<T, _$AsyncErrorImpl<T>> get copyWith =>
__$$AsyncErrorImplCopyWithImpl<T, _$AsyncErrorImpl<T>>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(T value) data,
required TResult Function() loading,
required TResult Function(Object error, StackTrace? stackTrace) error,
}) {
return error(this.error, stackTrace);
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(T value)? data,
TResult? Function()? loading,
TResult? Function(Object error, StackTrace? stackTrace)? error,
}) {
return error?.call(this.error, stackTrace);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(T value)? data,
TResult Function()? loading,
TResult Function(Object error, StackTrace? stackTrace)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this.error, stackTrace);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(AsyncData<T> value) data,
required TResult Function(AsyncLoading<T> value) loading,
required TResult Function(AsyncError<T> value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(AsyncData<T> value)? data,
TResult? Function(AsyncLoading<T> value)? loading,
TResult? Function(AsyncError<T> value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(AsyncData<T> value)? data,
TResult Function(AsyncLoading<T> value)? loading,
TResult Function(AsyncError<T> value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class AsyncError<T> extends AsyncValue<T> {
factory AsyncError(final Object error, [final StackTrace? stackTrace]) =
_$AsyncErrorImpl<T>;
AsyncError._() : super._();
Object get error;
StackTrace? get stackTrace;
@JsonKey(ignore: true)
_$$AsyncErrorImplCopyWith<T, _$AsyncErrorImpl<T>> get copyWith =>
throw _privateConstructorUsedError;
}

View file

@ -0,0 +1,19 @@
import 'dart:async';
import 'async_tag_lock.dart';
AsyncTagLock<Object> _keys = AsyncTagLock();
void singleFuture(Object tag, Future<void> Function() closure,
{void Function()? onBusy}) {
if (!_keys.tryLock(tag)) {
if (onBusy != null) {
onBusy();
}
return;
}
unawaited(() async {
await closure();
_keys.unlockTag(tag);
}());
}

View file

@ -0,0 +1,18 @@
name: async_tools
description: Useful data structures and tools for async/Future code
version: 1.0.0
publish_to: none
environment:
sdk: ^3.2.6
# Add regular dependencies here.
dependencies:
freezed_annotation: ^2.2.0
mutex:
path: ../mutex
dev_dependencies:
freezed: ^2.3.5
lint_hard: ^4.0.0
test: ^1.24.0

View file

@ -0,0 +1,16 @@
// import 'package:async_tools/async_tools.dart';
// import 'package:test/test.dart';
// void main() {
// group('A group of tests', () {
// final awesome = Awesome();
// setUp(() {
// // Additional setup goes here.
// });
// test('First Test', () {
// expect(awesome.isAwesome, isTrue);
// });
// });
// }