Nikolas Rimikis
2 years ago
committed by
GitHub
25 changed files with 284 additions and 117 deletions
@ -1,5 +1,6 @@
|
||||
/.dart_tool/ |
||||
/pubspec.lock |
||||
packages/**/coverage |
||||
|
||||
# Melos reccomends not adding them to vcs but we need them as we don't use melos in CI |
||||
# **/pubspec_overrides.yaml |
||||
|
@ -1,43 +1,71 @@
|
||||
part of '../../neon.dart'; |
||||
|
||||
@immutable |
||||
class Result<T> { |
||||
Result( |
||||
const Result( |
||||
this.data, |
||||
this.error, { |
||||
required this.loading, |
||||
required this.cached, |
||||
required this.isLoading, |
||||
required this.isCached, |
||||
}); |
||||
|
||||
factory Result.loading() => Result( |
||||
factory Result.loading() => const Result( |
||||
null, |
||||
null, |
||||
loading: true, |
||||
cached: false, |
||||
isLoading: true, |
||||
isCached: false, |
||||
); |
||||
|
||||
factory Result.success(final T data) => Result( |
||||
data, |
||||
null, |
||||
loading: false, |
||||
cached: false, |
||||
isLoading: false, |
||||
isCached: false, |
||||
); |
||||
|
||||
factory Result.error(final Object error) => Result( |
||||
null, |
||||
error, |
||||
loading: false, |
||||
cached: false, |
||||
isLoading: false, |
||||
isCached: false, |
||||
); |
||||
|
||||
final T? data; |
||||
final Object? error; |
||||
final bool loading; |
||||
final bool cached; |
||||
final bool isLoading; |
||||
final bool isCached; |
||||
|
||||
Result<R> transform<R>(final R? Function(T data) call) => Result( |
||||
data != null ? call(data as T) : null, |
||||
error, |
||||
loading: loading, |
||||
cached: cached, |
||||
isLoading: isLoading, |
||||
isCached: isCached, |
||||
); |
||||
|
||||
Result<T> asLoading() => Result( |
||||
data, |
||||
error, |
||||
isLoading: true, |
||||
isCached: isCached, |
||||
); |
||||
|
||||
bool get hasError => error != null; |
||||
|
||||
bool get hasData => data != null; |
||||
bool get hasUncachedData => hasData && !isCached; |
||||
|
||||
T get requireData { |
||||
if (hasData) { |
||||
return data!; |
||||
} |
||||
|
||||
throw StateError('Result has no data'); |
||||
} |
||||
|
||||
@override |
||||
bool operator ==(final Object other) => |
||||
other is Result && other.isLoading == isLoading && other.data == data && other.error == error; |
||||
|
||||
@override |
||||
int get hashCode => Object.hash(data, error, isLoading, isCached); |
||||
} |
||||
|
@ -1,28 +1,51 @@
|
||||
part of '../../neon.dart'; |
||||
|
||||
class ResultBuilder<R> extends StatelessWidget { |
||||
typedef ResultWidgetBuilder<T> = Widget Function(BuildContext context, Result<T> snapshot); |
||||
|
||||
class ResultBuilder<T> extends StreamBuilderBase<Result<T>, Result<T>> { |
||||
const ResultBuilder({ |
||||
required this.stream, |
||||
required this.builder, |
||||
this.initialData, |
||||
super.stream, |
||||
super.key, |
||||
}); |
||||
|
||||
final Stream<Result<R>?>? stream; |
||||
ResultBuilder.behaviorSubject({ |
||||
required this.builder, |
||||
BehaviorSubject<Result<T>>? super.stream, |
||||
super.key, |
||||
}) : initialData = stream?.valueOrNull; |
||||
|
||||
final ResultWidgetBuilder<T> builder; |
||||
final Result<T>? initialData; |
||||
|
||||
@override |
||||
Result<T> initial() => initialData?.asLoading() ?? Result<T>.loading(); |
||||
|
||||
@override |
||||
Result<T> afterData(final Result<T> current, final Result<T> data) { |
||||
// prevent rebuild when only the cache state cahnges |
||||
if (current == data) { |
||||
return current; |
||||
} |
||||
|
||||
return data; |
||||
} |
||||
|
||||
@override |
||||
Result<T> afterError(final Result<T> current, final Object error, final StackTrace stackTrace) { |
||||
if (current.hasError) { |
||||
return current; |
||||
} |
||||
|
||||
final Widget Function(BuildContext, Result<R>) builder; |
||||
return Result( |
||||
current.data, |
||||
error, |
||||
isLoading: false, |
||||
isCached: false, |
||||
); |
||||
} |
||||
|
||||
@override |
||||
Widget build(final BuildContext context) => StreamBuilder( |
||||
stream: stream, |
||||
builder: (final context, final snapshot) { |
||||
if (snapshot.hasError) { |
||||
return builder(context, Result.error(snapshot.error!)); |
||||
} |
||||
if (snapshot.hasData) { |
||||
return builder(context, snapshot.data!); |
||||
} |
||||
|
||||
return builder(context, Result.loading()); |
||||
}, |
||||
); |
||||
Widget build(final BuildContext context, final Result<T> currentSummary) => builder(context, currentSummary); |
||||
} |
||||
|
@ -0,0 +1,84 @@
|
||||
import 'package:neon/neon.dart'; |
||||
import 'package:test/test.dart'; |
||||
|
||||
void main() { |
||||
group('Result', () { |
||||
test('Equality', () { |
||||
const data = 'someData'; |
||||
|
||||
const a = Result( |
||||
data, |
||||
null, |
||||
isLoading: true, |
||||
isCached: false, |
||||
); |
||||
const b = Result( |
||||
data, |
||||
null, |
||||
isLoading: true, |
||||
isCached: true, |
||||
); |
||||
|
||||
expect(a, equals(a), reason: 'identical'); |
||||
expect(a, equals(b), reason: 'ignore cached state in equality'); |
||||
|
||||
expect(a.hashCode, equals(a.hashCode), reason: 'identical'); |
||||
expect(a.hashCode, isNot(equals(b.hashCode)), reason: 'hashcode should respect the cached state'); |
||||
}); |
||||
|
||||
test('Transform to loading', () { |
||||
const data = 'someData'; |
||||
|
||||
final a = Result.success(data); |
||||
const b = Result( |
||||
data, |
||||
null, |
||||
isLoading: true, |
||||
isCached: false, |
||||
); |
||||
|
||||
expect(a, isNot(equals(b))); |
||||
expect(a.asLoading(), equals(b)); |
||||
}); |
||||
|
||||
test('data check', () { |
||||
const data = 'someData'; |
||||
|
||||
final a = Result.loading(); |
||||
final b = Result.success(data); |
||||
const c = Result( |
||||
data, |
||||
null, |
||||
isLoading: false, |
||||
isCached: true, |
||||
); |
||||
|
||||
expect(a.hasData, false); |
||||
expect(b.hasData, true); |
||||
|
||||
expect(() => a.requireData, throwsStateError); |
||||
expect(b.requireData, equals(data)); |
||||
|
||||
expect(b.hasUncachedData, true); |
||||
expect(c.hasUncachedData, false); |
||||
}); |
||||
|
||||
test('error check', () { |
||||
const error = 'someError'; |
||||
|
||||
final a = Result.error(error); |
||||
|
||||
expect(a.hasError, true); |
||||
}); |
||||
|
||||
test('transform', () { |
||||
const data = 1; |
||||
|
||||
final a = Result.success(data); |
||||
|
||||
String transformer(final int data) => data.toString(); |
||||
|
||||
expect(a.transform(transformer), equals(Result.success(data.toString()))); |
||||
}); |
||||
}); |
||||
} |
Loading…
Reference in new issue