Skip to main content

🎯 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