You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
6.1 KiB
175 lines
6.1 KiB
import 'package:flutter/widgets.dart'; |
|
import 'package:hooks_riverpod/hooks_riverpod.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 as T) : const 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 = const RefreshFutureBuilderSnapshot.waiting(); |
|
break; |
|
case RefreshFutureBuilderState.initial: |
|
snapshot = RefreshFutureBuilderSnapshot.refresh(snapshot.data); |
|
break; |
|
case RefreshFutureBuilderState.waiting: |
|
return; |
|
case RefreshFutureBuilderState.error: |
|
snapshot = const 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, |
|
} |
|
|
|
class RefreshFutureBuilderProviderAdapter<T> extends ConsumerWidget { |
|
final Provider<AsyncValue<T>> futureProvider; |
|
final Future Function()? refresh; |
|
final Widget Function(BuildContext context, Future Function() refresh, Future Function(Future<T> Function()) replaceFuture, RefreshFutureBuilderSnapshot<T> snapshot) builder; |
|
|
|
const RefreshFutureBuilderProviderAdapter({required this.futureProvider, required this.builder, this.refresh, super.key}); |
|
|
|
@override |
|
Widget build(BuildContext context, WidgetRef ref) { |
|
final value = ref.watch(futureProvider); |
|
|
|
return builder( |
|
context, |
|
refresh ?? () async { |
|
ref.invalidate(futureProvider); |
|
}, |
|
(_) => throw UnimplementedError('Cannot replace the future when adapting a FutureProvider'), |
|
value.when( |
|
data: (data) => value.isLoading || value.isRefreshing |
|
? RefreshFutureBuilderSnapshot.refresh(data) |
|
: RefreshFutureBuilderSnapshot.withData(data), |
|
error: (error, st) => value.isLoading || value.isRefreshing |
|
? RefreshFutureBuilderSnapshot.refreshError(value.hasValue ? value.value : null, value.error, value.stackTrace) |
|
: RefreshFutureBuilderSnapshot.withError(error, st), |
|
loading: () { |
|
if (value.hasValue) { |
|
return RefreshFutureBuilderSnapshot.refresh(value.value, value.error, value.stackTrace); |
|
} |
|
else if (value.hasError) { |
|
return RefreshFutureBuilderSnapshot.refreshError(value.value, value.error, value.stackTrace); |
|
} |
|
return const RefreshFutureBuilderSnapshot.waiting(); |
|
}, |
|
), |
|
); |
|
} |
|
}
|
|
|