Save Flutter Widgets as PDF or Excel

Jackie Moraa
8 min readJul 17, 2023

--

In this digital age, businesses as well as individuals are relying heavily on mobile applications for various tasks and operations. E.g., viewing and printing documents in PDF and Excel etc — which was not the norm a couple of years ago.

PDF and Excel formats are widely recognised and supported, making it simple for users to open and view their data on various devices, including desktops, laptops, tablets, and smartphones. Providing this ‘Save as PDF or Excel’ functionality in our applications will allow our users to (Use Cases):

I. Easily access and share data
One of the main reasons why this feature is essential is the ability to enhance data accessibility and portability. By allowing users to save data in these formats, you enable them to store, share, and access information easily across different devices and platforms.

This flexibility allows users to work with their data whenever and wherever they need it, without being limited by the constraints of the application itself.

II. Analyse data efficiently
Businesses often require the ability to export data from their applications for further analysis or presentation purposes. With Excel files, users can leverage the powerful data manipulation capabilities of spreadsheet software to perform calculations, create charts, and visualise data trends.

This empowers users to gain valuable insights from their data and make informed decisions.

III. Generate professional reports
Similarly, PDFs are crucial for generating professional-looking reports. Whether it’s financial statements, receipts, invoices, or project summaries, the ability to save data in a PDF format ensures that the information is presented in a consistent and standardised manner.

PDF files preserve the formatting, fonts, and layout, making them ideal for sharing reports externally or archiving them for future reference.

In this article, we’re going to explore how to save elements/widgets in our Flutter screen to PDF and Excel. Let’s first start with the user interface we wish to save!

Consider a school system where we’ll need to view, download and send a report card for a particular student or view and analyse results for the entire class. I’ve created a simple application where in the home page we’ll have a list of all the students and when you click on one student, you can view their results and details.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:pdf_excel_flutter/constants/colors.dart';
import 'package:pdf_excel_flutter/data/results.dart';
import 'package:pdf_excel_flutter/pages/student_profile.dart';

class HomePage extends StatefulWidget {
const HomePage({super.key});

@override
State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
List results = Results.results;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
toolbarHeight: 60,
title: Text(
'All Students',
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontWeight: FontWeight.w400,
fontSize: 22.0,
color: AppColors.whiteColor,
),
),
centerTitle: true,
flexibleSpace: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
color: AppColors.primaryColor,
),
),
),
body: Container(
color: Colors.white,
child: ListView.separated(
itemCount: results.length,
itemBuilder: (BuildContext context, index) {
return InkWell(
onTap: () {
Navigator.of(context, rootNavigator: true).push(
MaterialPageRoute(
builder: (_) => StudentProfile(
studentName: results[index][0],
studentReg: results[index][1],
studentImage: results[index][2],
studentResults: results[index][3],
),
),
);
},
child: ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage(results[index][2]),
),
title: Text(
results[index][0],
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
results[index][1],
style: const TextStyle(fontSize: 13),
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider();
},
),
),
floatingActionButton: _excelFAB(),
);
}

Widget _excelFAB() {
return FloatingActionButton(
onPressed: () {},
backgroundColor: AppColors.greenColor,
child: const Icon(
CupertinoIcons.tray_arrow_down,
color: Colors.white,
),
);
}
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:pdf_excel_flutter/constants/colors.dart';

class StudentProfile extends StatefulWidget {
const StudentProfile({
super.key,
required this.studentName,
required this.studentReg,
required this.studentImage,
required this.studentResults,
});

final String studentName;
final String studentReg;
final String studentImage;
final Map studentResults;

@override
State<StudentProfile> createState() => _StudentProfileState();
}

