StatefullWrapperAppi
A powerful utility widget that wraps any child widget with stateful lifecycle management, allowing you to add initialization, disposal, and dependency change callbacks to stateless widgets without converting them to stateful widgets.
Overviewโ
StatefullWrapperAppi
provides a clean and efficient way to add stateful behavior to stateless widgets. It acts as a wrapper that manages the widget lifecycle and provides hooks for initialization, cleanup, and dependency changes, making it perfect for scenarios where you need stateful behavior without the overhead of creating a full stateful widget.
Featuresโ
- ๐ Lifecycle Management - Access to initState, dispose, and didChangeDependencies
- ๐ฏ Non-intrusive - Wrap any widget without modifying its structure
- ๐งน Automatic Cleanup - Proper disposal handling for resources
- ๐ฆ Lightweight - Minimal overhead and memory footprint
- ๐ง Flexible Callbacks - Optional callback functions for different lifecycle events
- ๐ช Dependency Tracking - React to dependency changes in the widget tree
- โก Performance Optimized - Efficient lifecycle management
- ๐ก๏ธ Null Safety - Full null safety support with optional callbacks
- ๐จ Transparent Rendering - Child widget renders exactly as it would normally
- ๐ Easy Integration - Drop-in replacement for complex stateful logic
Basic Usageโ
StatefullWrapperAppi(
onInit: () {
print('Widget initialized');
},
onDispose: () {
print('Widget disposed');
},
child: Text('Hello World'),
)
Propertiesโ
Property | Type | Default | Description |
---|---|---|---|
child | Widget | required | The child widget to wrap with stateful behavior |
onInit | Function()? | null | Callback executed during initState lifecycle |
onDispose | Function()? | null | Callback executed during dispose lifecycle |
didChangeDependencies | Function()? | null | Callback executed when dependencies change |
Examplesโ
Basic Initialization and Cleanupโ
class TimerDisplay extends StatelessWidget {
Widget build(BuildContext context) {
return StatefullWrapperAppi(
onInit: () {
print('Timer display initialized');
// Initialize any resources here
},
onDispose: () {
print('Timer display disposed');
// Clean up resources here
},
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue[200]!),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.timer, color: Colors.blue),
SizedBox(height: 8),
Text(
'Timer Component',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue[800],
),
),
Text(
'Initialized with lifecycle management',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
);
}
}
API Service Integrationโ
class UserProfileWidget extends StatelessWidget {
final String userId;
final Function(Map<String, dynamic>) onUserLoaded;
const UserProfileWidget({
Key? key,
required this.userId,
required this.onUserLoaded,
}) : super(key: key);
Widget build(BuildContext context) {
return StatefullWrapperAppi(
onInit: () async {
// Fetch user data when widget initializes
try {
final userData = await ApiService.fetchUser(userId);
onUserLoaded(userData);
} catch (e) {
print('Error loading user: $e');
}
},
onDispose: () {
// Cancel any ongoing requests
ApiService.cancelUserRequests(userId);
},
didChangeDependencies: () {
// React to dependency changes (e.g., theme, locale)
print('Dependencies changed for user $userId');
},
child: Card(
elevation: 4,
margin: EdgeInsets.all(16),
child: Padding(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 30,
backgroundColor: Colors.blue[100],
child: Icon(Icons.person, size: 30, color: Colors.blue),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'User Profile',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
'ID: $userId',
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
),
],
),
),
],
),
SizedBox(height: 16),
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green[200]!),
),
child: Row(
children: [
Icon(Icons.check_circle, color: Colors.green, size: 20),
SizedBox(width: 8),
Text(
'Profile data will load automatically',
style: TextStyle(
color: Colors.green[800],
fontSize: 12,
),
),
],
),
),
],
),
),
),
);
}
}
Stream Subscription Managementโ
class NotificationListener extends StatelessWidget {
final Function(String) onNotificationReceived;
const NotificationListener({
Key? key,
required this.onNotificationReceived,
}) : super(key: key);
Widget build(BuildContext context) {
StreamSubscription<String>? notificationSubscription;
return StatefullWrapperAppi(
onInit: () {
// Subscribe to notifications when widget initializes
notificationSubscription = NotificationService.stream.listen(
(notification) {
onNotificationReceived(notification);
},
onError: (error) {
print('Notification error: $error');
},
);
print('Notification listener started');
},
onDispose: () {
// Clean up subscription when widget is disposed
notificationSubscription?.cancel();
print('Notification listener stopped');
},
didChangeDependencies: () {
// Handle dependency changes if needed
print('Notification listener dependencies changed');
},
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple[50]!, Colors.white],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.2),
spreadRadius: 2,
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.notifications_active,
color: Colors.purple,
size: 32,
),
SizedBox(height: 12),
Text(
'Notification Listener',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.purple[800],
),
),
SizedBox(height: 4),
Text(
'Listening for real-time notifications',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
SizedBox(height: 12),
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.purple[100],
borderRadius: BorderRadius.circular(16),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
SizedBox(width: 6),
Text(
'Active',
style: TextStyle(
color: Colors.purple[800],
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
);
}
}
Analytics Trackingโ
class AnalyticsTracker extends StatelessWidget {
final String screenName;
final Map<String, dynamic>? additionalData;
const AnalyticsTracker({
Key? key,
required this.screenName,
this.additionalData,
}) : super(key: key);
Widget build(BuildContext context) {
DateTime? screenStartTime;
return StatefullWrapperAppi(
onInit: () {
// Track screen view when widget initializes
screenStartTime = DateTime.now();
AnalyticsService.trackScreenView(
screenName: screenName,
additionalData: additionalData,
);
print('Analytics: Screen view tracked for $screenName');
},
onDispose: () {
// Track screen exit and duration when widget is disposed
if (screenStartTime != null) {
final duration = DateTime.now().difference(screenStartTime!);
AnalyticsService.trackScreenExit(
screenName: screenName,
duration: duration,
);
print('Analytics: Screen exit tracked for $screenName (${duration.inSeconds}s)');
}
},
didChangeDependencies: () {
// Track dependency changes if relevant for analytics
AnalyticsService.trackEvent(
'screen_dependency_change',
{'screen': screenName},
);
},
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.orange[50],
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange[200]!),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(Icons.analytics, color: Colors.orange),
SizedBox(width: 8),
Text(
'Analytics Tracker',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.orange[800],
),
),
],
),
SizedBox(height: 8),
Text(
'Tracking: $screenName',
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
if (additionalData != null) ...[
SizedBox(height: 4),
Text(
'Additional data: ${additionalData!.keys.join(', ')}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
],
),
),
);
}
}
Resource Managerโ
class ResourceManager extends StatelessWidget {
final List<String> resourceUrls;
final Function(List<String>) onResourcesLoaded;
const ResourceManager({
Key? key,
required this.resourceUrls,
required this.onResourcesLoaded,
}) : super(key: key);
Widget build(BuildContext context) {
List<String> loadedResources = [];
return StatefullWrapperAppi(
onInit: () async {
// Preload resources when widget initializes
try {
for (String url in resourceUrls) {
await ResourceService.preloadResource(url);
loadedResources.add(url);
}
onResourcesLoaded(loadedResources);
print('All resources loaded successfully');
} catch (e) {
print('Error loading resources: $e');
}
},
onDispose: () {
// Clean up cached resources when widget is disposed
for (String url in loadedResources) {
ResourceService.clearCache(url);
}
print('Resource cache cleared');
},
child: Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.teal[50]!, Colors.white],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.teal[200]!),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Icon(Icons.storage, color: Colors.teal),
SizedBox(width: 8),
Text(
'Resource Manager',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.teal[800],
),
),
],
),
SizedBox(height: 8),
Text(
'Managing ${resourceUrls.length} resources',
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
SizedBox(height: 12),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.teal[100],
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.cloud_download, size: 16, color: Colors.teal[800]),
SizedBox(width: 6),
Text(
'Auto-loading resources',
style: TextStyle(
fontSize: 12,
color: Colors.teal[800],
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
),
);
}
}
Form Validation Setupโ
class FormValidationWrapper extends StatelessWidget {
final Widget formChild;
final GlobalKey<FormState> formKey;
final Function()? onFormReady;
const FormValidationWrapper({
Key? key,
required this.formChild,
required this.formKey,
this.onFormReady,
}) : super(key: key);
Widget build(BuildContext context) {
return StatefullWrapperAppi(
onInit: () {
// Setup form validation when widget initializes
WidgetsBinding.instance.addPostFrameCallback((_) {
onFormReady?.call();
});
print('Form validation setup complete');
},
onDispose: () {
// Clean up form state when widget is disposed
formKey.currentState?.reset();
print('Form state cleaned up');
},
didChangeDependencies: () {
// Handle form dependency changes
print('Form dependencies changed');
},
child: Form(
key: formKey,
child: Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 2,
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.assignment, color: Colors.blue),
SizedBox(width: 8),
Text(
'Form with Lifecycle Management',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue[800],
),
),
],
),
SizedBox(height: 16),
formChild,
SizedBox(height: 12),
Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.blue[50],
borderRadius: BorderRadius.circular(6),
),
child: Row(
children: [
Icon(Icons.info, size: 16, color: Colors.blue),
SizedBox(width: 6),
Text(
'Form automatically manages validation lifecycle',
style: TextStyle(
fontSize: 12,
color: Colors.blue[800],
),
),
],
),
),
],
),
),
),
);
}
}
Advanced Featuresโ
Lifecycle Event Chainingโ
class ChainedLifecycleWidget extends StatelessWidget {
Widget build(BuildContext context) {
return StatefullWrapperAppi(
onInit: () {
print('1. Widget initialized');
_setupInitialState();
_registerEventListeners();
_loadInitialData();
},
didChangeDependencies: () {
print('2. Dependencies changed');
_handleDependencyChanges();
_updateThemeBasedSettings();
},
onDispose: () {
print('3. Widget disposed');
_cleanupResources();
_unregisterEventListeners();
_saveState();
},
child: Container(
padding: EdgeInsets.all(16),
child: Text('Chained Lifecycle Management'),
),
);
}
void _setupInitialState() {
// Initialize component state
}
void _registerEventListeners() {
// Register event listeners
}
void _loadInitialData() {
// Load initial data
}
void _handleDependencyChanges() {
// Handle dependency changes
}
void _updateThemeBasedSettings() {
// Update theme-based settings
}
void _cleanupResources() {
// Clean up resources
}
void _unregisterEventListeners() {
// Unregister event listeners
}
void _saveState() {
// Save current state
}
}
Conditional Lifecycle Executionโ
class ConditionalLifecycleWidget extends StatelessWidget {
final bool enableAnalytics;
final bool enableCaching;
const ConditionalLifecycleWidget({
Key? key,
this.enableAnalytics = true,
this.enableCaching = false,
}) : super(key: key);
Widget build(BuildContext context) {
return StatefullWrapperAppi(
onInit: () {
if (enableAnalytics) {
AnalyticsService.trackEvent('widget_initialized');
}
if (enableCaching) {
CacheService.initializeCache();
}
},
onDispose: () {
if (enableAnalytics) {
AnalyticsService.trackEvent('widget_disposed');
}
if (enableCaching) {
CacheService.clearCache();
}
},
child: Container(
padding: EdgeInsets.all(16),
child: Text('Conditional Lifecycle Widget'),
),
);
}
}
Best Practicesโ
- Resource Management: Always clean up resources in
onDispose
to prevent memory leaks - Null Safety: Use null-aware operators when calling optional callbacks
- Performance: Keep lifecycle callbacks lightweight and avoid heavy computations
- Error Handling: Wrap lifecycle callbacks in try-catch blocks for robust error handling
- Async Operations: Use proper async/await patterns in lifecycle callbacks
- State Management: Don't rely on StatefullWrapperAppi for complex state management
Common Use Casesโ
- API Integration: Initialize API calls and clean up subscriptions
- Analytics Tracking: Track screen views and user interactions
- Resource Management: Preload and cache resources
- Event Listeners: Register and unregister event listeners
- Form Management: Setup and cleanup form validation
- Timer Management: Start and stop timers or periodic tasks
- Stream Subscriptions: Manage stream subscriptions lifecycle
- Cache Management: Initialize and clear caches
- Theme Updates: React to theme and locale changes
- Performance Monitoring: Track widget performance metrics
Related Widgetsโ
- HydratedAppi - For state persistence with lifecycle management
- BoxAppi - For container widgets with interactions
- AnimatedBoxAppi - For animated containers
Migration Notesโ
When migrating from StatefulWidget to StatefullWrapperAppi:
- Move
initState
logic toonInit
callback - Move
dispose
logic toonDispose
callback - Move
didChangeDependencies
logic todidChangeDependencies
callback - Remove the State class and extend StatelessWidget instead
- Wrap your build method's return widget with StatefullWrapperAppi
Technical Notesโ
- Minimal performance overhead compared to full StatefulWidget
- Callbacks are executed in the correct lifecycle order
- Null safety ensures optional callbacks don't cause runtime errors
- Compatible with all Flutter widgets and frameworks
- Works seamlessly with state management solutions like Riverpod, Provider, etc.
Ready to add stateful behavior to your stateless widgets? StatefullWrapperAppi makes it simple and efficient!