A document editor
You will build a report editor on DocumentEditorController: paragraphs, lists, tables and images that load from your app's assets, with a two-row action toolbar.
Why the editor runtime
DocumentEditorController is the runtime under the typed widget. Use it for host bindings: images, fonts, your own link dialog, review, export and the Properties inspector. Each element package adds its methods (insertTable, insertImage, setLink, ...).
Packages
yaml
dependencies:
vyuh_document_extension_table:
hosted: https://pub.vyuh.tech
version: ^0.19.3
vyuh_document_extension_list:
hosted: https://pub.vyuh.tech
version: ^0.19.3
vyuh_document_extension_image:
hosted: https://pub.vyuh.tech
version: ^0.19.3sh
flutter pub add vyuh_document_extension_table --hosted-url https://pub.vyuh.tech
flutter pub add vyuh_document_extension_list --hosted-url https://pub.vyuh.tech
flutter pub add vyuh_document_extension_image --hosted-url https://pub.vyuh.techYou add them because your code calls their methods: Dart only shows an extension method when your file imports its package.
The code
dart
import 'package:flutter/material.dart';
import 'package:vyuh_document/paragraph_extension.dart';
import 'package:vyuh_document_editor/document_editor_extensions.dart'
show ImageDocumentExtension, ListDocumentExtension, TableDocumentExtension;
import 'package:vyuh_document_editor/vyuh_document_editor.dart';
import 'package:vyuh_document_extension_image/vyuh_document_extension_image.dart';
import 'package:vyuh_document_extension_list/vyuh_document_extension_list.dart';
import 'package:vyuh_document_extension_table/vyuh_document_extension_table.dart';
/// Maps the image IDs stored in the document to Flutter image providers.
final class AppImages implements ImageAssetResolver {
const AppImages();
@override
ImageProvider resolve(String assetId) => AssetImage('assets/$assetId');
}
class ReportEditor extends StatefulWidget {
const ReportEditor({super.key});
@override
State<ReportEditor> createState() => _ReportEditorState();
}
class _ReportEditorState extends State<ReportEditor> {
static const images = AppImages();
// Paragraph editing plus lists, tables and images. Nothing else installs.
late final controller = DocumentEditorController(
layoutMode: DocumentEditorLayoutMode.pageless,
document: Document.empty(title: 'Report'),
extensions: [
...DocumentExtension.richText,
const ListDocumentExtension(),
const TableDocumentExtension(),
const ImageDocumentExtension(assetResolver: images),
],
);
@override
void dispose() {
controller.dispose();
super.dispose();
}
// Each installed family adds its own controller methods.
void insertStarterContent() {
controller
..insertText('Quarterly report')
..splitBlock()
..insertListItem(ListMarker.bullet)
..insertText('Revenue grew')
..insertTable(rows: 3, cols: 2)
..insertImage(
assetId: 'chart.png',
altText: 'Revenue by quarter',
width: 320,
height: 180,
);
}
@override
Widget build(BuildContext context) => DocumentEditor(
controller: controller,
assetResolver: images,
configuration: DocumentEditorConfiguration(
// The toolbar shows only actions of installed extensions.
toolbar: DocumentToolbarConfiguration(
rows: DocumentWorkspaceToolbarRows.doubleRow,
),
),
);
}You see a pageless editor with two toolbar rows. The toolbar offers lists, tables and images because those extensions are installed; it shows no equation or shape buttons because they are not.
Extensions decide what exists
| Composition | What users get |
|---|---|
DocumentExtension.richText | Paragraphs, marks, links, clipboard, history, keyboard navigation |
[...DocumentExtension.richText, TableDocumentExtension()] | + tables |
DocumentExtension.defaults | The starter set: paragraphs, headings, quotes, checklists, code, tables, images, fields, lists, dividers, text boxes, shapes, table of contents |
DocumentExtension.defaultsWith(assetResolver: ...) | The starter set with editor bindings |
A command needs both: import its package and install its extension.
Without the extension, the call throws DocumentCommandUnavailableException and nothing changes.
Restrict content with a schema
Extensions install capabilities; DocumentSchemaPolicy.restricted(...) narrows what loading, paste and commands admit. See Choose extensions and the table example.
Images
ImageAssetResolver.resolve(assetId) maps the IDs stored in the document to Flutter ImageProviders. The document holds IDs, not bytes: store image bytes alongside the document JSON. See Fonts and images.
Toolbar options
DocumentToolbarConfiguration field | Effect |
|---|---|
rows | DocumentWorkspaceToolbarRows.singleRow or .doubleRow |
placement | DocumentToolbarPlacement.docked or .floating |
collapsible, initiallyExpanded | A collapsible trigger |
actionIds, groups | Which installed actions show |
menus | Group actions into menus |
More in Toolbar.
Save and load
dart
final json = controller.toJsonString(); // save
final next = await DocumentEditorController.fromSource( // load
source: DocumentSource.jsonString(json),
);See Loading and saving.