Prevent Concurrent Access to File in Dart - flutter

I am creating a disk cache and I faced problem while running flutter tests cause by json.decode() but I found that the problem is that the same file is being read twice at the same time.
Here is the code for the disk cache class:
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:the_chef/repositories/cache/base_cache.dart';
class DiskCache<V> extends BaseCache<List<String>, V> {
String directoryPath;
DiskCache({
BaseCache<List<String>, V> other,
}) {
next = other;
}
Future<V> _get(List<String> path) async {
print("get $path");
var val;
var file;
file = await _exists(path);
if (file != null) {
print("start");
var str = await file.readAsString();
val = json.decode(str);
print("end get $path");
return val;
}
val ??= next?.get(path);
if (val != null) {
await _createFile(file);
var bytes = json.encode(val);
await file.writeAsString(bytes);
}
if(val == null) {
print("notfound");
}
return val;
}
Future<V> get(List<String> path) async {
var x = await _get(path);
return x;
}
#override
Future<void> set(List<String> path, V value) async {
print("set $path");
final file = await _createFileFromPath(path);
var bytes = json.encode(value);
await file.writeAsString(bytes);
next?.set(path, value);
print("end set $path");
}
#override
Future<void> evict(List<String> path) async {
print("evict $path");
final file = await _localFile(path);
if (await file.exists()) {
await file.delete();
} else {
await file.delete(recursive: true);
}
await next?.evict(path);
}
Future<File> _exists(List<String> path) async {
var file = await _localFile(path);
return (await file.exists()) ? file : null;
}
Future<File> _localFile(List<String> filePath) async {
final path = await _localPath();
return File('$path/${filePath.join('/')}');
}
Future<String> _localPath() async {
if (directoryPath != null) {
return directoryPath;
}
directoryPath = (await getApplicationDocumentsDirectory()).path;
return directoryPath;
}
Future<File> _createFileFromPath(List<String> path) async {
var file = await _localFile(path);
await _createFile(file);
return file;
}
Future<void> _createFile(File file) async {
file.createSync(recursive: true);
}
}
It would be very great if you have any suggestion of how to improve the cache or have any other advices.
Thanks in advance.

Related

Cannot get the download link after uploading files to firebase storage Flutter

so this is the my file picking and file upload code
class Storage with ChangeNotifier {
PlatformFile? pickedFile;
UploadTask? uploadTask;
Future uploadFile() async {
final path = 'files/${pickedFile!.name}.png';
final file = File(pickedFile!.path!);
final ref = FirebaseStorage.instance.ref().child(path);
ref.putFile(file);
try {
final snapshot = await uploadTask!.whenComplete(() {});
final urlDownload = await snapshot.ref.getDownloadURL();
print(urlDownload);
} catch (e) {
print("this is the error $e " );
}
}
void pickFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path!);
pickedFile = result.files.first;
} else {
print("no image picked");
}}}
the code works for upload the image but after that i didnt get any download link, the error is "Null check operator used on a null value" i dont know how to fix it, im still new in this topic, help please
i got the answer, need to change the uploadFile method to this
Future uploadFile() async {
final path = 'files/${pickedFile!.name}.png';
final file = File(pickedFile!.path!);
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child(path);
UploadTask uploadTask = ref.putFile(file);
uploadTask.then((res) {
res.ref.getDownloadURL();
});
try {
final snapshot = await uploadTask.whenComplete(() {});
final urlDownload = await snapshot.ref.getDownloadURL();
print(urlDownload);
} catch (e) {
print("this is the error $e " );
}
}

Flutter - How to save and play a recorded audio file?

