Flutter

What’s New in 2021 Volume 4: Flutter PDF Viewer

The Syncfusion Flutter PDF Viewer (SfPdfViewer) widget lets you view PDF documents seamlessly and efficiently in Android, iOS, web, and macOS platforms. It has highly interactive and customizable features such as magnification, virtual scrolling, page navigation, and bookmark navigation.

Adding to these, we have included the following new, user-friendly features in our Flutter PDF Viewer for the 2021 Volume 4 release:

  • Load password-protected and encrypted PDF documents.
  • Tile rendering.

Let’s look at them briefly.

Load password-protected and encrypted PDF documents

Now, Flutter PDF Viewer provides support to load password-protected PDF documents. It allows us to load and open PDF documents encrypted with AES and RC4 encryption algorithms.

When we load a password-protected PDF document with an invalid or empty password, the Flutter PDF Viewer will display the Password Protected dialog. In that, we have to use the proper password to open the encrypted PDF. To open an encrypted PDF document, use the password property.

Refer to the following image.

Loading an Encrypted PDF Document in Flutter PDF Viewer

We can customize the style (theme) of the built-in password dialog using the passwordDialogStyle property.

Note:For more details, refer to the Specialized theme widget in Flutter PDF Viewer documentation.

Customizing password dialog

We can customize the entire password dialog by disabling the built-in dialog and creating a new dialog. To disable the built-in password dialog, set the canShowPasswordDialog property as false.

The following code example illustrates how to display a customized password dialog in the Flutter PDF Viewer.

void main() {
  runApp(MaterialApp(home: CustomPasswordDialog()));
}

class CustomPasswordDialog extends StatefulWidget {
  @override
  _CustomPasswordDialogState createState() => _CustomPasswordDialogState();
}

class _CustomPasswordDialogState extends State<CustomPasswordDialog> {
  String? _password;
  final GlobalKey<FormFieldState> _formKey = GlobalKey<FormFieldState>();
  final TextEditingController _textFieldController = TextEditingController();
  final FocusNode _passwordDialogFocusNode = FocusNode();
  bool _hasPasswordDialog = false;
  String _errorText = '';