class _StudentProfileState extends State<StudentProfile> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
automaticallyImplyLeading: true,
foregroundColor: AppColors.whiteColor,
toolbarHeight: 60,
title: Text(
widget.studentName,
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontWeight: FontWeight.w400,
fontSize: 22.0,
color: AppColors.whiteColor,
),
),
centerTitle: true,
flexibleSpace: Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20)),
color: AppColors.primaryColor,
),
),
),
body: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
minRadius: 35.0,
backgroundImage: AssetImage(widget.studentImage),
),
const SizedBox(width: 20.0),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 16.0),
Text(
widget.studentName,
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineSmall,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(
widget.studentReg,
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineSmall,
fontSize: 13,
fontWeight: FontWeight.w300,
),
),
],
),
],
),
const SizedBox(height: 20.0),
DataTable(
columns: [
DataColumn(
label: Text(
'Subject',
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
DataColumn(
label: Text(
'Marks',
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
DataColumn(
label: Text(
'Grade',
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
],
rows: [
// maths
DataRow(
cells: [
DataCell(
Text(
'Maths',
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
DataCell(
Text(
widget.studentResults['Maths'][0].toString(),
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
DataCell(
Text(
widget.studentResults['Maths'][1],
style: GoogleFonts.montserrat(
textStyle: Theme.of(context).textTheme.headlineMedium,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
),
],
),
// ...
// Add all the subject rows
// ...
],
),
],
),
),
floatingActionButton: _pdfFAB(),
);
}

Widget _pdfFAB() {
return FloatingActionButton(
onPressed: () {},
backgroundColor: AppColors.redColor,
child: const Icon(
CupertinoIcons.tray_arrow_down,
color: AppColors.whiteColor,
),
);
}
}

This is the resulting UI. (The students and results data is just from a static list in a different dart file).

Download as Excel

To download as excel, I found this package that worked well for this use case: syncfusion_flutter_xlsio. Proceed to add that library in your pubspec.yaml file and import it to the dart file. Then call this method below in the first screen (green floating action button). This will allow us to create and download an excel document containing the results for all the students in the list. (See comments in line)

  Future<void> generateAndSaveExcel() async {
final xcel.Workbook workbook = xcel.Workbook(); // create a new excel workbook
final xcel.Worksheet sheet = workbook.worksheets[0]; // the sheet we will be populating (only the first sheet)
const String excelFile = 'test_download'; // the name of the excel

/// design how the data in the excel sheet will be presented
/// you can get the cell to populate by index e.g., (1, 1) or by name e.g., (A1)
sheet.getRangeByIndex(1, 1).setText('All Students');
sheet.getRangeByIndex(2, 1).setText('Form 4 West'); // example class
sheet.getRangeByIndex(4, 1).setText('Student Name');

// set the titles for the subject results we want to fetch
sheet.getRangeByIndex(4, 2).setText('Maths');
sheet.getRangeByIndex(4, 3).setText('English');
sheet.getRangeByIndex(4, 4).setText('Kiswahili');
sheet.getRangeByIndex(4, 5).setText('Physics');
sheet.getRangeByIndex(4, 6).setText('Biology');
sheet.getRangeByIndex(4, 7).setText('Chemistry');
sheet.getRangeByIndex(4, 8).setText('Geography');
sheet.getRangeByIndex(4, 9).setText('Spanish');
sheet.getRangeByIndex(4, 10).setText('Total');

// loop through the results to set the data in the excel sheet cells
for (var i = 0; i < results.length; i++) {
sheet.getRangeByIndex(i + 5, 1).setText(results[i][0]);
sheet.getRangeByIndex(i + 5, 2).setText(results[i][3]['Maths'][0].toString());
sheet.getRangeByIndex(i + 5, 3).setText(results[i][3]['English'][0].toString());
sheet.getRangeByIndex(i + 5, 4).setText(results[i][3]['Kiswahili'][0].toString());
sheet.getRangeByIndex(i + 5, 5).setText(results[i][3]['Physics'][0].toString());
sheet.getRangeByIndex(i + 5, 6).setText(results[i][3]['Biology'][0].toString());
sheet.getRangeByIndex(i + 5, 7).setText(results[i][3]['Chemistry'][0].toString());
sheet.getRangeByIndex(i + 5, 8).setText(results[i][3]['Geography'][0].toString());
sheet.getRangeByIndex(i + 5, 9).setText(results[i][3]['Spanish'][0].toString());
sheet.getRangeByIndex(i + 5, 10).setText(results[i][3]['Total'][0].toString());
}

// save the document in the downloads file
final List<int> bytes = workbook.saveAsStream();
File('/storage/emulated/0/Download/$excelFile.xlsx').writeAsBytes(bytes);

// toast message to user
Fluttertoast.showToast(
msg: 'Excel file successfully downloaded',
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
backgroundColor: AppColors.primaryColor,
textColor: AppColors.whiteColor,
);

//dispose the workbook
workbook.dispose();
}

This is the resulting excel functionality.

Save as PDF

To save and download as PDF, add the pdf package to your pubspec.yaml file as usual and import it to your dart file. You’ll also need the printing package. You’ll notice that to print the PDF you’ll need to recreate the entire screen with the PDF widget (pw).

  Future<void> generateAndSavePDF() async {
final image = await imageFromAssetBundle('assets/logos/school-logo.png'); // import 'package:printing/printing.dart'
final doc = pw.Document(); // import 'package:pdf/widgets.dart' as pw
doc.addPage(
pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Padding( // recreate the entire UI
padding: const pw.EdgeInsets.all(18.00),
child: pw.Column(
children: [
pw.Align(
alignment: pw.Alignment.topCenter,
child: pw.Image(image, width: 100, height: 100), // our school logo for the official PDF
),
pw.Text(
'Jackie & Co. Secondary School',
style: const pw.TextStyle(fontSize: 17.00),
),
pw.SizedBox(height: 10.00),
pw.Align(
alignment: pw.Alignment.topLeft,
child: pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Text(
'Name: ${widget.studentName}',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Form 4 West',
style: const pw.TextStyle(fontSize: 15.00),
),
],
),
),
pw.Divider(),
pw.SizedBox(height: 15.00),
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
children: [
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Subject',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.SizedBox(height: 5.00),
pw.Text(
'Maths',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'English',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Kiswahili',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Physics',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Biology',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Chemistry',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Geography',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
'Spanish',
style: const pw.TextStyle(fontSize: 15.00),
),
],
),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Marks',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.SizedBox(height: 5.00),
pw.Text(
widget.studentResults['Maths'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['English'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Kiswahili'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Physics'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Biology'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Chemistry'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Geography'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Spanish'][0].toString(),
style: const pw.TextStyle(fontSize: 15.00),
),
],
),
pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
'Grade',
style: const pw.TextStyle(fontSize: 15.00),
),
pw.SizedBox(height: 5.00),
pw.Text(
widget.studentResults['Maths'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['English'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Kiswahili'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Physics'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Biology'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Chemistry'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Geography'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
pw.Text(
widget.studentResults['Spanish'][1],
style: const pw.TextStyle(fontSize: 15.00),
),
],
),
],
),
pw.SizedBox(height: 30.00),
pw.Row(
mainAxisAlignment: pw.MainAxisAlignment.center,
children: [
pw.Text(
'Grand Total: ${widget.studentResults['Total'][0]}',
),
pw.SizedBox(width: 15),
pw.Text(
'Mean Grade: ${widget.studentResults['Total'][1]}',
),
],
),
],
),
);
},
),
);
await Printing.layoutPdf(onLayout: (PdfPageFormat format) async => doc.save());
}

This is the resulting pdf functionality.

The entire code can be found in my repository here.

“Behind every successful project, there’s an Excel sheet that made it happen.”
— Anonymous

I hope you found this article useful!
As always, thanks for reading ❤

--

--