Mock getExternalStorageDirectory on Flutter - 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.

Related

How to download a file on flutter?

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")
}
}

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();
}

Mokito wont return a stubbed value

I started TDD recently and it has slowed my progress, I m trying to stub a return value with Mockito, but i keep getting "null is not a subtype of Future-dynamic' not sure what i did wrong, here is my setup
class ImageSelectorMock extends Mock implements ImageSelector {}
getImageSelectorMock({logo}) {
_unregisterIfRegistered<ImageSelector>();
var imageSelector = ImageSelectorMock();
if (logo != null) {
when(imageSelector.businessLogo).thenReturn('string');
when(imageSelector.uploadImage(null, 'folder'))
.thenAnswer((realInvocation) async =>
Future.value('send this')
);
}
locator.registerSingleton<ImageSelector>(imageSelector);
return imageSelector;
}
setupService() {
//getAuthServiceMock();
//getFirestoreServiceMock();
//getNavigationServiceMock();
getImageSelectorMock();
}
void _unregisterIfRegistered<T extends Object>() {
if (locator.isRegistered<T>()) {
locator.unregister<T>();
}
}
getImageSelectorMock takes an optional arg "logo" to check if imageSelector.uploadImage is called. Also, I'm using a locator/get_it package
group('RegistrationviewmodelTest -', () {
setUp(() => {setupService()});
test('when business logo is NOT null => uplaodImage should be called', () async {
// final navigation = getNavigationServiceMock();
// final fireStore = getFirestoreServiceMock();
final imageSelector = await getImageSelectorMock(logo: "test");
final model = RegistraionViewModel();
await model.createBusiness({
'title': "text",
'description': "text",
});
verify(imageSelector.uploadImage('', 'upload'));
// verify(navigation.navigateTo());
});
});
// uploadImage does
Future uploadImage(file, folder) async {
// String fileName = basename(_imageFile.path);
String fileName = basename(file.path);
final firebaseStorageRef =
FirebaseStorage.instance.ref().child('$folder/$fileName');
final uploadTask = firebaseStorageRef.putFile(file);
final taskSnapshot = await uploadTask;
return taskSnapshot.ref.getDownloadURL().then((value) => value);
}
uploadImage is just an image picker that returns download Url from fire storege.
Thanks for helping out!!

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.

Prevent Concurrent Access to File in Dart

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.