import 'package:flutter/material.dart'; import 'package:infinite_lazy_grid/infinite_lazy_grid.dart'; /// Demo showing different approaches to handle dynamic widget inputs class DynamicWidgetExample extends StatefulWidget { const DynamicWidgetExample({super.key}); @override State createState() => _DynamicWidgetExampleState(); } class _DynamicWidgetExampleState extends State { final LazyCanvasController controller = LazyCanvasController(debug: false, buildCacheExtent: const Offset(50, 50)); // Store child IDs for later reference late CanvasChildId selfManagedId; late CanvasChildId externalDataId; late CanvasChildId reactiveId; // Simulate some dynamic data int counter = 0; String message = "Hello World"; Color selectedColor = Colors.blue; @override void initState() { super.initState(); _setupWidgets(); } void _setupWidgets() { // Approach 1: Using StatefulWidget with state inside the widget getting lost if unmounted selfManagedId = controller.addChild(const Offset(0, 0), SelfManagedCounterWidget(label: "Self-Managed")); // Approach 2: Using updateChildWidget, you need this since the children dep tree is direcly managed by the controller externalDataId = controller.addChild( const Offset(200, 0), ExternalDataWidget(counter: counter, message: message, color: selectedColor), ); // Approach 3: Using ValueListenableBuilder for reactive updates reactiveId = controller.addChild( const Offset(400, 0), ValueListenableBuilder( valueListenable: _counterNotifier, builder: (context, count, child) { return Container( width: 120, height: 80, decoration: BoxDecoration( color: Colors.green, border: Border.all(color: Colors.black, width: 2), borderRadius: BorderRadius.circular(8), ), child: Center( child: Text( 'Reactive: $count', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), ), ); }, ), ); } final ValueNotifier _counterNotifier = ValueNotifier(0); void _updateExternalData() { setState(() { counter++; message = "Updated $counter times"; selectedColor = Colors.primaries[counter % Colors.primaries.length]; }); // Approach 2: Update the widget when external data changes // Use ValueKey to ensure Flutter recognizes this as a different widget controller.updateChildWidget( externalDataId, // Use the stored widget ID ExternalDataWidget( // key: ValueKey('external_$counter'), // Force rebuild with unique key counter: counter, message: message, color: selectedColor, ), ); } void _updateReactiveWidget() { _counterNotifier.value++; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Widget State Updates')), body: Column( children: [ Container( padding: const EdgeInsets.all(16), color: Theme.of(context).colorScheme.surfaceContainerLowest, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Three approaches for state updates:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), const Text('1. Self-managed StatefulWidget (left), loses state on unmount'), const Text('2. External data + updateChildWidget (center) - manual updates'), const Text('3. ValueListenableBuilder (right) - reactive updates'), const SizedBox(height: 16), Row( children: [ ElevatedButton(onPressed: _updateExternalData, child: Text('Update External Data ($counter)')), const SizedBox(width: 16), ElevatedButton( onPressed: _updateReactiveWidget, child: Text('Update Reactive Widget (${_counterNotifier.value})'), ), ], ), ], ), ), Expanded(child: LazyCanvas(controller: controller)), ], ), ); } @override void dispose() { _counterNotifier.dispose(); super.dispose(); } } /// Approach 1: StatefulWidget that manages its own state class SelfManagedCounterWidget extends StatefulWidget { final String label; const SelfManagedCounterWidget({super.key, required this.label}); @override State createState() => _SelfManagedCounterWidgetState(); } class _SelfManagedCounterWidgetState extends State { int _count = 0; @override Widget build(BuildContext context) { return GestureDetector( onTap: () { setState(() { _count++; }); }, child: Container( width: 120, height: 80, decoration: BoxDecoration( color: Colors.purple, border: Border.all(color: Colors.black, width: 2), borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(widget.label, style: const TextStyle(color: Colors.white, fontSize: 10)), Text( 'Count: $_count', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), const Text('(Tap me)', style: TextStyle(color: Colors.white70, fontSize: 10)), ], ), ), ); } } /// Approach 2: Widget that depends on external data class ExternalDataWidget extends StatelessWidget { final int counter; final String message; final Color color; const ExternalDataWidget({super.key, required this.counter, required this.message, required this.color}); @override Widget build(BuildContext context) { return Container( width: 120, height: 80, decoration: BoxDecoration( color: color, border: Border.all(color: Colors.black, width: 2), borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('External', style: TextStyle(color: Colors.white, fontSize: 10)), Text( 'Count: $counter', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12), ), Text( message, style: const TextStyle(color: Colors.white, fontSize: 8), textAlign: TextAlign.center, maxLines: 2, overflow: TextOverflow.ellipsis, ), ], ), ); } }