  @override
  Widget build(BuildContext context) {
    final Orientation orientation = kIsWeb ?
        Orientation.portrait : MediaQuery.of(context).orientation;
    return Scaffold(
      appBar: AppBar(
        title: Text("Custom password dialog"),
        toolbarHeight: 56,
      ),
      body: SfPdfViewer.network(
        "https://cdn.syncfusion.com/content/PDFViewer/encrypted.pdf",
        canShowPasswordDialog: false,
        password: _password,
        onDocumentLoaded: (details) {
          Navigator.pop(context);
          _hasPasswordDialog = false;
          _passwordDialogFocusNode.unfocus();
        },
        onDocumentLoadFailed: (details) {
          if (details.description.contains('password')) {
            if (details.description.contains('invalid') && _hasPasswordDialog) {
              _errorText = "Invalid password !!";
              _formKey.currentState?.validate();
              _textFieldController.clear();
              _passwordDialogFocusNode.requestFocus();
            } else {
              _errorText = ''; // Creating custom password dialog.
              showDialog<String>(
                  context: context,
                  builder: (BuildContext context) => SafeArea(
                    minimum: EdgeInsets.only(left: 10,right: 10),
                        child: AlertDialog(
                          scrollable: true,
                          insetPadding: EdgeInsets.zero,
                          contentPadding: orientation == Orientation.portrait
                              ? const EdgeInsets.all(24)
                              : const EdgeInsets.only(
                                  top: 0, right: 24, left: 24, bottom: 0),
                          buttonPadding: orientation == Orientation.portrait
                              ? const EdgeInsets.all(8)
                              : const EdgeInsets.all(4),
                          title: Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: <Widget>[
                              Text(
                                'Password required',
                                style: TextStyle(
                                  fontFamily: 'Roboto',
                                  fontSize: 20,
                                  fontWeight: FontWeight.w500,
                                  color: Theme.of(context)
                                      .colorScheme
                                      .onSurface
                                      .withOpacity(0.87),
                                ),
                              ),
                              SizedBox(
                                height: 36,
                                width: 36,
                                child: RawMaterialButton(
                                  onPressed: () {
                                    _textFieldController.clear();
                                    Navigator.of(context).pop();
                                    _hasPasswordDialog = false;
                                    _passwordDialogFocusNode.unfocus();
                                  },
                                  child: Icon(
                                    Icons.clear,
                                    color: Theme.of(context)
                                        .colorScheme
                                        .onSurface
                                        .withOpacity(0.6),
                                    size: 24,
                                  ),
                                ),
                              ),
                            ],
                          ),
                          actionsPadding: EdgeInsets.only(top: 0),
                          shape: const RoundedRectangleBorder(
                              borderRadius:
                                  BorderRadius.all(Radius.circular(4.0))),
                          content: SingleChildScrollView(
                              child: SizedBox(
                                  child: Column(children: <Widget>[
                            Align(
                              alignment: Alignment.centerLeft,
                              child: Padding(
                                padding: const EdgeInsets.fromLTRB(0, 0, 0, 30),
                                child: Text(
                                  'The document is password protected. Please enter a password.',
                                  style: TextStyle(
                                    fontFamily: 'Roboto',
                                    fontSize: 16,
                                    fontWeight: FontWeight.w400,
                                    color: Theme.of(context)
                                        .colorScheme
                                        .onSurface
                                        .withOpacity(0.6),
                                  ),
                                ),
                              ),
                            ),
                            Form(
                              child: TextFormField(
                                key: _formKey,
                                controller: _textFieldController,
                                focusNode: _passwordDialogFocusNode,
                                obscureText: true,
                                decoration: InputDecoration(hintText: 'Password hint: syncfusion',
                                    border: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
                                focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue))),
                                obscuringCharacter: '#',
                                onFieldSubmitted: (value) {
                                  setState(() {
                                    _password = value;
                                  });
                                },
                                validator: (value) {
                                  if (_errorText.isNotEmpty) return _errorText;
                                  return null;
                                },
                                onChanged: (value) {
                                  _formKey.currentState?.validate();
                                  _errorText = '';
                                },
                              ),
                            )
                          ]))),
                          actions: <Widget>[
                            Row(
                              textDirection: TextDirection.rtl,
                              children: [
                                TextButton(
                                  onPressed: () {
                                    Navigator.pop(context, 'Cancel');
                                    _hasPasswordDialog = false;
                                    _passwordDialogFocusNode.unfocus();
                                  },
                                  child: const Text('CANCEL'),
                                ),
                                TextButton(
                                  onPressed: () {
                                    if (_textFieldController.text.isNotEmpty) {
                                      setState(() {
                                        _password = _textFieldController.text;
                                      });
                                    }
                                  },
                                  child: const Text('OK'),
                                )
                              ],
                            )
                          ],
                        ),
                      ));
              _hasPasswordDialog = true;
            }
          }
        },
      ),
    );
  }
}
Customizing the Password Dialog in Flutter PDF Viewer

Tile rendering

Whenever a PDF document is zoomed in, the PDF pages’ resolution should be increased so that pixelation can be avoided, and clarity of the content will be improved.

The new tile rendering feature in the PDF Viewer enhances the PDF pages’ clarity and readability. This feature renders the PDF page regions in high resolution when a user zooms in on them.

Tile Rendering Feature in Flutter PDF Viewer

Conclusion

Thanks for reading! In this blog, we have seen the new features of the Syncfusion Flutter PDF Viewer available in the 2021 Volume 4 release. Details on these features are also available in our Release Notes and What’s New pages. Refer to our Flutter PDF Viewer documentation for complete details about the widget. Try out these features and leave your feedback in the comments section below!

Also, don’t miss our Flutter demos on GitHub, Google Play, and the App Store.

If you are an existing Syncfusion user, please download the latest version of Essential Studio from the License & Downloads page. If you aren’t a customer yet, you can try our 30-day free trial to check out these features.

You can contact us through our support forumsupport portal, or feedback portal. We are always happy to assist you!

Related blogs

Dilli Babu N

Dilli Babu N is a Product Manager at Syncfusion who has specialized skills in File Format products, Flutter and Xamarin controls. In 2014, he started his career in Syncfusion as a Software developer and turned to be a technology enthusiast.