Last active
August 17, 2022 07:54
-
-
Save AlexV525/d43c25428fdea9852b14ea167af495f5 to your computer and use it in GitHub Desktop.
`TriState` indicates the loading process of specific data
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// [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