Skip to main content

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โ€‹

PropertyTypeDefaultDescription
childWidgetrequiredThe child widget to wrap with stateful behavior
onInitFunction()?nullCallback executed during initState lifecycle
onDisposeFunction()?nullCallback executed during dispose lifecycle
didChangeDependenciesFunction()?nullCallback 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โ€‹

  1. Resource Management: Always clean up resources in onDispose to prevent memory leaks
  2. Null Safety: Use null-aware operators when calling optional callbacks
  3. Performance: Keep lifecycle callbacks lightweight and avoid heavy computations
  4. Error Handling: Wrap lifecycle callbacks in try-catch blocks for robust error handling
  5. Async Operations: Use proper async/await patterns in lifecycle callbacks
  6. 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

Migration Notesโ€‹

When migrating from StatefulWidget to StatefullWrapperAppi:

  1. Move initState logic to onInit callback
  2. Move dispose logic to onDispose callback
  3. Move didChangeDependencies logic to didChangeDependencies callback
  4. Remove the State class and extend StatelessWidget instead
  5. 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!