Enable Dark Mode!
how-to-download-and-view-a-pdf-file-by-clicking-the-notification-in-flutter.jpg
By: Farhana Jahan PT

How to Download & View a PDF File by Clicking the Notification in Flutter

Technical

In mobile app development, integrating notifications that allow users to seamlessly interact with content is crucial for enhancing user experience. Flutter, with its robust framework and extensive plugin support, provides powerful tools to achieve this functionality efficiently. One common requirement is enabling users to download and view PDF files directly from notifications. 

In this blog post, we'll explore how to implement a feature in a Flutter application that notifies users when a PDF file is downloaded, and allows them to open and view the PDF directly from the notification itself. 

Firstly, we need to grant read, write, and manage permissions for external storage in your Flutter application's AndroidManifest.xml file

(your_app_path/android/app/src/main/AndroidManifest.xml), as shown below:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

Now, let's add a download icon to the body of your Flutter page. Additionally, integrate a downloading progress indicator that activates upon clicking the icon. To achieve this, import the percent_indicator package into your Dart file as follows:

import 'package:percent_indicator/circular_percent_indicator.dart';

In the terminal, run the following command to install the percent_indicator package:

flutter pub add percent_indicator

Below is the code that includes a download icon and a progress indicator that activates when the download icon is clicked:

import 'package:flutter/material.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Download PDF:',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
            ),
            SizedBox(
              height: 10,
            ),
            SizedBox(
              child: DownloadWidget(),
            )
          ],
        ),
      ),
    );
  }
}
class DownloadWidget extends StatefulWidget {
  @override
  _DownloadWidgetState createState() => _DownloadWidgetState();
}
class _DownloadWidgetState extends State<DownloadWidget> {
  double _progress = 0.0;
  bool _isDownloading = false;
  @override
  void initState() {
    super.initState();
  }
    Future<void> _downloadPDF() async {
    setState(() {
      _isDownloading = true;
      _progress = 0.0;
    });
    try {
      for (int i = 0; i <= 100; i++) {
        await Future.delayed(Duration(milliseconds: 50));
        setState(() {
          _progress = i / 100;
        });
      }
    } catch (e) {
      setState(() {
        _isDownloading = false;
      });
    }
  }
  @override
  Widget build(BuildContext context) {
    return CircularPercentIndicator(
      radius: 25.0,
      lineWidth: 5.0,
      percent: _progress,
      center: _isDownloading
          ? Text("${(_progress * 100).toStringAsFixed(0)}%")
          : IconButton(
        icon: Icon(
          Icons.file_download_outlined,
          color: Colors.blue,
        ),
        iconSize: 25.0,
        onPressed: _downloadPDF,
      ),
      progressColor: Colors.blue,
      backgroundColor: Colors.grey[200]!,
      circularStrokeCap: CircularStrokeCap.round,
    );
  }
}

The output of the code is given below:

When you do not give the code below, progress is not initiated.

for (int i = 0; i <= 100; i++) {
        await Future.delayed(Duration(milliseconds: 50));
        setState(() {
          _progress = i / 100;
        });
      }

await Future.delayed(Duration(milliseconds: 50));: Inside the loop, it awaits a delayed future of 50 milliseconds before proceeding to the next iteration. This delay simulates the progression of time or a process that takes time to complete.

State(() { _progress = i / 100; }); updates the _progress variable with the current progress calculated as a percentage (i / 100). This triggers a rebuild of the UI to reflect the updated progress visually.

How to Download & View a PDF File by Clicking the Notification in Flutter-cybrosys

Upon clicking the download icon, the progress indicator is displayed as follows:

How to Download & View a PDF File by Clicking the Notification in Flutter-cybrosys

Now, let's proceed to initialize the notification system.

To initialize the notification system, import the flutter_local_notifications package as shown below:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

Additionally, you can open the file by clicking on the notification. To achieve this, you need to import the following package:

import 'package:open_file/open_file.dart';