I, for the life of me, can't figure this out. All I am trying to do is record an audio (as in a sound/voice recorder) and later be able to play it.
Recorder class:
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
//String _pathToAudio = '/sdcard/myAudio.aac';
String _fileName = 'myAudio.aac';
String _path = "/storage/emulated/0";
class Recorder {
FlutterSoundRecorder? _recorder;
bool _isRecorderInitialized = false;
bool get isRecording => _recorder!.isRecording;
Future init() async {
_recorder = FlutterSoundRecorder();
//final directory = "/sdcard/downloads/";
//Directory? extStorageDir = await getExternalStorageDirectory();
//String _path = directory.path;
final status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('Recording permission required.');
}
await _recorder!.openAudioSession();
_isRecorderInitialized = true;
}
void _writeFileToStorage() async {
File audiofile = File('$_path/$_fileName');
Uint8List bytes = await audiofile.readAsBytes();
audiofile.writeAsBytes(bytes);
}
void dispose() {
_recorder!.closeAudioSession();
_recorder = null;
_isRecorderInitialized = false;
}
Future record() async {
if (!_isRecorderInitialized) {
return;
}
print('recording....');
await _recorder!.startRecorder(
toFile: '$_fileName',
//codec: Codec.aacMP4,
);
}
Future stop() async {
if (!_isRecorderInitialized) {
return;
}
await _recorder!.stopRecorder();
_writeFileToStorage();
print('stopped....');
}
Future toggleRecording() async {
if (_recorder!.isStopped) {
await record();
} else {
await stop();
}
}
}
Currently the error I am getting is "Cannot open file, path = '/storage/emulated/0/myAudio.aac' (OS Error: No such file or directory, errno = 2)".
I am using flutter_sound
Try initializing your file path by using path_provider.
Add these 2 lines to the beginning of your init function.
final directory = await getApplicationDocumentsDirectory();
_path = directory.path; // instead of "/storage/emulated/0"
Not sure how you're trying to access and play that file but on my end it at least cleared the error.
String _fileName = 'Recording_';
String _fileExtension = '.aac';
String _directoryPath = '/storage/emulated/0/SoundRecorder';
This is what I have currently and it's working.
void _createFile() async {
var _completeFileName = await generateFileName();
File(_directoryPath + '/' + _completeFileName)
.create(recursive: true)
.then((File file) async {
//write to file
Uint8List bytes = await file.readAsBytes();
file.writeAsBytes(bytes);
print(file.path);
});
}
void _createDirectory() async {
bool isDirectoryCreated = await Directory(_directoryPath).exists();
if (!isDirectoryCreated) {
Directory(_directoryPath).create()
// The created directory is returned as a Future.
.then((Directory directory) {
print(directory.path);
});
}
}
void _writeFileToStorage() async {
_createDirectory();
_createFile();
}

A way to serve a file through http without loading it into ram? Help. Flutter / Dart

So I've created an app that creates a static file server using the httpserver API and I used VirtualDirectory to generate a list of items in a specified directory on Android. The app is working but whenever there is a large file it crashes and from what I understand it is because it loads way too much data into the memory.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:io';
import 'package:http_server/http_server.dart';
import 'package:path_provider/path_provider.dart';
void main(){
runApp(MaterialApp(
home:HomePage(),
));
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static Future<String> get getFilePath async{
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
static Future<File> getFile(String fileName) async{
final path = await getFilePath;
return File('$path/$fileName');
}
static Future<File> saveToFile(var data, String filePath, String fileName) async{
print("filePath+fileName: " + filePath+fileName);
final file = await getFile(filePath+fileName);
return file.writeAsString(data);
}
static Future<void> createDir(String filePath) async{
final path = await getFilePath;
Directory('$path/$filePath').create(recursive: true);
}
static Future<String> readFromFile(String filePath, String fileName) async{
try{
final file = await getFile('$filePath$fileName');
String fileContents = await file.readAsString();
return fileContents;
}catch(e){
final assetFile = await rootBundle.loadString('assets/$filePath$fileName');
if(filePath == ''){
await saveToFile(assetFile, '$filePath', '$fileName');
}else{
await createDir(filePath);
await saveToFile(assetFile, '$filePath', '$fileName');
}
print('copying the file from assets');
return '';
}
}
String data = '';
String rootDirPathStr;
assetFolder() async{
final v = await getFilePath;
Directory('$v').create(recursive: true);
await createDir('dist/css');
await createDir('dist/js');
await readFromFile('','index.html');
await readFromFile('dist/css','/style.min.css');
await readFromFile('dist/js','/serverCom.js');
await readFromFile('dist/js','/main.js');
await readFromFile('dist/js','/files.js');
await readFromFile('dist/js','/index.json');
}
serverInit() async{
// setState(() {
// data = "Server running on IP : "+server.address.toString()+" On Port : "+server.port.toString();
// });
//getting the dir
final rootDir = await getApplicationDocumentsDirectory();
rootDirPathStr = rootDir.path;
print("rootDirPathStr: " + rootDirPathStr);
//getting the dir
HttpServer server = await HttpServer.bind(InternetAddress.anyIPv4, 8080);
print("Server running on IP : "+InternetAddress.anyIPv4.toString()+" On Port : "+server.port.toString());
VirtualDirectory rootVirDir = VirtualDirectory(rootDirPathStr)
..allowDirectoryListing = true;
VirtualDirectory userFilesVirDir = VirtualDirectory('/storage/emulated/0/IDM/')
..allowDirectoryListing = true;
// await userFilesVirDir.serve(server);
await for (HttpRequest request in server) {
String requestUriPath = request.uri.path;
String requestUriQuery = request.uri.query;
print('requestUriPath: $requestUriPath and requestUriQuery: $requestUriQuery');
if(requestUriPath == '/' && requestUriQuery == '') {
final path = await getFilePath;
await rootVirDir.serveFile(File('$path/index.html'), request);
}else if(requestUriQuery == 'file'){
print('file requested');
try{
await userFilesVirDir.serveRequest(request);
}catch(e){
print("error On file requested: $e");
}
}
else{
await rootVirDir.serveRequest(request);
}
}
}
#override
void initState() {
assetFolder();
serverInit();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child:Text('data'),
),
);
}
}
https://pub.dev/packages/shelf can return a ByteStream as the response... allowing virtually unlimited results. As reported in https://pub.dev/documentation/shelf/latest/shelf/Response/Response.ok.html:
body is the response body. It may be either a String, a List<int>, a Stream<List<int>>, or null to indicate no body.
So you can just open a stream on a large file, and hand it (perhaps through a StreamTransformer) directly in the response. I don't think http_server can do that.

