|
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
|
|
|
|
class RefreshFutureBuilder<T> extends StatefulWidget {
|
|
|
|
final Future<T> Function()? futureCreator;
|
|
|
|
final T? initialData;
|
|
|
|
final Widget Function(BuildContext context, Future Function() refresh, Future Function(Future<T> Function()) replaceFuture, RefreshFutureBuilderSnapshot<T> snapshot) builder;
|
|
|
|
|
|
|
|
const RefreshFutureBuilder({ Key? key, this.futureCreator, this.initialData, required this.builder }) : super(key: key);
|
|
|
|
|
|
|
|
@override
|
|
|
|
_RefreshFutureBuilderState<T> createState() => _RefreshFutureBuilderState<T>();
|
|
|
|
}
|
|
|
|
|
|
|
|
class _RefreshFutureBuilderState<T> extends State<RefreshFutureBuilder<T>> {
|
|
|
|
late RefreshFutureBuilderSnapshot<T> snapshot;
|
|
|
|
Future<T> Function()? futureCreator;
|
|
|
|
bool _disposed = false;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
snapshot = widget.initialData != null ? RefreshFutureBuilderSnapshot.initial(widget.initialData!) : RefreshFutureBuilderSnapshot.nothing();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void didChangeDependencies() {
|
|
|
|
super.didChangeDependencies();
|
|
|
|
if (futureCreator != widget.futureCreator) {
|
|
|
|
futureCreator = widget.futureCreator;
|
|
|
|
runFuture();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future runFuture() async {
|
|
|
|
if (futureCreator == null) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Set state to signify loading
|
|
|
|
setState(() {
|
|
|
|
switch (snapshot.state) {
|
|
|
|
case RefreshFutureBuilderState.none:
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.waiting();
|
|
|
|
break;
|
|
|
|
case RefreshFutureBuilderState.initial:
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data);
|
|
|
|
break;
|
|
|
|
case RefreshFutureBuilderState.waiting:
|
|
|
|
return;
|
|
|
|
case RefreshFutureBuilderState.error:
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.waiting();
|
|
|
|
break;
|
|
|
|
case RefreshFutureBuilderState.done:
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data);
|
|
|
|
break;
|
|
|
|
case RefreshFutureBuilderState.refreshing:
|
|
|
|
return;
|
|
|
|
case RefreshFutureBuilderState.refreshError:
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.refresh(null, snapshot.error, snapshot.stackTrace);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
});
|
|
|
|
try {
|
|
|
|
final data = await futureCreator!();
|
|
|
|
if (_disposed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setState(() {
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.withData(data);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
catch (e, st) {
|
|
|
|
if (_disposed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setState(() {
|
|
|
|
if (snapshot.state == RefreshFutureBuilderState.waiting) {
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.withError(e, st);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
snapshot = RefreshFutureBuilderSnapshot.refreshError(snapshot.data, e, st);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future replaceFutureCreator(Future<T> Function() newFutureCreator) {
|
|
|
|
futureCreator = newFutureCreator;
|
|
|
|
return runFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_disposed = true;
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return widget.builder(
|
|
|
|
context,
|
|
|
|
runFuture,
|
|
|
|
replaceFutureCreator,
|
|
|
|
snapshot,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class RefreshFutureBuilderSnapshot<T> {
|
|
|
|
final RefreshFutureBuilderState state;
|
|
|
|
final T? data;
|
|
|
|
final Object? error;
|
|
|
|
final StackTrace? stackTrace;
|
|
|
|
|
|
|
|
bool get hasData => data != null;
|
|
|
|
bool get hasError => error != null;
|
|
|
|
|
|
|
|
const RefreshFutureBuilderSnapshot._(this.state, this.data, this.error, this.stackTrace);
|
|
|
|
const RefreshFutureBuilderSnapshot.nothing() : state = RefreshFutureBuilderState.none, data = null, error = null, stackTrace = null;
|
|
|
|
const RefreshFutureBuilderSnapshot.initial(this.data) : state = RefreshFutureBuilderState.initial, error = null, stackTrace = null;
|
|
|
|
const RefreshFutureBuilderSnapshot.waiting() : state = RefreshFutureBuilderState.waiting, data = null, error = null, stackTrace = null;
|
|
|
|
const RefreshFutureBuilderSnapshot.withError(this.error, [this.stackTrace]) : state = RefreshFutureBuilderState.error, data = null;
|
|
|
|
const RefreshFutureBuilderSnapshot.withData(this.data) : state = RefreshFutureBuilderState.done, error = null, stackTrace = null;
|
|
|
|
const RefreshFutureBuilderSnapshot.refresh(this.data, [this.error, this.stackTrace]) : state = RefreshFutureBuilderState.refreshing;
|
|
|
|
const RefreshFutureBuilderSnapshot.refreshError(this.data, this.error, this.stackTrace) : state = RefreshFutureBuilderState.refreshError;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum RefreshFutureBuilderState {
|
|
|
|
none,
|
|
|
|
initial,
|
|
|
|
waiting,
|
|
|
|
error,
|
|
|
|
done,
|
|
|
|
refreshing,
|
|
|
|
refreshError,
|
|
|
|
}
|