Reputation: 55
I am working on a flutter project where I need to create a Digital Id for user and save a pdf of it. So for that I used "PDF" package. Currently I am just creating a PDF with text "Dart is awesome". But I am getting an error when I tap the Download Id button and select the folder location where I want to save my pdf. The error is :
E/flutter (31521): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PathAccessException: Cannot open file, path = '/storage/emulated/0/1Cd/example.pdf' (OS Error: Permission denied, errno = 13) E/flutter (31521): #0 _checkForErrorResponse (dart:io/common.dart:55:9) E/flutter (31521): #1 _File.open. (dart:io/file_impl.dart:381:7) E/flutter (31521): E/flutter (31521): #2 DigitalId.build. (package:rightclaim/screens/digital_id/digital_id.dart:241:9) E/flutter (31521): E/flutter (31521):
I have given all permissions: main/AndroidManifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rightclaim">
<application
android:label="rightclaim"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>
debug/AndroidManifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rightclaim">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
profile/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rightclaim">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
Here is my code for digital_id.dart You can skip to CommonElevatedButton.buttonWithoutIcon(...) where I am doing the pdf work.
import 'dart:io';
import "package:file_picker/file_picker.dart";
import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_svg/svg.dart";
import 'package:path/path.dart' as path;
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import "package:permission_handler/permission_handler.dart";
import "package:right_claim_common/color_constants.dart";
import "package:right_claim_common/common_appbar.dart";
import "package:right_claim_common/common_elevated_buttons.dart";
import "package:right_claim_common/common_textsyles.dart";
import "../../user_data/user_data.dart";
import "userid_details.dart";
class DigitalId extends StatelessWidget {
UserDigitalId userId = UserDigitalId(
fullName.toString(),
"XXXX XXXX XXXX XXXX",
"12455",
"9568743367",
"[email protected]",
"10/10/2022",
"Raj Nagar Colony Shahdara Agar,Shahdara",
"6152 7298 8282",
"AB+");
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CommonAppBar(title: "Digital Id"),
body: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.all(20.0),
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.vertical(top: Radius.circular(10)),
color: ColorConstants.primaryColor,
),
child: Column(
children: [
Column(
children: [
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular(10)),
color: ColorConstants.tertiaryColor,
),
child: Row(
children: [
Text(
"Right Claim",
style: CommonTextStyles.text20_700()!
.copyWith(
color: ColorConstants.primaryColor),
),
Spacer(),
ClipOval(
child: SvgPicture.asset(
'assets/logo2.svg',
width: 50,
height: 50,
),
),
],
)),
Container(
padding: EdgeInsets.all(16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
'${userId.name}',
style: CommonTextStyles.text20_800()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 20,
),
Text(
'VHDM Number: ${userId.vhdmNumber}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'Member ID: ${userId.memberId}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'Phone Number: ${userId.phoneNumber}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'Email: ${userId.email}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'DOB: ${userId.dob}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'Address: ${userId.address}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'Adhar Card: ${userId.adharCard}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 10,
),
Text(
'Blood Group: ${userId.bloodGroup}',
style: CommonTextStyles.text14_700()!
.copyWith(color: Colors.black),
),
SizedBox(
height: 20,
),
// Add more user data here
],
),
),
SizedBox(
width:
16.0), // Add some spacing between the text and image
Stack(
alignment: Alignment.bottomCenter,
children: [
Container(
width: 77,
height: 100,
decoration: BoxDecoration(
// color: Colors.red,
image: DecorationImage(
image:
AssetImage('assets/logo.png'),
fit: BoxFit.cover,
),
),
),
Container(
width: 89,
height: 20,
color: ColorConstants.quaternaryColor,
alignment: Alignment.center,
child: Text(
"Member",
style: TextStyle(color: Colors.black),
),
)
]),
],
),
),
],
),
],
),
),
SizedBox(
height: 40,
),
CommonElevatedButton.buttonWithoutIcon(
title: "Download Id",
//the function where i am trying to create a pdf and save it.
onButtonPressed: () async {
final pdf = pw.Document();
final font =
await rootBundle.load("assets/Roboto/Roboto-Black.ttf");
final ttf = pw.Font.ttf(font);
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
build: (pw.Context context) {
return pw.Center(
child: pw.Text('Dart is awesome',
style: pw.TextStyle(font: ttf, fontSize: 40)),
);
},
));
// Request necessary permissions
final status = await Permission.storage.request();
if (status.isGranted) {
// Get the directory path
final directoryPath =
await FilePicker.platform.getDirectoryPath();
if (directoryPath != null) {
final directory = Directory(directoryPath);
final fileName = 'example.pdf';
final filePath = path.join(directory.path, fileName);
final file = File(filePath);
await file.writeAsBytes(await pdf.save());
print('PDF saved to $filePath');
} else {
print('No folder selected');
}
} else {
print('Permission denied');
}
},
)
])),
));
}
}
Upvotes: 2
Views: 5623
Reputation: 31
I resolved the issue by changing the android:requestLegacyExternalStorage
field within the <application>
tag in the AndroidManifest from false
to true
. After recompiling from scratch, it worked. If it doesn't, try uninstalling the app and compiling again.
It's important to note that I have two test devices, one running Android 10 and the other Android 11. The Android 11 device works regardless of the value of android:requestLegacyExternalStorage
. However, the Android 10 device consistently encountered errors, which I resolved using the method mentioned above.
Upvotes: 2
Reputation: 301
//Not sure if you have to save it in that file directory. Only got that issue when I didn't have .pdf at the end of the filename I was trying to store so it wouldn't know what kind of document its actually working with.
class DownloadPDF extends StatefulWidget {
const DownloadPDF({super.key
});
@override
State<DownloadPDF> createState() => _DownloadPDFState();
}
class _DownloadPDFState extends State<DownloadPDF> {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => createPDF(), child: const Icon(Icons.download)),
);
}
Future<Uint8List?> createPDF() async {
final pdf = pw.Document();
List headers = [
"Property",
"Value",
];
List values = [CustomRow(name: "testing", value: "value")
];
final data = values.map((e) => [e.name, e.value
]).toList();
pdf.addPage(pw.MultiPage(
build: (context) => [
pw.Column(children: [
pw.SizedBox(height: 100),
pw.Text(
'Dart is awesome',
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
]),
pw.SizedBox(height: 100),
pw.TableHelper.fromTextArray(
headers: headers,
data: data,
tableWidth: pw.TableWidth.max,
border: pw.TableBorder.all(
color: PdfColors.black,
width: 1.0,
),
),
]));
List<int> bytes = await pdf.save();
saveAndLaunchFile(bytes, 'PDF Document.pdf');
return pdf.save();
}
Future<void> saveAndLaunchFile(List<int> bytes, String fileName) async {
bool dirDownloadExists = true;
String androidDirectory;
androidDirectory = "/storage/emulated/0/Download/";
dirDownloadExists = await Directory(androidDirectory).exists();
if (dirDownloadExists) {
androidDirectory = "/storage/emulated/0/Download";
} else {
androidDirectory = "/storage/emulated/0/Downloads";
}
final path = Platform.isAndroid
? androidDirectory
: (await getApplicationDocumentsDirectory()).path;
final file = File('$path/$fileName');
await file.writeAsBytes(bytes, flush: true);
OpenFile.open('$path/$fileName');
}
}
class CustomRow {
final String name;
final String value;
CustomRow({required this.name, required this.value
});
}
//IF YOU WANT THE FILE TO OPEN INSTANTLY AFTER DOWNLOADING YOU COULD USE OPENFILE PLUS BUT SOME THINGS WILL BE NEEDED TO BE ADDED TO YOUR ANDROID MANIFEST
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="xxx.xxx.xxxxx">
<application>
...
<provider android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"
tools:replace="android:resource" />
</provider>
</application>
ENSURE TO REPLACE xxx.xxx.xxxxx WITH UR PACKAGE NAME
This works for both iOS and android
hope this helps...
Upvotes: 0