If you’re an event organizer, you know the pain of generating the certificates for participants. What if there was a mobile application that can do this tedious task for you?!
Introducing Holden, easy to use certificate generator app made using Flutter.
flutter create certificate_generator
1dependencies:
2 flutter:
3 sdk: flutter
4 cupertino_icons: ^0.1.2
5 spreadsheet_decoder: # parsing xlsx files
6 path_provider: # reading/writing files in application document directory
7 file_picker: # using local files from device
8 pdf_viewer_plugin: # viewing pdf files
9 pdf: # generating pdf file
10 permission_handler: # handling runtime permissions
11 share_extend: # sharing files
12 provider: # state-management
13 printing: # using image assets in pdf generation
flutter packages get
1import 'package:flutter/material.dart';
2
3import 'package:provider/provider.dart';
4import 'package:permission_handler/permission_handler.dart';
5
6import 'package:certificate_generator/providers/home.dart';
7
8import 'package:certificate_generator/screens/home.dart';
9import 'package:certificate_generator/screens/result.dart';
10import 'package:certificate_generator/screens/viewer.dart';
11
12void main() async {
13 runApp(
14 MultiProvider(
15 providers: [
16 ChangeNotifierProvider<HomeProvider>(
17 create: (context) => HomeProvider(),
18 ),
19 ],
20 child: MyApp(),
21 ),
22 );
23 await PermissionHandler().checkPermissionStatus(PermissionGroup.storage);
24}
25
26class MyApp extends StatelessWidget {
27 @override
28 Widget build(BuildContext context) {
29 return MaterialApp(
30 debugShowCheckedModeBanner: false,
31 title: 'Certificate Generator',
32 theme: ThemeData(
33 primarySwatch: Colors.deepPurple,
34 accentColor: Colors.white,
35 appBarTheme: AppBarTheme(
36 iconTheme: IconThemeData(
37 color: Colors.black,
38 ),
39 color: Colors.white,
40 textTheme: TextTheme(
41 title: TextStyle(
42 color: Colors.black,
43 fontWeight: FontWeight.bold,
44 fontSize: 20,
45 ),
46 ),
47 ),
48 ),
49 routes: {
50 '/': (context) => HomeScreen(),
51 '/result': (context) => ResultScreen(),
52 '/viewer': (context) => ViewerScreen(),
53 },
54 initialRoute: '/',
55 );
56 }
57}
1import 'dart:io';
2
3import 'package:flutter/material.dart';
4
5import 'package:provider/provider.dart';
6import 'package:spreadsheet_decoder/spreadsheet_decoder.dart';
7import 'package:file_picker/file_picker.dart';
8
9import 'package:certificate_generator/providers/home.dart';
10
11class HomeScreen extends StatelessWidget {
12 @override
13 Widget build(BuildContext context) {
14 return Consumer<HomeProvider>(
15 builder: (_, homeProvider, __) => Scaffold(
16 body: Center(
17 child: homeProvider.xlsxFilePath != null
18 ? ListView(
19 // list of names
20 )
21 : Center(
22 child: Text(
23 'Click FAB to read xlsx',
24 style: TextStyle(color: Colors.white),
25 ),
26 ),
27 ),
28 ),
29 );
30 }
31}
1FloatingActionButton(
2 onPressed: () async {
3 homeProvider.xlsxFilePath = await FilePicker.getFilePath(
4 type: FileType.CUSTOM,
5 fileExtension: 'xlsx',
6 );
7 },
8 child: Icon(Icons.attach_file),
9)
1import 'package:flutter/foundation.dart';
2
3///
4/// Manages state for screens/HomeScreen.dart
5///
6class HomeProvider with ChangeNotifier {
7 // holds the path of selected xlsx file path
8 String _xlsxFilePath;
9
10 // holds the names of tables of selected xlsx file path
11 Map<String, dynamic> _xlsxFileTables;
12
13 // holds the name of selected table from dropdown in screens/home.dart
14 String _xlsxFileSelectedTable;
15
16 String get xlsxFilePath => this._xlsxFilePath;
17
18 set xlsxFilePath(String _xlsxFilePath) {
19 this._xlsxFilePath = _xlsxFilePath;
20 notifyListeners();
21 }
22
23 Map<String, dynamic> get xlsxFileTables => this._xlsxFileTables;
24
25 set xlsxFileTables(Map<String, dynamic> _xlsxFileTables) {
26 this._xlsxFileTables = _xlsxFileTables;
27 this._xlsxFileSelectedTable = this._xlsxFileTables.keys.first;
28 notifyListeners();
29 }
30
31 String get xlsxFileSelectedTable => this._xlsxFileSelectedTable;
32
33 set xlsxFileSelectedTable(String _xlsxFileSelectedTable) {
34 this._xlsxFileSelectedTable = _xlsxFileSelectedTable;
35 notifyListeners();
36 }
37}
1ListView(
2 padding: EdgeInsets.all(16),
3 children: <Widget>[
4 ...SpreadsheetDecoder.decodeBytes(
5 File(homeProvider.xlsxFilePath).readAsBytesSync())
6 .tables[homeProvider.xlsxFileSelectedTable]
7 .rows
8 .map(
9 (value) => Column(
10 children: <Widget>[
11 Container(
12 decoration: BoxDecoration(
13 color: Colors.transparent.withOpacity(0.1),
14 borderRadius: BorderRadius.circular(50),
15 ),
16 child: ListTile(
17 contentPadding: EdgeInsets.all(16),
18 onTap: () {
19 Navigator.pushNamed(context, '/result',
20 arguments: {'result': value});
21 },
22 leading: Container(
23 width: 40,
24 height: 40,
25 decoration: BoxDecoration(
26 shape: BoxShape.circle,
27 color: Colors.deepPurple[200],
28 ),
29 child: Icon(
30 Icons.person,
31 color: Colors.deepPurple[100],
32 size: 30,
33 ),
34 ),
35 title: Text(
36 value[0],
37 style: TextStyle(
38 color: Colors.white70,
39 fontWeight: FontWeight.w700,
40 ),
41 ),
42 ),
43 ),
44 SizedBox(
45 height: 24,
46 ),
47 ],
48 ),
49 ),
50 ],
51)
1AppBar(
2 title: Text('Holden'),
3 elevation: 0,
4 shape: RoundedRectangleBorder(
5 borderRadius: BorderRadius.only(
6 bottomRight: Radius.circular(50),
7 bottomLeft: Radius.circular(50),
8 ),
9 ),
10 centerTitle: true,
11 bottom: homeProvider.xlsxFilePath != null
12 ? PreferredSize(
13 preferredSize: Size.fromHeight(60),
14 child: Container(
15 margin: EdgeInsets.symmetric(horizontal: 60),
16 child: DropdownButtonFormField(
17 value: homeProvider.xlsxFileSelectedTable,
18 onChanged: (newXlsxFileSelectedTable) {
19 homeProvider.xlsxFileSelectedTable =
20 newXlsxFileSelectedTable;
21 },
22 items: [
23 ...homeProvider.xlsxFileTables.keys.map(
24 (table) => DropdownMenuItem(
25 child: Text(table),
26 value: table,
27 ),
28 ),
29 ],
30 ),
31 ),
32 )
33 : PreferredSize(
34 child: Container(),
35 preferredSize: Size.fromHeight(0),
36 ),
37)
1FloatingActionButton(
2 heroTag: 'Share',
3 onPressed: () async {
4 final names = homeProvider
5 .xlsxFileTables[homeProvider.xlsxFileSelectedTable]
6 .rows
7 .map((name) => name
8 .toString()
9 .substring(1, name.toString().length - 1));
10 names.forEach((name) => pdfGenerator(name));
11 final String downloadPath =
12 await getApplicationDocumentsDirectoryPath();
13 final files = names
14 .map((name) => File('$downloadPath/$name.pdf').path);
15 await ShareExtend.shareMultiple([
16 ...files,
17 ], 'file');
18 },
19 child: Icon(Icons.share),
20)
HomeScreen Flow
1import 'dart:io';
2
3import 'package:flutter/material.dart';
4
5import 'package:share_extend/share_extend.dart';
6
7import 'package:certificate_generator/utils/commons.dart';
8
9class ResultScreen extends StatelessWidget {
10 @override
11 Widget build(BuildContext context) {
12 String name = (ModalRoute.of(context).settings.arguments
13 as Map<String, dynamic>)['result'][0];
14 return Scaffold(
15 backgroundColor: Colors.deepPurple[400],
16 appBar: AppBar(
17 shape: RoundedRectangleBorder(
18 borderRadius: BorderRadius.only(
19 bottomRight: Radius.circular(50),
20 bottomLeft: Radius.circular(50),
21 ),
22 ),
23 title: Container(
24 child: Text('Result'),
25 ),
26 centerTitle: true,
27 bottom: PreferredSize(
28 preferredSize: Size.fromHeight(64),
29 child: Column(
30 children: <Widget>[
31 Row(
32 mainAxisAlignment: MainAxisAlignment.center,
33 children: <Widget>[
34 Container(
35 width: 50,
36 height: 50,
37 decoration: BoxDecoration(
38 shape: BoxShape.circle,
39 color: Colors.deepPurple[200],
40 ),
41 child: Icon(
42 Icons.person,
43 color: Colors.deepPurple[100],
44 size: 35,
45 ),
46 ),
47 SizedBox(
48 width: 20,
49 ),
50 Text(
51 name,
52 style: TextStyle(
53 fontWeight: FontWeight.w700,
54 ),
55 )
56 ],
57 ),
58 SizedBox(
59 height: 10,
60 ),
61 ],
62 ),
63 ),
64 ),
65 body: Builder(
66 builder: (context) => SingleChildScrollView(
67 child: Container(
68 margin: EdgeInsets.all(16),
69 color: Colors.deepPurple[50],
70 child: Column(
71 crossAxisAlignment: CrossAxisAlignment.center,
72 children: <Widget>[
73 SizedBox(
74 height: 50,
75 ),
76 Container(
77 width: 150,
78 height: 150,
79 decoration: BoxDecoration(
80 shape: BoxShape.circle,
81 color: Colors.deepPurple[200],
82 ),
83 child: Icon(
84 Icons.person,
85 color: Colors.deepPurple[100],
86 size: 100,
87 ),
88 ),
89 SizedBox(
90 height: 50,
91 ),
92 Text(
93 'certificate of completion',
94 style: TextStyle(
95 fontSize: 22,
96 color: Colors.grey[700],
97 ),
98 ),
99 SizedBox(
100 height: 20,
101 ),
102 Text(
103 'presented to:',
104 style: TextStyle(
105 color: Colors.grey[600],
106 ),
107 ),
108 SizedBox(
109 height: 30,
110 ),
111 Text(
112 name,
113 style: TextStyle(
114 fontSize: 24,
115 fontWeight: FontWeight.bold,
116 color: Colors.grey[800],
117 ),
118 ),
119 SizedBox(
120 height: 50,
121 ),
122 Row(
123 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
124 children: <Widget>[
125 // Actions to either download, view or share certificate goes here.
126 ],
127 ),
128 SizedBox(
129 height: 50,
130 )
131 ],
132 ),
133 ),
134 ),
135 ),
136 );
137 }
138}
1Row(
2 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
3 children: <Widget>[
4 FlatButton(
5 onPressed: () {
6 // Download
7 },
8 child: Icon(Icons.file_download),
9 ),
10 FlatButton(
11 onPressed: () {
12 // View
13 },
14 child: Icon(Icons.open_in_new),
15 ),
16 FlatButton(
17 onPressed: () {
18 // Share
19 },
20 child: Icon(Icons.share),
21 ),
22 ],
23)
1FlatButton(
2 onPressed: () {
3 pdfGenerator(name);
4 Scaffold.of(context).showSnackBar(
5 SnackBar(
6 content: Text('$name.pdf downloaded'),
7 action: SnackBarAction(
8 label: 'View',
9 onPressed: () async {
10 String downloadPath =
11 await getApplicationDocumentsDirectoryPath();
12 Navigator.pushNamed(context, '/viewer',
13 arguments: {
14 'view': '$downloadPath/$name.pdf'
15 });
16 },
17 ),
18 ),
19 );
20 },
21 child: Icon(Icons.file_download),
22)
1import 'dart:io';
2
3import 'package:flutter/material.dart';
4
5import 'package:pdf/pdf.dart';
6import 'package:pdf/widgets.dart' as pdf;
7import 'package:printing/printing.dart';
8import 'package:path_provider/path_provider.dart';
9
10Future<void> pdfGenerator(name) async {
11 final _pdf = pdf.Document();
12 final _assetImage = await pdfImageFromImageProvider(
13 pdf: _pdf.document,
14 image: AssetImage(
15 'assets/images/account.png',
16 ),
17 );
18 _pdf.addPage(
19 pdf.Page(
20 pageFormat: PdfPageFormat.a4,
21 build: (context) => pdf.Center(
22 child: pdf.Container(
23 margin: pdf.EdgeInsets.all(16),
24 width: double.maxFinite,
25 color: PdfColors.deepPurple50,
26 child: pdf.Column(
27 mainAxisAlignment: pdf.MainAxisAlignment.center,
28 crossAxisAlignment: pdf.CrossAxisAlignment.center,
29 children: [
30 pdf.SizedBox(
31 height: 50,
32 ),
33 pdf.Container(
34 width: 160,
35 height: 160,
36 decoration: pdf.BoxDecoration(
37 shape: pdf.BoxShape.circle,
38 color: PdfColors.deepPurple200,
39 ),
40 child: pdf.Image(_assetImage),
41 ),
42 pdf.SizedBox(
43 height: 50,
44 ),
45 pdf.Text(
46 'certificate of completion',
47 style: pdf.TextStyle(
48 fontSize: 22,
49 color: PdfColors.grey700,
50 ),
51 ),
52 pdf.SizedBox(
53 height: 20,
54 ),
55 pdf.Text(
56 'presented to:',
57 style: pdf.TextStyle(
58 color: PdfColors.grey600,
59 ),
60 ),
61 pdf.SizedBox(
62 height: 30,
63 ),
64 pdf.Text(
65 name,
66 style: pdf.TextStyle(
67 fontSize: 24,
68 fontWeight: pdf.FontWeight.bold,
69 color: PdfColors.grey800,
70 ),
71 ),
72 ],
73 ),
74 ),
75 ),
76 ),
77 );
78 var path = await getApplicationDocumentsDirectory();
79 File('${path.path}/$name.pdf').writeAsBytesSync(_pdf.save());
80}
81
82Future<String> getApplicationDocumentsDirectoryPath() async {
83 final Directory applicationDocumentsDirectory =
84 await getApplicationDocumentsDirectory();
85 return applicationDocumentsDirectory.path;
86}
1FlatButton(
2 onPressed: () async {
3 String downloadPath =
4 await getApplicationDocumentsDirectoryPath();
5 if (File('$downloadPath/$name.pdf').existsSync()) {
6 Navigator.pushNamed(context, '/viewer',
7 arguments: {'view': '$downloadPath/$name.pdf'});
8 } else {
9 Scaffold.of(context).showSnackBar(
10 SnackBar(
11 content: Text('$name.pdf File Not Found'),
12 action: SnackBarAction(
13 label: 'Download',
14 onPressed: () {
15 pdfGenerator(name);
16 Scaffold.of(context).showSnackBar(
17 SnackBar(
18 content: Text('$name.pdf downloaded'),
19 action: SnackBarAction(
20 label: 'View',
21 onPressed: () async {
22 String downloadPath =
23 await getApplicationDocumentsDirectoryPath();
24 Navigator.pushNamed(
25 context, '/viewer', arguments: {
26 'view': '$downloadPath/$name.pdf'
27 });
28 },
29 ),
30 ),
31 );
32 },
33 ),
34 ),
35 );
36 }
37 },
38 child: Icon(Icons.open_in_new),
39)
1FlatButton(
2 onPressed: () async {
3 String downloadPath =
4 await getApplicationDocumentsDirectoryPath();
5 if (File('$downloadPath/$name.pdf').existsSync()) {
6 ShareExtend.share(
7 File('$downloadPath/$name.pdf').path, 'file');
8 } else {
9 Scaffold.of(context).showSnackBar(
10 SnackBar(
11 content: Text('$name.pdf File Not Found'),
12 action: SnackBarAction(
13 label: 'Download',
14 onPressed: () {
15 pdfGenerator(name);
16 Scaffold.of(context).showSnackBar(
17 SnackBar(
18 content: Text('$name.pdf downloaded'),
19 action: SnackBarAction(
20 label: 'View',
21 onPressed: () async {
22 String downloadPath =
23 await getApplicationDocumentsDirectoryPath();
24 Navigator.pushNamed(
25 context, '/viewer', arguments: {
26 'view': '$downloadPath/$name.pdf'
27 });
28 },
29 ),
30 ),
31 );
32 },
33 ),
34 ),
35 );
36 }
37 },
38 child: Icon(Icons.share),
39)
ResultScreen Flow
1import 'package:flutter/material.dart';
2
3import 'package:pdf_viewer_plugin/pdf_viewer_plugin.dart';
4
5class ViewerScreen extends StatelessWidget {
6 @override
7 Widget build(BuildContext context) {
8 Map<String, dynamic> view = ModalRoute.of(context).settings.arguments;
9 return Scaffold(
10 appBar: AppBar(
11 title: Text('Certificate'),
12 ),
13 body: PdfViewer(
14 filePath: view['view'],
15 ),
16 );
17 }
18}
ViewerScreen Flow
This is a WIP project. We’re planning to add support for more file formats, the ability to use a URL of spreadsheets for simplicity, and certificate designer for customization.
You can check out the source code of the app here. Feel free to contribute to the project by creating issues or sending pull-requests.
That’s it for this one. Thank you for reading this
Add this to the end - Also Read: Creating a Flutter-based Code Screenshot Generator (Platypus)