Now you need to Initialize an instance of FlutterLocalNotificationsPlugin to manage local notifications.

For that, add following line to your  _DownloadWidgetState class:

final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();

Now, let's initialize notifications by adding the following function to the initState method of your download class:

Future<void> _initializeNotifications() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    final InitializationSettings initializationSettings =
        InitializationSettings(android: initializationSettingsAndroid);
    await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
    _flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) async {
        if (response.payload != null) {
          await _handleNotificationClick(response.payload);
        }
      },
    );
  }
const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher');

: Configures Android-specific settings for notifications, such as the app icon (@mipmap/ic_launcher). This icon is displayed in notifications triggered by the app.

final InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid);

: Combines Android initialization settings (initializationSettingsAndroid) into an InitializationSettings object, which encapsulates all platform-specific settings needed to initialize notifications.

await _flutterLocalNotificationsPlugin.initialize(initializationSettings);

: Initializes the flutterLocalNotificationsPlugin instance with initialization settings, configuring the plugin to use the specified settings for notification initialization. This step ensures that the plugin is ready to handle and display notifications on the Android platform.

_flutterLocalNotificationsPlugin.initialize(..., onDidReceiveNotificationResponse: (NotificationResponse response) async { ... });

: Sets up a callback function (onDidReceiveNotificationResponse) to handle user interaction with notifications. This callback is invoked when a user taps on a notification and provides a NotificationResponse object (response).

if (response.payload != null) { await _handleNotificationClick(response.payload); }

: Within the onDidReceiveNotificationResponse callback, checks if the notification payload (response.payload) is not null. If a payload exists, it invokes _handleNotificationClick function asynchronously to process the payload.

The _handleNotificationClick function is given below:

Future<void> _handleNotificationClick(String? payload) async {
    if (payload != null) {
      OpenFile.open(payload);
    }
  }

Here if (payload != null) { ... }: Checks if the payload parameter is not null. This ensures that there is valid data associated with the notification click action.

Now, create a demo PDF with the content "Hello, PDF!".

For that, you need to import pdf package as below:

import 'package:pdf/widgets.dart' as pw;

Add the following code to your  “_downloadPDF()” function

final pdf = pw.Document();
      pdf.addPage(
        pw.Page(
          build: (pw.Context context) {
            return pw.Center(
              child: pw.Text('Hello, PDF!'),
            );
          },
        ),
      );

After creating the PDF file, you need to obtain the external storage directory to store it. To do this, add the following line of code:

final output = await getExternalStorageDirectory();

When adding this line, you might encounter an error stating “The method 'getExternalStorageDirectory' isn't defined for the type '_DownloadWidgetState'.” To resolve this error, ensure you import the 'path_provider' package as follows:

import 'package:path_provider/path_provider.dart';

Now you need add the code below

final filePath = '${output!.path}/example.pdf';
final file = File(filePath);
await file.writeAsBytes(await pdf.save());

'${output!.path}/example.pdf' constructs a string that represents the complete file path where the PDF file will be stored. In this case, it appends /example.pdf to the external storage directory path, create a File object at that path (file), and asynchronously write PDF content to it using bytes obtained from pdf.save().

Add the following code to the _DownloadWidgetState class to enable

notifications. Also integrate _showDownloadCompleteNotification(filePath); In the _downloadPDF() function.

Future<void> _showDownloadCompleteNotification(String filePath) async {
    const AndroidNotificationDetails androidPlatformChannelSpecifics =
        AndroidNotificationDetails(
      'id',
      'name',
      channelDescription: 'PDF Document',
      importance: Importance.max,
      priority: Priority.high,
      showWhen: false,
    );
    const NotificationDetails platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);
    await _flutterLocalNotificationsPlugin.show(
      0,
      'Download Complete',
      'Your PDF has been downloaded.',
      platformChannelSpecifics,
      payload: filePath,
    );
  }

