Skip to content

Latest commit

History

18 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Any Refreshable Widget

pub packagepub pointspopularitylikes

A powerful and flexible Flutter package that provides pull-to-refresh functionality for any widget, with support for single and multiple futures, custom indicators, and comprehensive error handling.

Any Refreshable Widget Demo

Features

  • 🔄 Single & Multiple Future Support - Handle one or multiple asynchronous operations
  • 🎮 Controller Support - Programmatic refresh triggering with state management
  • 🎨 Customizable Refresh Indicator - Full control over appearance and behavior
  • 📱 Universal Widget Support - Works with any widget, automatically makes content scrollable
  • 🎯 Smart Error Handling - Comprehensive error states and callbacks
  • 🔧 Highly Configurable - Colors, displacement, stroke width, trigger modes, and more
  • Lifecycle Callbacks - onBeforeRefresh and onAfterRefresh hooks with sync/async support
  • 🔀 Flexible Concurrency - Choose between concurrent (parallel) or sequential execution
  • 🚀 Production Ready - Thoroughly tested and optimized for real-world applications

Installation

Add this to your package's pubspec.yaml file:

dependencies:
any_refreshable_widget: ^0.2.0

Then run:

flutter pub get

Quick Start

Import the package

import'package:any_refreshable_widget/any_refreshable_widget.dart';

Basic Usage - Single Future

AnyRefreshableWidget.single(
onRefresh: () async {
// Your refresh logic hereawaitfetchUserData();
},
builder: (context, isLoading, error) {
if (error !=null) {
returnCenter(child:Text('Error: $error'));
}
if (isLoading) {
returnconstCenter(child:CircularProgressIndicator());
}
returnconstCenter(child:Text('Pull down to refresh!'));
},
)

Advanced Usage - Multiple Futures

AnyRefreshableWidget(
onRefresh: [
() =>fetchUserData(),
() =>fetchNotifications(),
() =>fetchSettings(),
],
builder: (context, isLoading, error) {
if (error !=null) {
returnErrorWidget(error);
}
if (isLoading) {
returnconstLoadingWidget();
}
returnconstContentWidget();
},
)

Controller State Management

Access refresh state and errors through the controller:

final controller =AnyRefreshableController();
// Check if controller is attached to a widgetif (controller.isAttached) {
// Check if refresh is in progressbool isRefreshing = controller.isRefreshing;
// Get current error, if anyObject? currentError = controller.error;
// Trigger refresh programmaticallyawait controller.refresh();
}

Concurrency Control

Control how multiple futures are executed:

Concurrent Execution (Default)

AnyRefreshableWidget(
concurrency:RefreshConcurrency.concurrent,
onRefresh: [
() =>fetchUserData(), // These run simultaneously
() =>fetchNotifications(), // for faster completion
() =>fetchSettings(),
],
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Sequential Execution

AnyRefreshableWidget(
concurrency:RefreshConcurrency.sequential, // Default
onRefresh: [
() =>authenticateUser(), // Runs first
() =>fetchUserData(), // Then this
() =>fetchNotifications(), // Finally this
],
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Comprehensive Examples

Custom Refresh Indicator

AnyRefreshableWidget.single(
onRefresh: () =>performRefresh(),
refreshColor:Colors.blue,
backgroundColor:Colors.white,
displacement:60.0,
strokeWidth:3.0,
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Custom Indicator Widget

AnyRefreshableWidget.single(
onRefresh: () =>performRefresh(),
customIndicator:Container(
padding:constEdgeInsets.all(16),
child:constRow(
mainAxisAlignment:MainAxisAlignment.center,
children: [
CircularProgressIndicator(strokeWidth:2),
SizedBox(width:16),
Text('Refreshing...'),
],
),
),
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Error Handling

AnyRefreshableWidget.single(
onRefresh: () async {
// Simulate potential errorif (Random().nextBool()) {
throwException('Network error occurred');
}
awaitfetchData();
},
builder: (context, isLoading, error) {
if (error !=null) {
returnCenter(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children: [
constIcon(Icons.error, color:Colors.red, size:48),
constSizedBox(height:16),
Text('Error: ${error.toString()}'),
constSizedBox(height:16),
ElevatedButton(
onPressed: () {
// Trigger refresh programmatically
},
child:constText('Retry'),
),
],
),
);
}
if (isLoading) {
returnconstCenter(
child:Column(
mainAxisAlignment:MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height:16),
Text('Loading...'),
],
),
);
}
returnYourDataWidget();
},
)

