Skip to content

Instantly share code, notes, and snippets.

@mqhamdam
Created March 27, 2025 04:45
Show Gist options
  • Save mqhamdam/499019287da338d722d41c6e0e699c00 to your computer and use it in GitHub Desktop.
Save mqhamdam/499019287da338d722d41c6e0e699c00 to your computer and use it in GitHub Desktop.
CSV File Service for interacting with CSV File used in project
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