The complete code is given below:

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:open_file/open_file.dart';
import 'package:path_provider/path_provider.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:pdf/widgets.dart' as pw;
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Download PDF:',
              style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
            ),
            SizedBox(
              height: 10,
            ),
            SizedBox(
              child: DownloadWidget(),
            )
          ],
        ),
      ),
    );
  }
}
class DownloadWidget extends StatefulWidget {
  @override
  _DownloadWidgetState createState() => _DownloadWidgetState();
}
class _DownloadWidgetState extends State<DownloadWidget> {
  double _progress = 0.0;
  bool _isDownloading = false;
  final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();
  @override
  void initState() {
    super.initState();
    _initializeNotifications();
  }
  Future<void> _initializeNotifications() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');
    final InitializationSettings initializationSettings =
        InitializationSettings(android: initializationSettingsAndroid);
    await _flutterLocalNotificationsPlugin.initialize(initializationSettings);
    _flutterLocalNotificationsPlugin.initialize(
      initializationSettings,
      onDidReceiveNotificationResponse: (NotificationResponse response) async {
        if (response.payload != null) {
          await _handleNotificationClick(response.payload);
        }
      },
    );
  }
  Future<void> _handleNotificationClick(String? payload) async {
    if (payload != null) {
      OpenFile.open(payload);
    }
  }
  Future<void> _showDownloadCompleteNotification(String filePath) async {
    const AndroidNotificationDetails androidPlatformChannelSpecifics =
        AndroidNotificationDetails(
      'id',
      'name',
      channelDescription: 'PDF Document',
      importance: Importance.max,
      priority: Priority.high,
      showWhen: false,
    );
    const NotificationDetails platformChannelSpecifics =
        NotificationDetails(android: androidPlatformChannelSpecifics);
    await _flutterLocalNotificationsPlugin.show(
      0,
      'Download Complete',
      'Your PDF has been downloaded.',
      platformChannelSpecifics,
      payload: filePath,
    );
  }
  Future<void> _downloadPDF() async {
    setState(() {
      _isDownloading = true;
      _progress = 0.0;
    });
    try {
      for (int i = 0; i <= 100; i++) {
        await Future.delayed(Duration(milliseconds: 50));
        setState(() {
          _progress = i / 100;
        });
      }
      final pdf = pw.Document();
      pdf.addPage(
        pw.Page(
          build: (pw.Context context) {
            return pw.Center(
              child: pw.Text('Hello, PDF!'),
            );
          },
        ),
      );
      final output = await getExternalStorageDirectory();
      final filePath = '${output!.path}/example.pdf';
      final file = File(filePath);
      await file.writeAsBytes(await pdf.save());
      setState(() {
        _isDownloading = false;
        _progress = 1.0;
      });
      await _showDownloadCompleteNotification(filePath);
    } catch (e) {
      setState(() {
        _isDownloading = false;
      });
    }
  }
  @override
  Widget build(BuildContext context) {
    return CircularPercentIndicator(
      radius: 25.0,
      lineWidth: 5.0,
      percent: _progress,
      center: _isDownloading
          ? Text("${(_progress * 100).toStringAsFixed(0)}%")
          : IconButton(
              icon: Icon(
                Icons.file_download_outlined,
                color: Colors.blue,
              ),
              iconSize: 25.0,
              onPressed: _downloadPDF,
            ),
      progressColor: Colors.blue,
      backgroundColor: Colors.grey[200]!,
      circularStrokeCap: CircularStrokeCap.round,
    );
  }
}

And the output for notification is given below:

How to Download & View a PDF File by Clicking the Notification in Flutter-cybrosys

When you click on it, the PDF will open as below:

How to Download & View a PDF File by Clicking the Notification in Flutter-cybrosys

By integrating these components, you create a smooth and user-friendly experience for your app users. They can download and view important PDF documents with a simple tap on a notification, making your app more functional and intuitive

To read more about How to Create Bottom Bar Navigation in Flutter, refer to our blog How to Create Bottom Bar Navigation in Flutter.


If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, Kinfra Techno Park
Kakkancherry, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message