Downloading PDF file in the user download directory on Android is a very common feature, a lot of app uses PDF viewing and downloading feature.
I hoped that flutter plugins could solve my problem but they didn’t. Fortunately, it was working great on iOS but it was not on android. So I decided to use my old java code in Flutter using Method Channel.
First I tried two Flutter plugins one for permission and one for creating a directory.
flutter pub add path_provider
flutter pub add permission_handler
Request permission was very easy and it worked on both iOS and Android
Future<bool> _requestWritePermission() async {
await Permission.storage.request();
var r = await Permission.storage.request();
return r.isGranted;
}
Since it was working on iOS, I decided to leave that code and separate platform code by importing ‘dart:io’ show Platform. Use this code on your onTap action.
static const channel = MethodChannel('com.flutterframework/test');
bool hasPermission = await _requestWritePermission();
if (hasPermission) {
if (Platform.isAndroid) {
final int downloader = await channel.invokeMethod(
'download',
<String, String>{'title': title, 'pdf': pdf},
);
print(downloader);
}
if (Platform.isIOS) {
var dir = await getApplicationDocumentsDirectory();
if (dir != null) {
String saveName = "$title.pdf";
String savePath = "${dir.path}/$saveName";
print(savePath);
try {
await Dio().download(pdf, savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
var percentage = (received / total * 100);
print(percentage);
if (percentage >= 100.0) {
ScaffoldMessenger.of(context).showSnackBar(
snackMessage(
'Download completed, please open Files app.'));
}
}
});
print("File is saved to download folder.");
} on DioError catch (e) {
ScaffoldMessenger.of(context).showSnackBar("Download error");
}
}
}
} else {
ScaffoldMessenger.of(context).showSnackBar(filePermissionError);
}
Finally, the Java code, create a Java class in your main directory.
package your_package;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
public class DownloadHelper {
public static void downloadPDF(Context context, String title, String pdfUrl) {
try {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(pdfUrl));
request.setDescription("MyApp");
request.setTitle(title);
// in order for this if to run, you must use the android 3.2 to compile your app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
}
if (Build.VERSION.SDK_INT <= 17) {
request.setDestinationInExternalFilesDir(context, "Documents", "MyFolder/" + title + ".pdf");
} else {
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "MyFolder/" + title + ".pdf");
}
// get download service and enqueue file
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
Call that Java code from MainActivity, my MainActivity in Kotlin
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.flutterframework/test";
private lateinit var channel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
channel.setMethodCallHandler { call, result ->
if (call.method == "download") {
val pdfTitle = call.argument<String>("title")
val pdfUrl = call.argument<String>("pdf")
Log.d("TAG", pdfTitle.toString());
Log.d("TAG", pdfUrl.toString());
DownloadHelper.downloadPDF(this, pdfTitle, pdfUrl);
}
}
}
}