Hello world
This example uses only the free Core + Flutter foundations. They are Apache-2.0 licensed and free for commercial and open-source applications. We will create two shapes, connect them and enable interactive editing. Start with the Flutter project from Getting started.
1. Create the controller
The controller owns the document and edit history. Standard shapes and straight connectors are already installed. Enable the Flutter editing tools with an editable extension. Import package:vyuh_diagram_flutter/extension_api.dart alongside the Flutter entry point.
dart
final controller = DiagramController(
configuration: DiagramConfiguration(
extensions: [
const DiagramFlutterExtension(id: 'hello.editing', editable: true),
],
),
);Keep it in your screen's state rather than creating it inside build.
2. Add two shapes
A shape needs a type and a position. DiagramPoint specifies its center in diagram coordinates. createShape returns the new shape's ID directly.
dart
final hello = controller.createShape(
Shapes.rectangle,
at: DiagramPoint.zero,
text: 'Hello',
);
final world = controller.createShape(
Shapes.ellipse,
at: const DiagramPoint(240, 0),
text: 'World',
);Use these IDs to refer to the shapes in later edits. Diagram coordinates describe the world, rather than pixels relative to the Flutter screen.
Use controller.edits.createShape instead when you need to check whether an edit was allowed. See When you need to check an edit below.
3. Connect them
The connector binds its endpoints to the two elements and follows them when they move or resize.
dart
controller.connect(hello, world);This uses the standard straight route. Controller methods can also add labels and change the connector's stroke style.
4. Mount the canvas
DiagramCanvas displays the controller. Give it bounded space, such as the body of a Scaffold or an Expanded inside a column.
dart
DiagramCanvas(controller: controller)With DiagramFlutterExtension(editable: true), the free canvas supports interactive editing with the installed tools. Select and drag a shape to move it, and use its handles to resize or rotate it. No private packages or package token are needed.
The paid editor extension adds the ready-made toolbar, palette and property inspector. These controls are optional; your application can build its own controls around the free controller APIs.
For another complete application, see the published Flutter package example.
5. Run the complete example
The JSON codec saves the initial document as a string. A Reset button loads that snapshot back into the controller. This is also the save/load API your application can use with its own storage.
Replace lib/main.dart with this application, then run flutter run. Content is created once in initState; the host disposes the controller when the screen closes.
dart
import 'package:flutter/material.dart';
import 'package:vyuh_diagram_flutter/vyuh_diagram_flutter.dart';
import 'package:vyuh_diagram_flutter/extension_api.dart';
void main() => runApp(const MaterialApp(home: Scaffold(body: HelloDiagram())));
class HelloDiagram extends StatefulWidget {
const HelloDiagram({super.key});
@override
State<HelloDiagram> createState() => _HelloDiagramState();
}
class _HelloDiagramState extends State<HelloDiagram> {
final controller = DiagramController(
configuration: DiagramConfiguration(
extensions: [
const DiagramFlutterExtension(id: 'hello.editing', editable: true),
],
),
);
late final String saved;
@override
void initState() {
super.initState();
final hello = controller.createShape(
Shapes.rectangle,
at: DiagramPoint.zero,
text: 'Hello',
);
final world = controller.createShape(
Shapes.ellipse,
at: const DiagramPoint(240, 0),
text: 'World',
);
controller.connect(hello, world);
saved = controller.save(DiagramJsonCodec.new);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => Column(
children: [
TextButton(
onPressed: () => controller.load(saved, format: DiagramJsonCodec.new),
child: const Text('Reset'),
),
Expanded(child: DiagramCanvas(controller: controller)),
],
);
}6. Group edits and use history
Each controller edit is undoable. Wrap several edits in a batch when they should count as one action—for example, creating both shapes and their connection together.
dart
controller.batch('Add greeting', () {
final hello = controller.createShape(
Shapes.rectangle,
at: DiagramPoint.zero,
text: 'Hello',
);
final world = controller.createShape(
Shapes.ellipse,
at: const DiagramPoint(240, 0),
text: 'World',
);
controller.connect(hello, world);
});
controller.undo();
controller.redo();Use this batch in place of the creation code in initState. A refused direct edit throws DiagramChangeDenied and rolls back the batch. Use controller.edits when your application needs to inspect allowed and reason instead.
7. Save and reopen
The JSON codec converts the document to a string. Store that string wherever your application saves documents. Loading it replaces the controller's content.
dart
final saved = controller.save(DiagramJsonCodec.new);
controller.load(saved, format: DiagramJsonCodec.new);The Flutter entry point exposes the JSON codec, so this needs no additional import. Selection and the camera are view state rather than document edits.
Next: Extending it with extensions.
When you need to check an edit
There are two ways to create a shape. Call controller.createShape directly for the new ID. Use controller.edits.createShape when your application needs to handle a refused edit. Both use the same document and undo history.
dart
final result = controller.edits.createShape(
Shapes.rectangle,
at: DiagramPoint.zero,
);
if (result.allowed) {
controller.select([result.id!]);
} else {
print(result.reason);
}