import 'package:flutter/widgets.dart'; class RefreshFutureBuilder extends StatefulWidget { final Future Function()? futureCreator; final T? initialData; final Widget Function(BuildContext context, Future Function() refresh, 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!) : 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); } }); } } @override void dispose() { _disposed = true; super.dispose(); } @override Widget build(BuildContext context) { return widget.builder( context, runFuture, 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, }