Is there a way or a package in Flutter that downloads a file directly to downloads folder for Android and iOS from a direct URL for example: https://******/image.jpg, without any user overhead just a click and download..
Yes use following packages to completely achieve it :
dio: ^4.0.0
path_provider: ^2.0.2
permission_handler: ^8.0.0+2
then use following code :
define variables:
late String _localPath;
late bool _permissionReady;
late TargetPlatform? platform;
get device platform in initState()
#override
void initState() {
super.initState();
if (Platform.isAndroid) {
platform = TargetPlatform.android;
} else {
platform = TargetPlatform.iOS;
}
}
for check and requesting device's permissions :
Future<bool> _checkPermission() async {
if (platform == TargetPlatform.android) {
final status = await Permission.storage.status;
if (status != PermissionStatus.granted) {
final result = await Permission.storage.request();
if (result == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}
prepare for finding localpath :
Future<void> _prepareSaveDir() async {
_localPath = (await _findLocalPath())!;
print(_localPath);
final savedDir = Directory(_localPath);
bool hasExisted = await savedDir.exists();
if (!hasExisted) {
savedDir.create();
}
}
Future<String?> _findLocalPath() async {
if (platform == TargetPlatform.android) {
return "/sdcard/download/";
} else {
var directory = await getApplicationDocumentsDirectory();
return directory.path + Platform.pathSeparator + 'Download';
}
}
and at last for downloading file:
InkWell(
onTap: () async {
_permissionReady = await _checkPermission();
if (_permissionReady) {
await _prepareSaveDir();
print("Downloading");
try {
await Dio().download("https://******/image.jpg",
_localPath + "/" + "filename.jpg");
print("Download Completed.");
} catch (e) {
print("Download Failed.\n\n" + e.toString());
}
}
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle, color: Colors.grey.withOpacity(0.5)),
padding: EdgeInsets.all(8),
child: Icon(Icons.download, color: Colors.black),
));
Make sure you have added required permissions in the AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
EDIT :
if your app directory is not shown in iOS files then add the below line in Info.plist
<key>UISupportsDocumentBrowser</key>
<true/>
You can use Dio package to download any file.
A powerful Http client for Dart, which supports Interceptors, Global
configuration, FormData, Request Cancellation, File downloading,
Timeout etc.
Example
import 'package:dio/dio.dart';
var dio = Dio();
response = await dio.download('https://******/image.jpg');
Related
How to download a .pdf file from an URL with POST Request having headers in Flutter?
i have a different pdf files for each user and i want the link to be different depend on the user.
You can use flutter_downloader plugin for downloading your files.
dependencies:
flutter_downloader: ^1.8.4
And also you can use this codebase for that:
import 'dart:io';
import 'dart:ui';
import 'package:fimber/fimber.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class DownloadingService {
static const downloadingPortName = 'downloading';
static Future<void> createDownloadTask(String url) async {
final _storagePermission = await _permissionGranted();
Fimber.d('Current storage permission: $_storagePermission');
if (!_storagePermission) {
final _status = await Permission.storage.request();
if (!_status.isGranted) {
Fimber.d('Permission wasnt granted. Cancelling downloading');
return;
}
}
final _path = await _getPath();
Fimber.d('Downloading path $_path');
if (_path == null) {
Fimber.d('Got empty path. Cannot start downloading');
return;
}
final taskId = await FlutterDownloader.enqueue(
url: url,
savedDir: _path,
showNotification: true,
// show download progress in status bar (for Android)
openFileFromNotification: true,
// click on notification to open downloaded file (for Android)
saveInPublicStorage: true);
await Future.delayed(const Duration(seconds: 1));
if (taskId != null) {
await FlutterDownloader.open(taskId: taskId);
}
}
static Future<bool> _permissionGranted() async {
return await Permission.storage.isGranted;
}
static Future<String?> _getPath() async {
if (Platform.isAndroid) {
final _externalDir = await getExternalStorageDirectory();
return _externalDir?.path;
}
return (await getApplicationDocumentsDirectory()).absolute.path;
}
static downloadingCallBack(id, status, progress) {
final _sendPort = IsolateNameServer.lookupPortByName(downloadingPortName);
if (_sendPort != null) {
_sendPort.send([id, status, progress]);
} else {
Fimber.e('SendPort is null. Cannot find isolate $downloadingPortName');
}
}
}
And in another page you can use this class to download your file:
final _receivePort = ReceivePort();
#override
void initState() {
super.initState();
IsolateNameServer.registerPortWithName(
_receivePort.sendPort, DownloadingService.downloadingPortName);
FlutterDownloader.registerCallback(DownloadingService.downloadingCallBack);
_receivePort.listen((message) {
Fimber.d('Got message from port: $message');
});
}
#override
void dispose() {
_receivePort.close();
super.dispose();
}
void _downloadFile() async {
try {
await DownloadingService.createDownloadTask(url.toString());
} catch (e) {
print("error")
}
}
i am uploading files to firebase cloud storage in background with workmanager in flutter, it works good when the app is in background but when the app killed by user the uploading process starts from beginning and also when the user disconnect internet and reconnect it starts uploading process from beginning.
Here is my upload function
Future<void> uploadVideo() async {
List<String> filesPath = [];
await Future.forEach(imgSource, (AssetEntity element) async {
File file = await element.file;
filesPath.add(file.path);
});
await Workmanager().registerOneOffTask(
"1",
uploadFileTask,
inputData: <String, dynamic>{
'filesPath': filesPath,
},
constraints: Constraints(
networkType: NetworkType.connected,
requiresBatteryNotLow: true,
),
backoffPolicy: BackoffPolicy.exponential,
existingWorkPolicy: ExistingWorkPolicy.keep,
);
}
// Here is callbackDispatcher function
void callbackDispatcher() {
Workmanager().executeTask((task, inputData) async {
switch (task) {
case uploadFileTask:
try {
List<dynamic> dynamicType = inputData["filesPath"];
List<String> filesPath =
dynamicType.map((e) => e.toString()).toList();
await Firebase.initializeApp();
int counter = 1;
List<String> downloadUrlLinks = [];
await Future.forEach(filesPath, (String filePath) async {
File file = File(filePath);
String fullPath = getRandomName(file.path);
String storagePath = "test/$fullPath";
print("Full path HM" + fullPath);
String downloadUrl = await CloudService.uploadFileWithProgressBar(
file: file,
filePath: storagePath,
maxLength: filesPath.length,
uploadedLength: counter,
);
downloadUrlLinks.add(downloadUrl);
counter++;
});
await NotificationService.finishedNotification(
title: 'Uploading files finished');
print("download link: " + downloadUrlLinks.toString());
downloadUrlLinks = [];
} catch (e) {
print("uploading error" + e.toString());
}
break;
}
return Future.value(true);
});
}
I have managed to make flutter downloader and the path provider work on android but it won't work on ios saying that this is a android only operation , what's wrong ?
here is the code :
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Download',
color: Colors.green,
icon: Icons.arrow_circle_down,
onTap: () async {
final status = await Permission.storage.request();
if (status.isGranted) {
final externalDir = await getExternalStorageDirectory();
final id = await FlutterDownloader.enqueue(
url: f.url,
savedDir: externalDir.path,
showNotification: true,
openFileFromNotification: true);
} else {
toast('Permission Denied');
}
},
),
For iOS, after having permission, you should use final externalDir = await getApplicationDocumentsDirectory() since getExternalStorageDirectory() is not supported for iOS.
You can do an OS check:
var externalDir;
if (Platform.isIOS) { // Platform is imported from 'dart:io' package
externalDir = await getApplicationDocumentsDirectory();
} else if (Platform.isAndroid) {
externalDir = await getExternalStorageDirectory();
}
I tried get user location when button clicked using flutter location package
Code
FloatingActionButton(
onPressed: () async {
await location
.hasPermission()
.then((PermissionStatus status) async {
if (_permissionGranted == PermissionStatus.denied) {
await location.requestPermission().then(
(PermissionStatus requestStatus) async {
print("PERMISSION TAKEN");
await location
.getLocation()
.then((LocationData userLocation) {
print("LOCATION TAKEN 1");
print(userLocation);
});
},
);
} else {
await location
.getLocation()
.then((LocationData userLocation) {
print("LOCATION TAKEN 2");
print(userLocation);
});
}
});
},
child: Icon(Icons.place, color: Colors.white),
backgroundColor: Colors.green,
),
When user clicked to button requested permission to location and in my code after permission granted work this part of my code
print("PERMISSION TAKEN");
But then not work this part of code
await location
.getLocation()
.then((LocationData userLocation) {
print("LOCATION TAKEN 1");
print(userLocation);
});
you can also get the location by following:---------
pubspec.yaml file under dependencies:----
dependencies:
location: ^3.0.0
Android, add this permission in AndroidManifest.xml :
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
iOS, you have to add this permission in Info.plist :
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
Warning: there is a currently a bug in iOS simulator in which you have to manually select a Location several in order for the Simulator to actually send data. Please keep that in mind when testing in iOS simulator.
main.dart
import 'package:flutter/material.dart';
import 'package:location/location.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter GPS',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: GetLocationPage(),
);
}
}
class GetLocationPage extends StatefulWidget {
#override
_GetLocationPageState createState() => _GetLocationPageState();
}
class _GetLocationPageState extends State<GetLocationPage> {
LocationData _currentLocation;
Location _locationService = new Location();
#override
void initState() {
// TODO: implement initState
super.initState();
_getLocation().then((value) {
setState(() {
_currentLocation = value;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_currentLocation == null
? CircularProgressIndicator()
: Text("Location:" +
_currentLocation.latitude.toString() +
" " +
_currentLocation.longitude.toString()),
],
),
),
);
}
Future<LocationData> _getLocation() async {
LocationData currentLocation;
try {
currentLocation = await _locationService.getLocation();
} catch (e) {
currentLocation = null;
}
return currentLocation;
}
}
ss:---
In order to request location, you should always check manually Location Service status and Permission status. refer this https://pub.dev/packages/location
i get current location as below , try to use like this
Future<LatLng> getUserLocation() async {
LocationData currentLocation;
var location = new Location();
bool _serviceEnabled;
PermissionStatus _permissionGranted;
LocationData _locationData;
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
}
}
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.DENIED) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.GRANTED) {
}
}
// Platform messages may fail, so we use a try/catch PlatformException.
try {
currentLocation = await location.getLocation();
final lat = currentLocation.latitude;
final lng = currentLocation.longitude;
final coordinates = new Coordinates(lat, lng);
var addresses =
await Geocoder.local.findAddressesFromCoordinates(coordinates);
var first = addresses.first;
updateLocation(lat, lng, first.postalCode, first.locality,
first.countryName, first.adminArea, first.addressLine);
final center = LatLng(lat, lng);
return center;
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
showToast("LOCATION PERMISSION DENIED",
gravity: Toast.TOP, duration: Toast.LENGTH_LONG);
}
currentLocation = null;
}
}
And don't forget to add this permission in Info.plist
NSLocationWhenInUseUsageDescription
NSLocationAlwaysUsageDescription
I´m trying mock the function getExternalStorageDirectory, but alway return the error:
"UnsupportedError (Unsupported operation: Functionality only available on Android)"
I´m using the method setMockMethodCallHandler to mock it, but the erro occurs before the method be called.
test method
test('empty listReportModel', () async {
TestWidgetsFlutterBinding.ensureInitialized();
final directory = await Directory.systemTemp.createTemp();
const MethodChannel channel =
MethodChannel('plugins.flutter.io/path_provider');
channel.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'getExternalStorageDirectory') {
return directory.path;
}
return ".";
});
when(Modular.get<IDailyGainsController>().listDailyGains())
.thenAnswer((_) => Future.value(listDailyGainsModel));
when(Modular.get<IReportsController>().listReports())
.thenAnswer((_) => Future.value(new List<ReportsModel>()));
var configurationController = Modular.get<IConfigurationController>();
var response = await configurationController.createBackup();
expect(response.filePath, null);
});
method
Future<CreateBackupResponse> createBackup() async {
CreateBackupResponse response = new CreateBackupResponse();
var dailyGains = await exportDailyGainsToCSV();
var reports = await exportReportsToCSV();
final Directory directory = await getApplicationDocumentsDirectory();
final Directory externalDirectory = await getExternalStorageDirectory();
if (dailyGains.filePath != null && reports.filePath != null) {
File dailyGainsFile = File(dailyGains.filePath);
File reportsFile = File(reports.filePath);
var encoder = ZipFileEncoder();
encoder.create(externalDirectory.path + "/" + 'backup.zip');
encoder.addFile(dailyGainsFile);
encoder.addFile(reportsFile);
encoder.close();
await _removeFile(dailyGainsFile.path);
await _removeFile(reportsFile.path);
response.filePath = directory.path + "/" + 'backup.zip';
}
return response;
}
As in pub.dev stated:
path_provider now uses a PlatformInterface, meaning that not all
platforms share the a single PlatformChannel-based implementation.
With that change, tests should be updated to mock PathProviderPlatform
rather than PlatformChannel.
That means somewhere in your test directory you create the following mock class:
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
const String kTemporaryPath = 'temporaryPath';
const String kApplicationSupportPath = 'applicationSupportPath';
const String kDownloadsPath = 'downloadsPath';
const String kLibraryPath = 'libraryPath';
const String kApplicationDocumentsPath = 'applicationDocumentsPath';
const String kExternalCachePath = 'externalCachePath';
const String kExternalStoragePath = 'externalStoragePath';
class MockPathProviderPlatform extends Mock
with MockPlatformInterfaceMixin
implements PathProviderPlatform {
Future<String> getTemporaryPath() async {
return kTemporaryPath;
}
Future<String> getApplicationSupportPath() async {
return kApplicationSupportPath;
}
Future<String> getLibraryPath() async {
return kLibraryPath;
}
Future<String> getApplicationDocumentsPath() async {
return kApplicationDocumentsPath;
}
Future<String> getExternalStoragePath() async {
return kExternalStoragePath;
}
Future<List<String>> getExternalCachePaths() async {
return <String>[kExternalCachePath];
}
Future<List<String>> getExternalStoragePaths({
StorageDirectory type,
}) async {
return <String>[kExternalStoragePath];
}
Future<String> getDownloadsPath() async {
return kDownloadsPath;
}
}
And build your tests the following way:
void main() {
group('PathProvider', () {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
PathProviderPlatform.instance = MockPathProviderPlatform();
// This is required because we manually register the Linux path provider when on the Linux platform.
// Will be removed when automatic registration of dart plugins is implemented.
// See this issue https://github.com/flutter/flutter/issues/52267 for details
disablePathProviderPlatformOverride = true;
});
test('getTemporaryDirectory', () async {
Directory result = await getTemporaryDirectory();
expect(result.path, kTemporaryPath);
});
}
}
Here you can check out the complete example.