π‘οΈ 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
Made with β€οΈ for the Flutter community
Armor up your Flutter apps! π‘οΈ
Libraries
- armor
- Armor - Bulletproof protection for Flutter apps