Form filling in Flutter PDF Viewer (SfPdfViewer)
12 Mar 202424 minutes to read
The SfPdfViewer allows you to fill, edit, save, export, and import the AcroForm fields in a PDF document.
Supported form fields
You can load and fill in the following form fields in a PDF document using the Flutter PDF Viewer.
- Text box.
- Checkbox.
- Radio button.
- Combo box.
- Signature.
- List box.
Adding or Editing the form data programmatically
Programmatically add or edit the form data in the document using the getFormFields method, which retrieves the form field collection.
Adding or Editing text box data
Programmatically add or edit the text of the text box by changing the text property.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
final PdfTextFormField textbox = formFields.singleWhere(
(PdfFormField formField) => formField.name == 'name')
as PdfTextFormField;
textbox.text = 'John';
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Adding or Editing checkbox data
Programmatically check or uncheck the checkbox by changing the isChecked property.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
final PdfCheckboxFormField checkbox = formFields.singleWhere(
(PdfFormField formField) => formField.name == 'newsletter')
as PdfCheckboxFormField;
checkbox.isChecked = true;
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Editing combo box data
Programmatically select an item from the combo box using the selectedItem property.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
final PdfComboBoxFormField combobox = formFields.singleWhere(
(PdfFormField formField) => formField.name == 'state')
as PdfComboBoxFormField;
combobox.selectedItem = combobox.items[4];
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Editing radio button data
Programmatically select an item from the radio buttons using the selectedItem property.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
final PdfRadioFormField radiobutton = formFields.singleWhere(
(PdfFormField formField) => formField.name == 'gender')
as PdfRadioFormField;
radiobutton.selectedItem = radiobutton.items[2];
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Editing list box data
Programmatically select an item or more from the list box using the selectedItems property.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
final PdfListBoxFormField listbox = formFields.singleWhere(
(PdfFormField formField) => formField.name == 'list')
as PdfListBoxFormField;
listbox.selectedItems = listbox.items.sublist(0, 2);
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Adding or Editing signature data
Programmatically add or remove the signature in a signature form field by assigning the image bytes or null
to the signature property.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () async {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
final PdfSignatureFormField signature = formFields.singleWhere(
(PdfFormField formField) => formField.name == 'signature')
as PdfSignatureFormField;
final ByteData bytedata =
await rootBundle.load('assets/signature.png');
signature.signature = bytedata.buffer.asUint8List();
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Customize the visibility of built-in Signature pad
By default, the SfPdfViewer
displays the signature pad when tapped on the signature form field. You can customize the visibility of the built-in signature pad using the canShowSignaturePad property. The following code example explains the same.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
canShowSignaturePadDialog: false,
),
);
}
How to create and display a custom signature pad?
With the above option in the SfPdfViewer
, you can easily hide the built-in signature pad and create and display a custom signature pad to draw and add personalized signatures to the signature form field. The following code example explains the same.
In this example, the custom signature pad using the SfSignaturePad
will be displayed when tapping on the signature field with the following options:
-
Clear - Clears all the signature strokes in the
SfSignaturePad
. -
Save - Saves the signature strokes in the
SfSignaturePad
to the signature form field as an image.
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
import 'package:syncfusion_flutter_signaturepad/signaturepad.dart';
void main() {
runApp(MaterialApp(
title: 'Syncfusion PDF Viewer Demo',
home: HomePage(),
));
}
class HomePage extends StatefulWidget {
@override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
final GlobalKey<SfSignaturePadState> _signaturePadKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SfPdfViewer.asset(
'assets/form_document.pdf',
key: _pdfViewerKey,
canShowSignaturePadDialog: false,
onFormFieldFocusChange: (PdfFormFieldFocusChangeDetails details) {
if (details.formField is PdfSignatureFormField && details.hasFocus) {
final PdfSignatureFormField signatureFormField =
details.formField as PdfSignatureFormField;
_showCustomSignaturePadDialog(signatureFormField);
}
},
),
);
}
/// Displays the custom signature pad dialog.
Future<void> _showCustomSignaturePadDialog(
PdfSignatureFormField formField) async {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text(
'Draw your Signature',
textAlign: TextAlign.center,
),
titlePadding: const EdgeInsets.all(8),
contentPadding: const EdgeInsets.all(12),
content: Container(
height: 200,
width: 300,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
),
child: SfSignaturePad(
key: _signaturePadKey,
),
),
actions: [
TextButton(
onPressed: () {
// Clears the strokes in the signature pad.
_signaturePadKey.currentState!.clear();
},
child: const Text('Clear'),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
await _saveSignature(formField);
},
child: const Text('Save'),
),
],
);
},
);
}
/// Saves the image from the signature pad to the form field.
Future<void> _saveSignature(PdfSignatureFormField formField) async {
final ui.Image image =
await _signaturePadKey.currentState!.toImage(pixelRatio: 3.0);
final ByteData? imageBytes =
await image.toByteData(format: ui.ImageByteFormat.png);
if (imageBytes != null) {
final Uint8List data = imageBytes.buffer.asUint8List();
formField.signature = data;
}
}
}
Restrict the editing of form fields
To prevent editing the values of the form fields in the PDF document, set the readOnly
property of the respective form field to true
.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.edit,
color: Colors.white,
),
onPressed: () {
final List<PdfFormField> formFields =
_pdfViewerController.getFormFields();
formFields[0].readOnly = true;
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Clear form data
The clearFormData method clears all the form field data in the PDF document. The optional pageNumber
parameter can be used to clear the form field data on a specific page. By default, the pageNumber
parameter is 0. Refer to the following code example.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.clear,
color: Colors.white,
),
onPressed: () {
// Clears all the form field data on the 2nd page.
_pdfViewerController.clearFormData(pageNumber: 2);
},
),
IconButton(
icon: const Icon(
Icons.clear,
color: Colors.white,
),
onPressed: () {
// Clears all the form field data in the pdf document.
_pdfViewerController.clearFormData();
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Save form data
You can save the modified form field data by calling the saveDocument method. Refer to the following code example.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.save,
color: Colors.white,
),
onPressed: () async {
final List<int> savedBytes =
await _pdfViewerController.saveDocument();
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
NOTE
When the
saveDocument
method is called, the document will be automatically reloaded after the save in the viewer if any signature field in the PDF document is signed using theSfPdfViewer
. Also, the signature field will be flattened on save irrespective of thePdfFlattenOption
provided.
Flattening the form data on save
Flattening PDF form is a process of removing the form fields in the PDF document, thereby rendering the form fields appearance and content in the page graphics. This will avoid the PDF form being edited on any device. Flutter PDF Viewer supports flattening the PDF form when saving. You can perform this action by setting the PdfFlattenOption to PdfFlattenOption.formFields
in the saveDocument
method.
By default, the PdfFlattenOption
will be PdfFlattenOption.none
, which means the form fields (except signature fields) can be edited after saving.
Refer to the following code example.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.save,
color: Colors.white,
),
onPressed: () async {
final List<int> savedBytes = await _pdfViewerController
.saveDocument(flattenOption: PdfFlattenOption.formFields);
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Importing and Exporting form data
The Flutter PDF viewer allows users to import and export form data to and from PDF documents. The import and export of form data support the following extensions.
- fdf
- xfdf
- json
- xml
The required file type can be chosen from the DataFormat enumeration. In the following sections, only the xfdf
file type is explained for brevity.
NOTE
Import ‘package:syncfusion_flutter_pdf/pdf.dart’ in the Dart code to use the DartFormat parameter.
Exporting form data
The exportFormData method exports the current data filled in the form fields into a bytes list in the specified data format. Refer to the following code example.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.outbox,
color: Colors.white,
),
onPressed: () async {
final List<int> formDataBytes = _pdfViewerController
.exportFormData(dataFormat: DataFormat.xfdf);
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
NOTE
When exporting, the signature form field data will not be exported.
Importing form data
The importFormData method imports the data from a file of a specified type and fills the saved data into the form fields.
- To continue the import, if any field generates an error, set the optional parameter
continueImportOnError
to true. This will ignore the error and continue with the next field.
final PdfViewerController _pdfViewerController = PdfViewerController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
actions: <Widget>[
IconButton(
icon: const Icon(
Icons.inbox,
color: Colors.white,
),
onPressed: () async {
final ByteData data = await DefaultAssetBundle.of(context)
.load('assets/form_data.xfdf');
final List<int> formDataBytes = data.buffer.asUint8List();
_pdfViewerController.importFormData(formDataBytes, DataFormat.xfdf);
},
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
),
);
}
Undo and redo actions on form fields
If you performed undesired actions when editing the form fields, you can undo and redo the action to restore the previous state.
The undo and redo operations are performed by assigning the UndoHistoryController
instance to the undoController
property of the SfPdfViewer
. The UndoHistoryController class contains the undo
and redo
methods to perform the same, respectively. The canUndo
and canRedo
properties are used to check whether the undo and redo operations can be performed or not, respectively. The following code example illustrates how to perform the form filling undo and redo operations programmatically with the SfPdfViewer
.
final UndoHistoryController _undoController = UndoHistoryController();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(actions: [
ValueListenableBuilder(
valueListenable: _undoController,
builder: (context, value, child) {
return IconButton(
onPressed:
_undoController.value.canUndo ? _undoController.undo : null,
icon: const Icon(Icons.undo),
);
}),
ValueListenableBuilder(
valueListenable: _undoController,
builder: (context, value, child) {
return IconButton(
onPressed:
_undoController.value.canRedo ? _undoController.redo : null,
icon: const Icon(Icons.redo),
);
}),
]),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
undoController: _undoController,
),
),
);
}
For desktop platforms such as Windows, macOS, and desktop web, use the following shortcut keys to perform the actions.
Action & Shortcut keys | Windows | macOS |
---|---|---|
Undo |
Ctrl + z
|
Command + z
|
Redo |
Ctrl + y
|
Command + y
|
NOTE
This
undoController
is common for annotations and form fields. You do not need to create separate instances for both.
Callbacks
The SfPdfViewer
supports the PdfFormFieldFocusChangeCallback to notify the interaction with the form fields and the PdfFormFieldValueChangedCallback to notify the values changes in the form fields.
Form field focus change callback
The onFormFieldFocusChange callback triggers when the focus changes in or out of the form field. The PdfFormFieldFocusChangeDetails will return the formField
instance and its focus change value in the hasFocus
property. The following code example explains the same.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
onFormFieldFocusChange: (PdfFormFieldFocusChangeDetails details) {
print('${details.formField.name} - ${details.hasFocus}');
},
),
);
}
NOTE
The
PdfFormFieldFocusChangeCallback
only triggers for text boxes and signature form fields.
Form field value changed callback
The onFormFieldValueChanged callback triggers when the value is changed in the form field. The PdfFormFieldValueChangedDetails the formField
instance along with its oldValue
and newValue
. The following code example explains the same.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Syncfusion Flutter PDF Viewer'),
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
onFormFieldValueChanged:(PdfFormFieldValueChangedDetails details) {
print('${details.oldValue}');
print('${details.newValue}');
},
),
);
}
How to synchronize text form field data with the text entry widget periodically for each character change?
With the help of the onFormFieldValueChanged callback, we can synchronize text form field data with the text entry widget periodically for each character change. The following code example explains the same.
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
void main() {
runApp(MaterialApp(
title: 'Syncfusion PDF Viewer Demo',
home: HomePage(),
));
}
/// Represents Homepage for Navigation
class HomePage extends StatefulWidget {
@override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
final TextEditingController _nameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
width: 200,
child: TextField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Name',
fillColor: Colors.grey[100],
),
),
),
Expanded(
flex: 5,
child: SfPdfViewer.asset(
'assets/form_document.pdf',
key: _pdfViewerKey,
onFormFieldValueChanged:
(PdfFormFieldValueChangedDetails details) {
final PdfFormField field = details.formField;
if (field is PdfTextFormField && field.name == 'name') {
_nameController.value = TextEditingValue(text: field.text);
}
},
),
),
],
),
);
}
}
How to synchronize text form field data with the text entry widget after filling it out completely?
With the help of the onFormFieldFocusChange callback, we can synchronize text form field data with the text entry widget after filling it out completely. The following code example explains the same.
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
void main() {
runApp(MaterialApp(
title: 'Syncfusion PDF Viewer Demo',
home: HomePage(),
));
}
/// Represents Homepage for Navigation
class HomePage extends StatefulWidget {
@override
_HomePage createState() => _HomePage();
}
class _HomePage extends State<HomePage> {
final GlobalKey<SfPdfViewerState> _pdfViewerKey = GlobalKey();
final TextEditingController _nameController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
width: 200,
child: TextField(
controller: _nameController,
decoration: InputDecoration(
labelText: 'Name',
fillColor: Colors.grey[100],
),
),
),
Expanded(
flex: 5,
child: SfPdfViewer.asset(
'assets/form_document.pdf',
key: _pdfViewerKey,
onFormFieldFocusChange:
(PdfFormFieldFocusChangeDetails details) async {
final PdfFormField field = details.formField;
if (field is PdfTextFormField && field.name == 'name') {
if (details.hasFocus) {
_nameController.value = TextEditingValue.empty;
}
}
},
),
),
],
),
);
}
}
How to perform validation over the form field data?
With the help of the onFormFieldFocusChange and onFormFieldValueChanged callbacks and the form field programmatic options available in the SfPdfViewer
, we can validate the form field data. The following code examples explain the same.
In this example, we are validating the form field data in a registration form when saving. The validation includes,
Field name | Validation messages |
Name |
* Name is required. * Name should have at least 3 characters. * Name should not exceed 30 characters. * Name should not contain numbers. * Name should not contain special characters. |
* Email is required. * Email should be in correct format. |
|
Date of birth |
* Date of birth is required. * Date of birth should be in dd/mm/yyyy format. |
Course | * Atleast one course should be selected. |
Signature | * Signature is required. |
final PdfViewerController _pdfViewerController = PdfViewerController();
List<PdfFormField>? _formFields;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
icon: const Icon(Icons.save),
onPressed: _validateAndSaveForm,
),
],
),
body: SfPdfViewer.asset(
'assets/form_document.pdf',
controller: _pdfViewerController,
onDocumentLoaded: (PdfDocumentLoadedDetails details) {
_formFields = _pdfViewerController.getFormFields();
},
),
);
}
/// Validates the form field data.
Future<void> _validateAndSaveForm() async {
if (_formFields == null) {
return;
} else if (_formFields!.isEmpty) {
return;
}
final List<String> errors = <String>[];
for (final PdfFormField formField in _formFields!) {
if (formField is PdfTextFormField) {
if (formField.name == 'name') {
if (formField.text == null || formField.text.isEmpty) {
errors.add('Name is required.');
} else if (formField.text.length < 3) {
errors.add('Name should have atleast 3 characters.');
} else if (formField.text.length > 30) {
errors.add('Name should not exceed 30 characters.');
} else if (formField.text.contains(RegExp(r'[0-9]'))) {
errors.add('Name should not contain numbers.');
} else if (formField.text
.contains(RegExp(r'[!@#$%^&*(),.?":{}|<>]'))) {
errors.add('Name should not contain special characters.');
}
} else if (formField.name == 'dob') {
if (formField.text == null || formField.text.isEmpty) {
errors.add('Date of birth is required.');
} else if (!RegExp(r'^\d{1,2}\/\d{1,2}\/\d{4}$')
.hasMatch(formField.text)) {
errors.add('Date of birth should be in dd/mm/yyyy format.');
}
} else if (formField.name == 'email') {
if (formField.text == null || formField.text.isEmpty) {
errors.add('Email is required.');
} else if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$')
.hasMatch(formField.text)) {
errors.add('Email should be in correct format.');
}
}
} else if (formField is PdfListBoxFormField) {
if (formField.selectedItems == null ||
formField.selectedItems!.isEmpty) {
errors.add('Please select atleast one course.');
}
} else if (formField is PdfSignatureFormField) {
if (formField.signature == null) {
errors.add('Please sign the document.');
}
}
}
if (errors.isNotEmpty) {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Errors'),
content: SizedBox(
height: 200,
width: 100,
child: ListView.builder(
itemCount: errors.length,
itemBuilder: (_, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(errors[index]),
);
},
),
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Try Again'),
),
],
);
},
);
} else {
_pdfViewerController.saveDocument(
flattenOption: PdfFlattenOption.formFields);
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Success'),
content: const Text('Form submitted successfully.'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
);
}
}