Skip to content

Instantly share code, notes, and snippets.

@AlexV525
Last active August 17, 2022 07:54
Show Gist options
  • Save AlexV525/d43c25428fdea9852b14ea167af495f5 to your computer and use it in GitHub Desktop.
Save AlexV525/d43c25428fdea9852b14ea167af495f5 to your computer and use it in GitHub Desktop.
`TriState` indicates the loading process of specific data
//
// [Author] Alex (https://github.com/AlexV525)
// [Date] 2022/08/17 13:45
//
import 'package:flutter/material.dart';
/// A tri-state indicates the loading process of specific data.
enum TriState {
/// The data is still loading, not available.
loading,
/// The data has failed to load, not available.
failed,
/// The data has loaded successfully, available.
succeed,
}
/// A [ValueNotifier] that holds the [TriState] and [T].
///
/// [state] and [value] will notify listeners when they've changed.
///
/// [T] should be non-nullable since it's an actual/real data.
class TriStateNotifier<T extends Object> extends ValueNotifier<T?> {
TriStateNotifier(
T? data, {
TriState state = TriState.loading,
}) : _state = state,
super(data);
TriState get state => _state;
TriState _state;
set state(TriState newValue) {
if (_state == newValue) {
return;
}
_state = newValue;
notifyListeners();
}
void successWithData(T data) {
_state = TriState.succeed;
value = data;
}
void fail() {
state = TriState.failed;
}
}
/// A widget whose content stays synced with a [TriStateNotifier].
///
/// Given a [TriStateNotifier<T>] and a [dataBuilder] which builds widgets from
/// concrete values of `T`, this class will automatically register itself as a
/// listener of the [TriStateNotifier] and call the [dataBuilder] with updated
/// values when the value changes.
class TriStateBuilder<T extends Object> extends StatefulWidget {
/// Creates a [TriStateBuilder].
const TriStateBuilder({
Key? key,
required this.notifier,
this.stateBuilder,
this.dataBuilder,
this.builder,
this.child,
}) : assert(
builder != null || (stateBuilder != null && dataBuilder != null),
'At least one kind of builders must be specified.',
),
super(key: key);
/// The [TriStateNotifier] whose value you depend on in order to build.
///
/// This widget does not ensure that the [TriStateNotifier]'s value is not
/// null, therefore your [builder] may need to handle null values.
///
/// This [notifier] itself must not be null.
final TriStateNotifier<T> notifier;
/// A [ValueWidgetBuilder] which builds a widget when the [notifier]'s state
/// is changing, and not succeed.
final ValueWidgetBuilder<TriState>? stateBuilder;
/// A [ValueWidgetBuilder] which builds a widget when the [notifier] succeed
/// and it's data is not null.
final ValueWidgetBuilder<T>? dataBuilder;
/// A builder which builds a widget when the [notifier]'s state or value is
/// changing, you'll need to handle all cases within the [notifier].
///
/// When the [builder] is not null, all other builder won't have effects.
final Widget Function(BuildContext, TriState, T?, Widget?)? builder;
/// A [notifier]-independent widget which is passed back to the [builder].
final Widget? child;
@override
State<TriStateBuilder<T>> createState() => _TriStateBuilderState<T>();
}
class _TriStateBuilderState<T extends Object>
extends State<TriStateBuilder<T>> {
late TriState state;
late T? value;
@override
void initState() {
super.initState();
state = widget.notifier.state;
value = widget.notifier.value;
widget.notifier.addListener(_valueChanged);
}
@override
void didUpdateWidget(TriStateBuilder<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.notifier != widget.notifier) {
oldWidget.notifier.removeListener(_valueChanged);
state = widget.notifier.state;
value = widget.notifier.value;
widget.notifier.addListener(_valueChanged);
}
}
@override
void dispose() {
widget.notifier.removeListener(_valueChanged);
super.dispose();
}
void _valueChanged() {
setState(() {
state = widget.notifier.state;
value = widget.notifier.value;
});
}
@override
Widget build(BuildContext context) {
if (widget.builder != null) {
return widget.builder!(context, state, value, widget.child);
}
if (state != TriState.succeed) {
return widget.stateBuilder!(context, state, widget.child);
}
return widget.dataBuilder!(context, value!, widget.child);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment