async_switcher 1.0.0
async_switcher: ^1.0.0 copied to clipboard
A smart Flutter widget that automatically switches UI based on async state (loading → empty → error → data). Framework-independent, supports Future & Stream.
Async Switcher #
A smart Flutter widget that automatically switches UI based on async state. Framework-independent and works with any state management solution.
Features #
- Automatic State Switching: Loading → Empty → Error → Data
- Future & Stream Support: Built-in constructors for
Future<T>andStream<T> - Animated Transitions: Smooth transitions between states (configurable)
- Retry Functionality: Built-in retry callback for error states
- Auto Empty Detection: Automatically detects empty collections
- Customizable Widgets: Custom widgets for each state
- Framework Independent: Works with any state management (Bloc, Riverpod, Provider, etc.)
- Default Widgets: Beautiful default widgets included out of the box
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
async_switcher: ^1.0.0
Quick Start #
Basic Usage #
import 'package:async_switcher/async_switcher.dart';
StateWrapper<List<Product>>(
state: productState,
builder: (context, products) => ProductList(products: products),
loadingMessage: 'Loading products...',
emptyMessage: 'No products found',
errorMessage: 'Failed to load products',
onRetry: () => loadProducts(),
)
With Future #
StateWrapper.fromFuture<List<Product>>(
future: fetchProducts(),
builder: (context, products) => ProductList(products: products),
loadingMessage: 'Loading...',
emptyMessage: 'No products',
onRetry: () => loadProducts(),
)
With Stream #
StateWrapper.fromStream<List<Product>>(
stream: productsStream(),
builder: (context, products) => ProductList(products: products),
loadingMessage: 'Loading...',
onRetry: () => resubscribe(),
)
API Reference #
StateWrapper #
The main widget that switches UI based on AsyncValue<T> state.
Constructor Parameters
| Parameter | Type | Description |
|---|---|---|
state |
AsyncValue<T> |
The current async state |
builder |
Widget Function(BuildContext, T) |
Builder for data state (required) |
loading |
Widget? |
Custom loading widget |
empty |
Widget? |
Custom empty widget |
error |
Widget? |
Custom error widget |
onRetry |
VoidCallback? |
Retry callback for error state |
animate |
bool |
Enable animated transitions (default: true) |
transitionDuration |
Duration |
Transition duration (default: 300ms) |
transitionCurve |
Curve |
Transition curve (default: Curves.easeInOut) |
loadingMessage |
String? |
Message for default loading widget |
emptyMessage |
String? |
Message for default empty widget |
errorMessage |
String? |
Message for default error widget |
detectEmpty |
bool |
Auto-detect empty collections (default: true) |
isEmptyPredicate |
bool Function(T)? |
Custom empty detection predicate |
AsyncValue #
Sealed class representing async states:
AsyncLoading<T>()- Loading stateAsyncData<T>(T data)- Success state with dataAsyncEmpty<T>()- Empty state (no data)AsyncError<T>(String message, {Object? error, StackTrace? stackTrace})- Error state
Helper Methods
// Check state type
state.isLoading // bool
state.hasData // bool
state.isEmpty // bool
state.isError // bool
// Get data if available
final data = state.dataOrNull; // T?
// Map over data
final newState = state.map((data) => data.length);
Customization Examples #
Custom Loading Widget #
StateWrapper<List<Product>>(
state: productState,
builder: (context, products) => ProductList(products: products),
loading: ShimmerLoader(), // Your custom shimmer
)
Custom Empty Widget #
StateWrapper<List<Product>>(
state: productState,
builder: (context, products) => ProductList(products: products),
empty: Center(
child: Column(
children: [
Icon(Icons.shopping_bag_outlined, size: 64),
Text('Your cart is empty'),
],
),
),
)
Custom Error Widget #
StateWrapper<List<Product>>(
state: productState,
builder: (context, products) => ProductList(products: products),
error: CustomErrorView(
message: 'Failed to load',
onRetry: () => loadProducts(),
),
)
Disable Animations #
StateWrapper<List<Product>>(
state: productState,
builder: (context, products) => ProductList(products: products),
animate: false,
)
Custom Empty Detection #
StateWrapper<Map<String, dynamic>>(
state: mapState,
builder: (context, map) => MapView(map: map),
isEmptyPredicate: (map) => map.isEmpty, // Custom check
)
State Management Examples #
With Riverpod #
final productsProvider = FutureProvider<List<Product>>((ref) async {
return await fetchProducts();
});
// In widget
StateWrapper<List<Product>>(
state: ref.watch(productsProvider).when(
data: (data) => AsyncData(data),
loading: () => AsyncLoading(),
error: (err, stack) => AsyncError(err.toString()),
),
builder: (context, products) => ProductList(products: products),
)
With Bloc #
// In your bloc
Stream<List<Product>> mapEventToState(ProductEvent event) async* {
yield ProductLoading();
try {
final products = await repository.fetchProducts();
yield ProductLoaded(products);
} catch (e) {
yield ProductError(e.toString());
}
}
// In widget
StateWrapper<List<Product>>(
state: state.when(
loading: () => AsyncLoading(),
loaded: (products) => AsyncData(products),
error: (message) => AsyncError(message),
),
builder: (context, products) => ProductList(products: products),
)
With Provider/ChangeNotifier #
class ProductNotifier extends ChangeNotifier {
AsyncValue<List<Product>> state = AsyncLoading();
Future<void> loadProducts() async {
state = AsyncLoading();
notifyListeners();
try {
final products = await repository.fetchProducts();
state = AsyncData(products);
} catch (e) {
state = AsyncError(e.toString());
}
notifyListeners();
}
}
// In widget
StateWrapper<List<Product>>(
state: context.watch<ProductNotifier>().state,
builder: (context, products) => ProductList(products: products),
onRetry: () => context.read<ProductNotifier>().loadProducts(),
)
Use Cases #
- API Call Screens - Handle loading/error/data states automatically
- List Screens - Perfect for product lists, user lists, etc.
- Grid Views - Image galleries, product grids
- Search Results - Empty states when no results found
- Pagination - Loading more items
- Firebase Streams - Real-time data from Firestore
- Forms - Async validation states
Architecture #
The package uses a sealed class hierarchy for type-safe state management:
AsyncValue<T>
├── AsyncLoading<T>
├── AsyncData<T>
├── AsyncEmpty<T>
└── AsyncError<T>
This ensures exhaustive pattern matching and prevents invalid states.
Additional Resources #
- Example App - Complete working examples
- CHANGELOG - Version history
- API Documentation - Full API docs
Testing #
The package includes comprehensive tests. Run them with:
flutter test
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments #
Inspired by Riverpod's AsyncValue but designed to be framework-independent and more flexible.
Package Health #
This package aims for high pub.flutter-io.cn scores with:
- Comprehensive documentation
- Full test coverage
- Example application
- Type-safe API
- Framework independence