readAsBytesSync is incomplete

Since I can't convert convert a file directly from url (e.g File(url)).
I am downloading the file and then use the temp file path.
I tried different files : images, pdfs and it's still incomplete.
Am I doing something wrong here?
Future<String> downloadFile() async {
print(imgUrl);
Dio dio = Dio();
try {
var dir = await getApplicationDocumentsDirectory();
await dio.download(imgUrl, "${dir.path}/${widget.name}.pdf",
onReceiveProgress: (rec, total) {});
path = "${dir.path}/${widget.name}.pdf";
setState(() {
downloading = false;
progressString = "Completed";
});
if (path != null) {
List<int> imageBytes = File(path).readAsBytesSync();
print("NEW BYTE : $imageBytes");
}
} catch (e) {
print(e);
}
return path;
}
Checkout this solution:-
https://gist.github.com/Nitingadhiya/3e029e2475eeffac311ecd76f273941f
Uint8List? _documentBytes;
getPdfBytes() async {
_documentBytes = await http.readBytes(Uri.parse('https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf'));
return _documentBytes;
}
Future<void> readPDf() async {
//Load the existing PDF document.
Uint8List documentInBytes = await getPdfBytes();
final PdfDocument document = PdfDocument(inputBytes: documentInBytes);
//Get the existing PDF page.
final PdfPage page = document.pages[0];
//Draw text in the PDF page.
page.graphics.drawString('Hello World!', PdfStandardFont(PdfFontFamily.helvetica, 12), brush: PdfSolidBrush(PdfColor(0, 0, 0)), bounds: const Rect.fromLTWH(0, 0, 150, 20));
//Save the document.
final List<int> bytes = await document.save(); //document.saveSync();
await saveAndLaunchFile(bytes, 'Invoice.pdf');
//Dispose the document.
document.dispose();
}
Future<void> saveAndLaunchFile(List<int> bytes, String fileName) async {
//Get the storage folder location using path_provider package.
String? path;
if (Platform.isAndroid || Platform.isIOS || Platform.isLinux || Platform.isWindows) {
final Directory directory = await path_provider.getApplicationSupportDirectory();
path = directory.path;
} else {
path = await PathProviderPlatform.instance.getApplicationSupportPath();
}
final File file = File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName');
await file.writeAsBytes(bytes, flush: true);
if (Platform.isAndroid || Platform.isIOS) {
//Launch the file (used open_file package)
// await open_file.OpenFile.open('$path/$fileName');
} else if (Platform.isWindows) {
await Process.run('start', <String>['$path\\$fileName'], runInShell: true);
} else if (Platform.isMacOS) {
await Process.run('open', <String>['$path/$fileName'], runInShell: true);
} else if (Platform.isLinux) {
await Process.run('xdg-open', <String>['$path/$fileName'], runInShell: true);
}
}

Mock getExternalStorageDirectory on Flutter

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.