🎯 Loader System
The Loader System provides automatic and manual loading indicators for better user experience during module loading and async operations.
🚀 Automatic Loader
The loader automatically appears during:
- Module loading and initialization
- Dependency injection
- Route transitions
- Async navigation
Default Behavior
class AppWidget extends StatelessWidget {
Widget build(BuildContext context) {
return ModularApp.router(
title: 'My App',
theme: ThemeData(primarySwatch: Colors.blue),
// Automatic loader is enabled by default
);
}
}
🔧 Manual Control
Show/Hide Loader
// Show loader
ModularLoader.show();
// Hide loader
ModularLoader.hide();
// Show with custom message
ModularLoader.show(message: 'Loading data...');
// Hide with delay
ModularLoader.hide(delay: Duration(milliseconds: 500));
Usage in Controllers
class UserController {
Future<void> loadUserData() async {
ModularLoader.show(message: 'Loading user data...');
try {
await userService.fetchUser();
} finally {
ModularLoader.hide();
}
}
}
🎨 Custom Loader
Create Custom Loader
class MyLoader extends CustomModularLoader {
Color get backgroundColor => Colors.black87;
Widget get child => Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: Colors.blue),
SizedBox(height: 16),
Text('Loading...', style: TextStyle(color: Colors.white)),
],
);
}
Use Custom Loader
class AppWidget extends StatelessWidget {
Widget build(BuildContext context) {
return ModularApp.router(
customModularLoader: MyLoader(),
title: 'My App',
);
}
}
Advanced Custom Loader
class AnimatedLoader extends CustomModularLoader {
Color get backgroundColor => Colors.black.withOpacity(0.8);
Widget get child => Container(
padding: EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
SizedBox(height: 16),
Text(
'Loading...',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[800],
),
),
],
),
);
}
🔄 Async Navigation
Basic Async Navigation
ElevatedButton(
onPressed: () async {
setState(() => isLoading = true);
try {
await context.goAsync('/heavy-page');
} finally {
setState(() => isLoading = false);
}
},
child: Text('Navigate'),
);
Pre-loading Data
ElevatedButton(
onPressed: () async {
ModularLoader.show(message: 'Preparing data...');
try {
// Pre-load data before navigation
await userService.loadUserData();
await productService.loadProducts();
context.go('/dashboard');
} finally {
ModularLoader.hide();
}
},
child: Text('Load Dashboard'),
);
Conditional Loading
ElevatedButton(
onPressed: () async {
if (needsDataRefresh) {
ModularLoader.show(message: 'Refreshing data...');
await refreshData();
ModularLoader.hide();
}
context.go('/next-page');
},
child: Text('Continue'),
);
🎯 Advanced Patterns
Loader with Progress
class ProgressLoader extends CustomModularLoader {
final double progress;
ProgressLoader(this.progress);
Widget get child => Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(value: progress),
SizedBox(height: 16),
Text('${(progress * 100).toInt()}% Complete'),
],
);
}
// Usage
ModularLoader.show(customLoader: ProgressLoader(0.5));
Loader with Actions
class ActionLoader extends CustomModularLoader {
final VoidCallback? onCancel;
ActionLoader({this.onCancel});
Widget get child => Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('Processing...'),
if (onCancel != null) ...[
SizedBox(height: 16),
TextButton(
onPressed: onCancel,
child: Text('Cancel'),
),
],
],
);
}
// Usage
ModularLoader.show(
customLoader: ActionLoader(
onCancel: () {
ModularLoader.hide();
// Cancel operation
},
),
);
Module-Specific Loaders
class AuthModule extends Module {
void initState(Injector i) {
// Show custom loader for auth module
ModularLoader.show(
customLoader: AuthLoader(),
message: 'Initializing authentication...',
);
}
void dispose() {
ModularLoader.hide();
}
}
class AuthLoader extends CustomModularLoader {
Widget get child => Column(
children: [
Icon(Icons.security, color: Colors.blue, size: 48),
SizedBox(height: 16),
Text('Authenticating...'),
],
);
}
🛡️ Best Practices
1. Always Hide Loader
// ✅ Good - Always hide loader
try {
ModularLoader.show();
await heavyOperation();
} finally {
ModularLoader.hide();
}
// ❌ Avoid - Might not hide on error
ModularLoader.show();
await heavyOperation();
ModularLoader.hide(); // Won't execute if error occurs
2. Use Descriptive Messages
// ✅ Good - Clear message
ModularLoader.show(message: 'Loading user profile...');
// ❌ Avoid - Generic message
ModularLoader.show(message: 'Loading...');
3. Consider User Experience
// ✅ Good - Show loader only for long operations
if (operationDuration > Duration(milliseconds: 500)) {
ModularLoader.show();
}
// ❌ Avoid - Show loader for quick operations
ModularLoader.show(); // For operations < 100ms
📚 Related Topics
- 🛣️ Routes - Async navigation patterns
- 💉 Dependency Injection - Module initialization
- 🎭 Event System - Event-driven loading