WDynamic
WDynamic renders a Flutter widget tree from a JSON/Map configuration at runtime, with built-in action handling and form state management.
- Basic Usage
- Constructor
- Props
- JSON Schema
- Action Handling
- State Management
- Custom Builders
- Security
- Error Handling
- Related Documentation
WDynamic(
json: const {
'type': 'WDiv',
'props': {
'className': 'p-6 bg-white rounded-xl shadow-sm'
},
'children': [
{
'type': 'WText',
'props': {
'text': 'Hello from JSON!',
'className': 'text-xl font-bold text-gray-800'
}
},
],
},
)
Basic Usage
The WDynamic widget converts JSON/Map structures into live Flutter widgets. This enables server-driven UI, dynamic form generation, and runtime-configurable layouts without rebuilding your app.
WDynamic(
json: const {
'type': 'WDiv',
'props': {'className': 'flex gap-4 p-4'},
'children': [
{
'type': 'WButton',
'props': {
'className': 'bg-blue-500 text-white px-4 py-2 rounded',
'onTap': {'action': 'submit'},
},
'children': [
{'type': 'WText', 'props': {'text': 'Submit'}}
],
}
],
},
actions: {
'submit': (args) => print('Button tapped'),
},
)
Constructor
const WDynamic({
Key? key,
required Map json,
Map actions = const {},
WDynamicController? controller,
Set? denyWidgets,
Map? builders,
Map? customIcons,
int maxDepth = 50,
Widget Function(String type, Object error)? onError,
Widget Function(String type, Map props)? onUnknownWidget,
})
Props
| Prop | Type | Default | Description |
|---|---|---|---|
json |
Map |
Required | JSON configuration defining the widget tree. Must include type, optional props and children. |
actions |
Map |
{} |
Action handlers keyed by name. Called when interactive widgets trigger events. |
controller |
WDynamicController? |
null |
Optional controller for external state access (read/write form values). |
denyWidgets |
Set |
null |
Widget types to remove from the default whitelist. |
builders |
Map |
null |
Custom widget builders keyed by type name. Signature: Widget Function(Map. |
customIcons |
Map |
null |
Custom icon mappings for WIcon widgets. Keys are icon names used in the JSON icon prop, values are IconData instances. Extends the default Material icon map. |
maxDepth |
int |
50 |
Maximum recursion depth for nested widgets. Prevents infinite loops. |
onError |
Widget Function(String, Object)? |
null |
Error handler for widget build failures. Returns a fallback widget. |
onUnknownWidget |
Widget Function(String, Map)? |
null |
Handler for unknown or denied widget types. Returns a fallback widget. |
JSON Schema
Every widget node in the JSON structure follows this format:
{
'type': 'WidgetType', // Required: Wind widget, Flutter widget, or custom builder
'props': { // Optional: properties passed to the widget
'className': 'flex gap-4',
'text': 'Hello',
'onTap': {'action': 'handleTap', 'args': {'id': 1}},
'id': 'username', // Auto-tracked form state key
},
'children': [ // Optional: nested widget array
{ /* child node */ }
]
}
Type Field
The type field determines which widget to render:
- Wind Widgets:
WDiv,WText,WButton,WInput,WCheckbox,WSelect,WDatePicker,WIcon,WImage,WSvg,WPopover,WAnchor,WSpacer - Flutter Widgets:
Column,Row,Center,SizedBox,Expanded,Container,Wrap,Stack,Positioned,Padding,Align,Opacity,AspectRatio,FittedBox,ClipRRect,Spacer - Custom Widgets: Any key from the
buildersmap
Props Field
Widget properties are passed directly to the constructor. Common props include:
{
'type': 'WDiv',
'props': {
'className': 'flex gap-4 p-6 bg-white rounded-xl', // Wind utilities
'id': 'formContainer', // Auto-tracked state key
}
}
{
'type': 'WText',
'props': {
'text': 'Welcome!', // Text content
'className': 'text-lg font-bold', // Wind utilities
}
}
{
'type': 'WInput',
'props': {
'id': 'email', // Auto-tracked in controller state
'placeholder': 'Enter email...',
'className': 'border rounded-lg',
}
}
Children Field
An array of nested widget nodes. Used by layout widgets like WDiv, Column, Row, etc.
{
'type': 'WDiv',
'props': {'className': 'flex gap-2'},
'children': [
{'type': 'WText', 'props': {'text': 'First'}},
{'type': 'WText', 'props': {'text': 'Second'}},
]
}
Action Handling
Interactive widgets like WButton can trigger actions defined in the actions map. Actions are referenced in the JSON using an object format.
Action Format
{
'type': 'WButton',
'props': {
'onTap': {
'action': 'actionName', // Required: key from actions map
'args': {'key': 'value'} // Optional: arguments passed to handler
}
}
}
Action Signatures
Actions can accept arguments only, or both arguments and state:
// Simple: (Map args)
WDynamic(
actions: {
'showAlert': (Map args) {
print('Alert: ${args['message']}');
},
},
)
// With state access: (Map args, WDynamicState state)
WDynamic(
actions: {
'submitForm': (Map args, WDynamicState state) {
final email = state.get('email');
final password = state.get('password');
print('Submitting: $email');
},
},
)
Example
WDynamic(
json: const {
'type': 'WButton',
'props': {
'className': 'bg-blue-500 text-white px-4 py-2 rounded',
'onTap': {
'action': 'increment',
'args': {'step': 1}
},
},
'children': [
{'type': 'WText', 'props': {'text': 'Tap Me'}}
],
},
actions: {
'increment': (Map args, WDynamicState state) {
final step = args['step'] ?? 1;
setState(() => count += step);
},
},
)
State Management
WDynamic automatically tracks form widget values using the id prop. Values are stored in an internal state object accessible via WDynamicController or action handlers.
Auto-Tracking with ID
Any widget with an id prop automatically stores its value in state:
{
'type': 'WInput',
'props': {
'id': 'username', // Auto-tracked
'placeholder': 'Username...',
}
}
Using the Controller
Create a WDynamicController to access state externally:
final controller = WDynamicController();
// Pre-fill values
controller.setValue('email', '[email protected]');
// Read values
final email = controller.getValue('email');
final allValues = controller.getAll();
// Listen for changes
final dispose = controller.addListener('email', (value) {
print('Email changed: $value');
});
// Pass to WDynamic
WDynamic(
controller: controller,
json: myJson,
)
// Clean up
controller.dispose();
Accessing State in Actions
Action handlers receive the state object as a second parameter:
WDynamic(
json: const {
'type': 'WDiv',
'children': [
{
'type': 'WInput',
'props': {'id': 'name', 'placeholder': 'Your name...'}
},
{
'type': 'WButton',
'props': {
'onTap': {'action': 'greet'},
'className': 'bg-green-500 text-white px-4 py-2 rounded',
},
'children': [
{'type': 'WText', 'props': {'text': 'Greet'}}
],
}
],
},
actions: {
'greet': (args, state) {
final name = state.get('name') ?? 'World';
print('Hello, $name!');
},
},
)
Custom Builders
Extend WDynamic with custom widget types using the builders prop.
WWidgetBuilder Signature
typedef WWidgetBuilder = Widget Function(
Map props,
List children,
);
Example
WDynamic(
json: const {
'type': 'InfoCard',
'props': {
'title': 'Custom Widget',
'subtitle': 'Built with a custom builder'
},
},
builders: {
'InfoCard': (Map props, List children) {
return WDiv(
className: 'p-4 bg-blue-50 rounded-xl border border-blue-200',
child: WDiv(
className: 'flex items-center gap-3',
children: [
WIcon(Icons.info, className: 'text-blue-500 text-2xl'),
WDiv(
className: 'flex flex-col',
children: [
WText(props['title'] ?? '', className: 'font-bold text-blue-800'),
WText(props['subtitle'] ?? '', className: 'text-sm text-blue-600'),
],
),
],
),
);
},
},
)
Custom builders have priority over default widgets. You can override built-in widgets by providing a builder with the same type name.
Custom Icons
Map string icon names to IconData for use in JSON WIcon widget nodes. Custom icons extend (not replace) the built-in Material icon map.
WDynamic(
json: const {
'type': 'WDiv',
'props': {'className': 'flex gap-4 items-center'},
'children': [
{
'type': 'WIcon',
'props': {'icon': 'heart', 'className': 'text-red-500 text-2xl'},
},
{
'type': 'WIcon',
'props': {'icon': 'star', 'className': 'text-yellow-500 text-2xl'},
},
],
},
customIcons: {
'heart': Icons.favorite,
'star': Icons.star,
'settings': Icons.settings,
},
)
Security
WDynamic uses a whitelist approach for security. Only explicitly allowed widget types can be rendered.
Default Whitelist
Wind Widgets:
WDiv,WText,WButton,WImage,WIcon,WAnchor,WInput,WCheckbox,WSvg,WSelect,WPopover,WDatePicker,WSpacer
Flutter Widgets:
Column,Row,Center,SizedBox,Expanded,Container,Wrap,Stack,Positioned,Padding,Align,Opacity,AspectRatio,FittedBox,ClipRRect,Spacer
Denying Widgets
Remove specific widget types from the whitelist:
WDynamic(
json: myJson,
denyWidgets: const {'WButton', 'WInput'}, // Block buttons and inputs
)
Unknown Widget Handler
Provide a fallback for unknown or denied widget types:
WDynamic(
json: myJson,
onUnknownWidget: (String type, Map props) {
return WDiv(
className: 'p-2 bg-red-100 border border-red-300 rounded',
child: WText('Unknown widget: $type', className: 'text-red-700 text-sm'),
);
},
)
Error Handling
Handle widget build failures gracefully with the onError callback.
WDynamic(
json: myJson,
onError: (String type, Object error) {
return WDiv(
className: 'p-4 bg-yellow-50 border border-yellow-300 rounded',
child: WDiv(
className: 'flex flex-col gap-2',
children: [
WText('Error rendering $type', className: 'font-bold text-yellow-800'),
WText(error.toString(), className: 'text-sm text-yellow-700'),
],
),
);
},
)
Max Depth Protection
Prevent infinite recursion with the maxDepth prop (default: 50):
WDynamic(
json: deeplyNestedJson,
maxDepth: 100, // Allow deeper nesting
)
When max depth is exceeded, rendering stops and returns an empty SizedBox.shrink() or calls onError if provided.
Related Documentation
- WDiv - Primary layout container
- WButton - Interactive button widget
- WInput - Text input widget
- WSelect - Dropdown select widget
- State Management - State handling in Wind
- Dynamic Rendering - Server-driven UI concepts