With ListView

AnyRefreshableWidget.single(
onRefresh: () =>refreshListData(),
builder: (context, isLoading, error) {
if (error !=null) returnErrorWidget(error);
returnListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
returnListTile(
title:Text(items[index].title),
subtitle:Text(items[index].description),
);
},
);
},
)

Lifecycle Callbacks

The package supports onBeforeRefresh and onAfterRefresh callbacks that can be either synchronous or asynchronous:

Synchronous Callbacks

AnyRefreshableWidget.single(
onBeforeRefresh: () {
print('Starting refresh...');
// Synchronous setup logic
},
onRefresh: () =>fetchData(),
onAfterRefresh: () {
print('Refresh completed!');
// Synchronous cleanup logic
},
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Asynchronous Callbacks

AnyRefreshableWidget.single(
onBeforeRefresh: () async {
print('Starting refresh...');
awaitprepareForRefresh();
// Asynchronous setup logic
},
onRefresh: () =>fetchData(),
onAfterRefresh: () {
print('Refresh completed!');
// Cleanup logic (always sync)
},
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

API Reference

AnyRefreshableWidget

ParameterTypeRequiredDefaultDescription
onRefreshList<Future<void> Function()>-List of async functions to execute on refresh
builderWidget Function(BuildContext, bool, Object?)-Builder function with loading and error states
controllerAnyRefreshableController?nullController for programmatic refresh triggering
concurrencyRefreshConcurrencysequentialHow futures should be executed (concurrent/sequential)
onBeforeRefreshFutureOr<void> Function()?nullCallback executed before refresh starts (sync/async)
onAfterRefreshVoidCallback?nullCallback executed after refresh completes
refreshColorColor?nullColor of the refresh indicator
backgroundColorColor?nullBackground color of the refresh indicator
displacementdouble40.0Distance from top to show indicator
strokeWidthdouble2.0Stroke width of the progress indicator
customIndicatorWidget?nullCustom refresh indicator widget
triggerModeRefreshIndicatorTriggerMode?anywhereWhen the indicator should trigger
notificationPredicatebool Function(ScrollNotification)?nullCustom scroll notification predicate

AnyRefreshableWidget.single

Same parameters as AnyRefreshableWidget, but onRefresh takes a single Future<void> Function() instead of a list. The concurrency parameter is not applicable for single futures.

RefreshConcurrency Enum

ValueDescriptionUse Case
RefreshConcurrency.concurrentExecute all futures simultaneously using Future.waitfastest refresh when futures are independent
RefreshConcurrency.sequentialExecute futures one by one in orderWhen futures depend on each other or to limit resource usage

Callback Execution Order

When a refresh is triggered, the callbacks execute in this order:

  1. onBeforeRefresh - Called first, awaited if async
  2. Loading state - isLoading becomes true, UI updates
  3. onRefresh - All futures execute concurrently
  4. Loading state - isLoading becomes false, UI updates
  5. onAfterRefresh - Called last, always synchronous

Advanced Configuration

Custom Scroll Notification Predicate

AnyRefreshableWidget.single(
onRefresh: () =>performRefresh(),
notificationPredicate: (ScrollNotification notification) {
// Custom logic to determine when refresh should triggerreturn notification.depth ==0&& notification.metrics.pixels <=0;
},
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Trigger Modes

AnyRefreshableWidget.single(
onRefresh: () =>performRefresh(),
triggerMode:RefreshIndicatorTriggerMode.onEdge, // or .anywhere
builder: (context, isLoading, error) {
returnYourContentWidget();
},
)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Issues

If you encounter any issues or have suggestions, please file them in the GitHub Issues.

Changelog

See CHANGELOG.md for a detailed changelog.


Made with ❤️ by Yama-Roni

About

A powerful and flexible Flutter package that provides pull-to-refresh functionality for any widget, with support for single and multiple futures, custom indicators, and comprehensive error handling.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages