NodeFlowEditor
Complete API reference for the NodeFlowEditor widget
NodeFlowEditor
The NodeFlowEditor is the main widget for creating interactive node-based flow editors. It provides a full-featured canvas with support for nodes, connections, panning, zooming, and more.
Constructor
NodeFlowEditor<T>({
Key? key,
required NodeFlowController<T> controller,
required Widget Function(BuildContext, Node<T>) nodeBuilder,
NodeFlowTheme? theme,
NodeFlowEvents<T>? events,
NodeFlowConfig? config,
})Required Parameters
controller
required NodeFlowController<T> controllerThe controller that manages the graph state. Create it in your widget's state:
late final NodeFlowController<MyData> controller;
@override
void initState() {
super.initState();
controller = NodeFlowController<MyData>();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}nodeBuilder
required Widget Function(BuildContext, Node<T>) nodeBuilderA function that builds the widget for each node. This is where you customize how nodes appear:
nodeBuilder: (context, node) {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue),
),
child: Text(node.data.label),
);
}Optional Parameters
theme
NodeFlowTheme? themeCustomizes the visual appearance of the editor. See Theming.
theme: NodeFlowTheme(
nodeTheme: NodeTheme.light,
connectionStyle: ConnectionStyles.smoothstep,
backgroundColor: Colors.grey[50]!,
gridStyle: GridStyle.dots,
gridColor: Colors.grey[300]!,
)events
NodeFlowEvents<T>? eventsComprehensive event handling for all editor interactions. See Event System for complete documentation.
events: NodeFlowEvents(
node: NodeEvents(
onTap: (node) => print('Tapped: ${node.id}'),
onDoubleTap: (node) => _editNode(node),
onSelected: (node) => setState(() => _selected = node),
onDragStop: (node) => _savePosition(node),
onContextMenu: (node, pos) => _showMenu(node, pos),
),
)events: NodeFlowEvents(
connection: ConnectionEvents(
onCreated: (conn) => print('Connected: ${conn.id}'),
onDeleted: (conn) => print('Disconnected: ${conn.id}'),
onBeforeComplete: (context) => _validateConnection(context),
),
)events: NodeFlowEvents(
viewport: ViewportEvents(
onCanvasTap: (pos) => _addNodeAt(pos),
onCanvasContextMenu: (pos) => _showCanvasMenu(pos),
onMove: (viewport) => _updateMinimap(viewport),
),
)config
NodeFlowConfig? configControls editor behavior and interaction settings:
config: NodeFlowConfig(
enablePanning: true,
enableZooming: true,
enableSelection: true,
enableNodeDragging: true,
enableConnectionCreation: true,
scrollToZoom: false,
readOnly: false,
)| Option | Default | Description |
|---|---|---|
enablePanning | true | Allow dragging the canvas |
enableZooming | true | Allow zoom in/out |
enableSelection | true | Allow selecting nodes |
enableNodeDragging | true | Allow dragging nodes |
enableConnectionCreation | true | Allow creating connections |
scrollToZoom | false | Use scroll wheel for zooming |
readOnly | false | Disable all editing |
Use readOnly: true for viewer-only mode, or set individual flags for fine-grained control.
Complete Example
class MyFlowEditor extends StatefulWidget {
@override
State<MyFlowEditor> createState() => _MyFlowEditorState();
}
class _MyFlowEditorState extends State<MyFlowEditor> {
late final NodeFlowController<MyNodeData> _controller;
Node<MyNodeData>? _selectedNode;
@override
void initState() {
super.initState();
_controller = NodeFlowController<MyNodeData>();
_initializeGraph();
}
void _initializeGraph() {
final node1 = Node<MyNodeData>(
id: 'node-1',
type: 'start',
position: Offset(100, 100),
size: Size(150, 80),
data: MyNodeData(label: 'Start'),
outputPorts: [
Port(id: 'node-1-out', name: 'Output'),
],
);
_controller.addNode(node1);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Node Flow Editor'),
actions: [
IconButton(icon: Icon(Icons.add), onPressed: _addNode),
if (_selectedNode != null)
IconButton(icon: Icon(Icons.delete), onPressed: _deleteSelectedNode),
],
),
body: Row(
children: [
Expanded(
flex: 3,
child: NodeFlowEditor<MyNodeData>(
controller: _controller,
theme: NodeFlowTheme.light,
nodeBuilder: (context, node) => _buildNode(node),
events: NodeFlowEvents(
node: NodeEvents(
onSelected: (node) => setState(() => _selectedNode = node),
onDoubleTap: (node) => _editNode(node),
onContextMenu: (node, pos) => _showNodeMenu(node, pos),
),
connection: ConnectionEvents(
onCreated: (conn) => _showSnackBar('Connection created'),
onDeleted: (conn) => _showSnackBar('Connection deleted'),
),
viewport: ViewportEvents(
onCanvasTap: (pos) => _controller.clearSelection(),
),
),
config: NodeFlowConfig(
enablePanning: true,
enableZooming: true,
),
),
),
if (_selectedNode != null)
SizedBox(
width: 300,
child: _buildPropertiesPanel(),
),
],
),
);
}
Widget _buildNode(Node<MyNodeData> node) {
return Container(
padding: EdgeInsets.all(12),
child: Text(
node.data.label,
style: TextStyle(fontWeight: FontWeight.bold),
),
);
}
Widget _buildPropertiesPanel() {
return Container(
color: Colors.grey[100],
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Properties', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
Text('Node ID: ${_selectedNode!.id}'),
Text('Type: ${_selectedNode!.type}'),
SizedBox(height: 16),
ElevatedButton(onPressed: _deleteSelectedNode, child: Text('Delete')),
],
),
);
}
void _addNode() {
final node = Node<MyNodeData>(
id: 'node-${DateTime.now().millisecondsSinceEpoch}',
type: 'process',
position: Offset(200, 200),
size: Size(150, 80),
data: MyNodeData(label: 'New Node'),
inputPorts: [Port(id: 'in-${DateTime.now().millisecondsSinceEpoch}', name: 'Input')],
outputPorts: [Port(id: 'out-${DateTime.now().millisecondsSinceEpoch}', name: 'Output')],
);
_controller.addNode(node);
}
void _deleteSelectedNode() {
if (_selectedNode != null) {
_controller.removeNode(_selectedNode!.id);
setState(() => _selectedNode = null);
}
}
void _editNode(Node<MyNodeData> node) { /* Show edit dialog */ }
void _showNodeMenu(Node<MyNodeData> node, Offset pos) { /* Show context menu */ }
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}Keyboard Shortcuts
The editor includes built-in keyboard shortcuts:
- Delete / Backspace: Delete selected nodes
- Ctrl+A / Cmd+A: Select all nodes
- Escape: Clear selection
- Arrow keys: Move selected nodes
See Keyboard Shortcuts for the complete list and customization options.
Best Practices
- Dispose Controller: Always dispose the controller in your widget's dispose method
- Responsive Layout: Use
LayoutBuilderto make the editor responsive - Loading State: Show a loading indicator while initializing the graph
- Error Handling: Wrap operations in try-catch blocks
- Performance: Keep node widgets lightweight
- State Management: Use controller APIs instead of directly modifying graph
See Also
- NodeFlowViewer - Read-only view
- NodeFlowMinimap - Overview minimap
- Theming - Customization guide