Created
March 27, 2025 04:45
-
-
Save mqhamdam/499019287da338d722d41c6e0e699c00 to your computer and use it in GitHub Desktop.
CSV File Service for interacting with CSV File used in project
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "dart:async"; | |
import "dart:developer"; | |
import "dart:io"; | |
import "dart:isolate"; | |
import "package:fpdart/fpdart.dart"; | |
import "package:motion_data_recorder/app/core_type_extensions.dart"; | |
import "package:motion_data_recorder/model/protocol.dart"; | |
import "package:mutex/mutex.dart"; | |
/// A service class to handle reading, writing, and deleting CSV files | |
/// containing [ReadProtocol] data. | |
/// | |
/// This class ensures thread-safe access using a [ReadWriteMutex], | |
/// and offloads file operations to isolates for performance. | |
class CSVFileService { | |
final _mutex = ReadWriteMutex(); | |
/// Gets the folder path for storing CSV files. | |
/// | |
/// The directory used is `/storage/emulated/0/Download/motion_data_recorder`. | |
/// If the folder doesn't exist, it will be created. | |
Future<String> get _folderPath async { | |
try { | |
final appDir = | |
Directory("/storage/emulated/0/Download/motion_data_recorder"); | |
log("App Dir: ${appDir.path}"); | |
await appDir.create(recursive: true); | |
return appDir.path; | |
} catch (e) { | |
log("Error on get folder path: $e"); | |
rethrow; | |
} | |
} | |
/// Writes a list of [ReadProtocol] data to a CSV file. | |
/// | |
/// - [fileName]: The name of the file to write. | |
/// - [data]: List of data to write. | |
/// - [overwrite]: If true, the file will be overwritten; otherwise, data will be appended. | |
/// | |
/// Returns a [Right] if successful, or [Left] with an error message on failure. | |
Future<Either<String, Unit>> writeCSVFile( | |
String fileName, | |
List<ReadProtocol> data, { | |
bool overwrite = false, | |
}) async { | |
return await _mutex.protectWrite( | |
() async => await _withIsolate( | |
() async { | |
IOSink? sink; | |
try { | |
final file = File("${await _folderPath}/$fileName"); | |
final isExist = await file.exists(); | |
sink = file.openWrite( | |
mode: overwrite ? FileMode.write : FileMode.append, | |
); | |
if (!isExist || overwrite) { | |
sink.writeln(ReadProtocol.csvHeader); | |
} | |
final dataCsv = data.csvIndexed(); | |
for (final protocol in dataCsv) { | |
sink.writeln(protocol); | |
} | |
return const Right(unit); | |
} catch (err) { | |
return Left(err.toString()); | |
} finally { | |
await sink?.flush(); | |
await sink?.close(); | |
} | |
}, | |
), | |
); | |
} | |
/// Reads a CSV file and parses its contents into a list of [ReadProtocol]. | |
/// | |
/// - [fileName]: The name of the file to read. | |
/// | |
/// Returns a [Right] with the list of data, or [Left] with an error message on failure. | |
Future<Either<String, List<ReadProtocol>>> readCSVFile( | |
String fileName, | |
) async { | |
return await _mutex.protectRead( | |
() async => await _withIsolate( | |
() async { | |
try { | |
final file = File("${await _folderPath}/$fileName"); | |
if (!await file.exists()) { | |
return const Right([]); | |
} | |
final List<String> lines = await file.readAsLines(); | |
lines.removeAt(0); // Remove header | |
final data = lines.map(ReadProtocol.fromCsv).toList(); | |
return Right(data); | |
} catch (err) { | |
return Left(err.toString()); | |
} | |
}, | |
), | |
); | |
} | |
/// Deletes a specified CSV file. | |
/// | |
/// - [fileName]: The name of the file to delete. | |
/// | |
/// Returns a [Right] if deleted or file doesn't exist, or [Left] with an error message. | |
Future<Either<String, Unit>> deleteCSVFile(String fileName) async { | |
return await _mutex.protectWrite( | |
() async => await _withIsolate( | |
() async { | |
try { | |
final file = File("${await _folderPath}/$fileName"); | |
if (await file.exists()) { | |
await file.delete(); | |
} | |
return const Right(unit); | |
} catch (err) { | |
return Left(err.toString()); | |
} | |
}, | |
), | |
); | |
} | |
/// Runs a callback function in a new isolate. | |
/// | |
/// This is used to offload heavy computation or file IO from the main thread. | |
Future<T> _withIsolate<T>(FutureOr<T> Function() callback) async { | |
return await Isolate.run(callback); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment