armor 1.0.0
armor: ^1.0.0 copied to clipboard
Bulletproof protection for Flutter apps. Automatically handles crashes, errors, and failures with graceful recovery.
π‘οΈ Armor #
Bulletproof protection for Flutter apps. Automatically handles crashes, errors, and failures with graceful recovery.
β¨ Features #
- π« Prevents crashes - No more red error screens
- π Automatic recovery - Graceful fallbacks for failed operations
- π Render overflow protection - Eliminates yellow/black overflow stripes
- π Network error handling - Retry logic with intelligent fallbacks
- πΌοΈ Safe image loading - Automatic placeholder and error widgets
- β‘ Performance monitoring - Built-in performance tracking
- π§Ή Resource management - Automatic cleanup of timers and subscriptions
- π― Zero configuration - Works out of the box
π Quick Start #
1. Add Armor to your pubspec.yaml #
dependencies:
armor: ^1.0.0
2. Initialize Armor (one-time setup) #
import 'package:armor/armor.dart';
void main() {
ArmorInterceptor.initialize(); // Enable global error protection
runApp(MyApp());
}
3. Add ArmorMixin to your widgets #
import 'package:armor/armor.dart';
class MyWidget extends StatefulWidget {
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with ArmorMixin {
// Your widget is now protected against all common Flutter errors!
@override
Widget build(BuildContext context) {
return safeBuild(
() => YourActualWidget(),
fallback: Text('Content temporarily unavailable'),
);
}
}
That's it! Your app is now armored against crashes. π
π‘ Core Methods #
safeExecute()
- Execute any code safely #
final name = safeExecute(
() => userData!['name'] as String,
fallback: 'Unknown User',
cacheKey: 'user_name',
);
safeSetState()
- Prevent setState after dispose #
safeSetState(() {
counter++;
});
safeAsync()
- Safe async operations #
final result = await safeAsync(() async {
final data = await api.fetchData();
safeSetState(() => this.data = data);
return data;
});
safeBuild()
- Build widgets with fallback UI #
return safeBuild(
() => ComplexWidget(data: complexData),
fallback: Text('Content temporarily unavailable'),
);
armorImage()
- Load images safely #
armorImage(
'https://example.com/image.jpg',
width: 100,
height: 100,
cacheKey: 'profile_image',
)
armorNetworkRequest()
- Network calls with retry #
final data = await armorNetworkRequest(
() => api.fetchUserData(),
fallback: cachedData,
maxRetries: 3,
cacheKey: 'user_data',
);
armorTimer()
- Managed timers #
armorTimer(
Duration(seconds: 5),
() => safeSetState(() => showMessage = false),
key: 'hide_message_timer',
);
π― What Armor Protects Against #
Error Type | Example | Armor Solution |
---|---|---|
Null Safety | userData!['name'] |
Returns fallback value |
setState after dispose | Timer callbacks | Automatically prevented |
Render Overflow | Yellow/black stripes | Responsive fallback layouts |
Image Loading | Broken image URLs | Automatic placeholder widgets |
Network Failures | API timeouts | Retry logic + cached fallbacks |
Platform Errors | Native method calls | Graceful error handling |
File Operations | Permission denied | Safe file access |
Memory Leaks | Undisposed timers | Automatic resource cleanup |
π Real-World Example #
Here's a complete example showing Armor protecting a user profile widget:
class UserProfile extends StatefulWidget {
final Map<String, dynamic>? userData;
const UserProfile({Key? key, this.userData}) : super(key: key);
@override
State<UserProfile> createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> with ArmorMixin {
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: safeBuild(
() => _buildUserContent(),
fallback: _buildPlaceholder(),
),
),
);
}
Widget _buildUserContent() {
// These would normally crash with null data, but Armor provides fallbacks
final name = safeExecute(
() => widget.userData!['name'] as String,
fallback: 'Unknown User',
cacheKey: 'user_name',
);
final email = safeExecute(
() => widget.userData!['email'] as String,
fallback: 'no-email@example.com',
cacheKey: 'user_email',
);
return Column(
children: [
armorImage(
widget.userData?['avatar_url'] ?? '',
width: 80,
height: 80,
cacheKey: 'user_avatar',
),
SizedBox(height: 16),
Text(name!, style: Theme.of(context).textTheme.headlineSmall),
Text(email!, style: Theme.of(context).textTheme.bodyMedium),
],
);
}
Widget _buildPlaceholder() {
return Column(
children: [
CircleAvatar(
radius: 40,
backgroundColor: Colors.grey[300],
child: Icon(Icons.person, size: 40),
),
SizedBox(height: 16),
Container(
height: 20,
width: 120,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(4),
),
),
],
);
}
}
π Monitoring & Analytics #
Armor provides built-in monitoring to track protection effectiveness:
// Get protection statistics
final stats = getArmorStats();
print('Cached values: ${stats['cached_values']}');
print('Active timers: ${stats['active_timers']}');
// Monitor global error interception
final interceptor = ArmorInterceptor.instance;
print('Total errors intercepted: ${interceptor.totalIntercepted}');
print('Total errors healed: ${interceptor.totalHealed}');
print('Heal rate: ${(interceptor.healRate * 100).toStringAsFixed(1)}%');
// Listen to error stream for analytics
ArmorInterceptor.instance.errorStream.listen((error) {
// Send to your analytics service
analytics.recordError(error);
});
π§ Advanced Configuration #
Custom Error Handling #
// Listen to all intercepted errors
ArmorInterceptor.instance.errorStream.listen((error) {
if (error.wasHealed) {
print('β
Armor healed: ${error.error}');
} else {
print('β Unhealed error: ${error.error}');
}
});
Performance Monitoring #
final result = armorPerformanceOperation(
() => expensiveComputation(),
operationName: 'data_processing',
warningThreshold: Duration(milliseconds: 500),
);
Cache Management #
// Clear cache when needed (e.g., user logout)
clearArmorCache();
// Check cache size
final stats = getArmorStats();
if (stats['cached_values'] > 1000) {
clearArmorCache();
}
ποΈ Architecture #
Armor consists of three main components:
- ArmorMixin - The main protection system for widgets
- ArmorInterceptor - Global error catching and healing
- ArmorRegistry - Tracking and statistics for healed errors
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β Your Widget β β ArmorMixin β β ArmorInterceptorβ
β βββββΆβ (Protection) βββββΆβ (Global Errors) β
βββββββββββββββββββ ββββββββββββββββββββ βββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββ βββββββββββββββββββ
β Safe Operations β β ArmorRegistry β
β (Fallbacks) β β (Statistics) β
ββββββββββββββββββββ βββββββββββββββββββ
π§ͺ Testing #
Armor includes comprehensive test coverage and provides utilities for testing:
// Test your armored widgets
testWidgets('should recover from null data error', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: UserProfile(userData: null), // Null data
),
);
// Should show placeholder instead of crashing
expect(find.byIcon(Icons.person), findsOneWidget);
});
π¦ Migration Guide #
From Manual Error Handling #
// Before: Manual try-catch everywhere
try {
setState(() => counter++);
} catch (e) {
print('Error: $e');
}
// After: Armor handles it automatically
safeSetState(() => counter++);
From Other Error Packages #
// Before: Complex setup
SomeErrorPackage.configure(/* lots of config */);
// After: One line setup
ArmorInterceptor.initialize();
π Best Practices #
- Always use ArmorMixin for StatefulWidgets
- Provide meaningful fallbacks instead of generic error messages
- Use cache keys for expensive operations
- Monitor error statistics to identify problem areas
- Clear cache periodically to prevent memory growth
β FAQ #
Q: Does Armor add performance overhead? A: Minimal. Armor uses efficient error handling and caching strategies. Performance monitoring is built-in to detect any issues.
Q: Can I use Armor with other error handling packages? A: Yes! Armor works alongside other packages and can complement existing error handling.
Q: What happens to errors that can't be healed? A: They're logged for monitoring but won't crash your app. In debug mode, you'll still see them in the console.
Q: Is Armor production-ready? A: Absolutely! Armor is designed for production use with comprehensive testing and proven patterns.
π€ Contributing #
We welcome contributions! Please see our Contributing Guide for details.
π License #
This project is licensed under the MIT License - see the LICENSE file for details.
π Links #
- Documentation
- Example App
- Issue Tracker
- Changelog
Made with β€οΈ for the Flutter community
Armor up your Flutter apps! π‘οΈ