import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class RefreshFutureBuilder extends StatefulWidget { final Future Function()? futureCreator; final T? initialData; final Widget Function(BuildContext context, Future Function() refresh, Future Function(Future Function()) replaceFuture, RefreshFutureBuilderSnapshot snapshot) builder; const RefreshFutureBuilder({ Key? key, this.futureCreator, this.initialData, required this.builder }) : super(key: key); @override _RefreshFutureBuilderState createState() => _RefreshFutureBuilderState(); } class _RefreshFutureBuilderState extends State> { late RefreshFutureBuilderSnapshot snapshot; Future 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 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 { 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 extends ConsumerWidget { final Provider> futureProvider; final Future Function()? refresh; final Widget Function(BuildContext context, Future Function() refresh, Future Function(Future Function()) replaceFuture, RefreshFutureBuilderSnapshot 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(); }, ), ); } }