How can I get multiple messages from dart isolate? - flutter

How can I get multiple messages from dart isolate?
I'm trying to create an excel file and want to do some operation on that file in an isolate. Before doing an operation on that file, I want to return an message to main isolate, that excel file is created.
Here is function goes in isolate :
foo(String filePath){
// create excel file
var bytes = File(filePath).readAsBytesSync();
var excel = Excel.decodeBytes(bytes);
//HERE I WANT TO SEND THE MESSAGE THAT CREATING EXCEL FILE IS DONE
// some operatoin on excel file
var result = doSomeOperation(excel);
return result;
}
Main isolate code :
var result = await compute(foo, filePath);
What should I do to get creating file message before the actual result comes?
For excel, I'm using excel: ^2.0.0-null-safety-3 package.

Compute only returns one result. If you want to pass multiple 'events' back to the main isolate then you need to use the full Isolate logic (with sendPort and receivePort).
For example, the following code runs in an isolate, and downloads a file while emitting float values to represent progress, potentially a String to indicate log messages and then a bool to indicate success or failure upon completion.
Future<void> isolateDownload(
DownloadRequest request) async {
final sendPort = request.sendPort;
if (sendPort != null) {
var success = false;
var errorMessage = '';
var url = Uri.parse('a_url_based_on_request');
IOSink? out;
try {
http.StreamedResponse response =
await http.Client().send(http.Request('GET', url));
if (response.statusCode == 200) {
var filePath =
join(request.destinationDirPath, '${request.fileName}.ZIP');
var contentLength = response.contentLength;
var bytesLoadedUpdateInterval = (contentLength ?? 0) / 50;
var bytesLoaded = 0;
var bytesLoadedAtLastUpdate = 0;
out = File(filePath).openWrite();
await response.stream.forEach((chunk) {
out?.add(chunk);
bytesLoaded += chunk.length;
// update if enough bytes have passed since last update
if (contentLength != null &&
bytesLoaded - bytesLoadedAtLastUpdate >
bytesLoadedUpdateInterval) {
sendPort.send(bytesLoaded / contentLength);
bytesLoadedAtLastUpdate = bytesLoaded;
}
});
success = true;
if (contentLength != null) {
sendPort.send(1.0); // send 100% downloaded message
}
} else {
errorMessage =
'Download of ${request.fileName} '
'received response ${response.statusCode} - ${response.reasonPhrase}';
}
} catch (e) {
errorMessage = 'Download of ${request.chartType}:${request.chartName} '
'received error $e';
} finally {
await out?.flush();
await out?.close();
if (errorMessage.isNotEmpty) {
sendPort.send(errorMessage);
}
sendPort.send(success);
}
}
}
The code that spawns the isolate then simply checks for the type of the message passed to it to determine the action.
Future<bool> _downloadInBackground(
DownloadRequest request) async {
var receivePort = ReceivePort();
request.sendPort = receivePort.sendPort;
var isDone = Completer();
var success = false;
receivePort.listen((message) {
if (message is double) {
showUpdate(message);
}
if (message is String) {
log.fine(message); // log error messages
}
if (message is bool) {
success = message; // end with success or failure
receivePort.close();
}
}, onDone: () => isDone.complete()); // wraps up
await Isolate.spawn(isolateDownload, request);
await isDone.future;
return success;
}

Related

Flutter: Failed assertion: 'file.absolute.existsSync()': is not true

In my app, a user can send a file to others in a group chat. First, the user records some audio using their mic. The file is then touched up using FFMPEG. Then, the file is uploaded to Firebase Cloud Storage and if this is successful, a record is written in Firebase Realtime Database.
I'm getting the error below when the user records a long audio file and then presses submit. It almost seems as though FFMPEG hasn't finished processing the file...but I thought I used my async/await correctly to make sure that this processing is finished before moving on?
##MyAppFile## saveMyAppFileToCloudStorage Error: 'package:firebase_storage/src/reference.dart': Failed assertion: line 127 pos 12: 'file.absolute.existsSync()': is not true.
Psuedo-code:
User records audio
Audio file is processed using FFMPEG and the new processed file is created on the user's phone
User hits submit, uploading the file to Cloud Storage and, if successful, writing a record to Realtime Database
Order of Functions After User Hits Submit:
msgInput.dart -> sendMyAppFile()
msgInput.dart -> prepareMyAppFileForSending()
msgInput.dart -> runFFMPEGHighLow()
message_dao.dart -> sendMyAppFile()
message_dao.dart -> saveMyAppFileToCloudStorage() //ERROR COMES FROM THIS FUNCTION
The Code:
//msgInput.dart
Future<void> sendMyAppFile() async {
if (sendableMyAppFileExists == 1) {
final MyAppFileReadyToBeSent = await prepareMyAppFileForSending();
if (MyAppFileReadyToBeSent == '1') {
messageDao.sendMyAppFile(MyAppFile, filepath, filename);
} else {
}
}
setState(() {
sendableMyAppFileExists = 0;
});
}
Future<String> prepareMyAppFileForSending() async {
if (sendableMyAppFileExists == 1) {
if (recordedMyAppFileFilterID == '1') {
await runFFMPEGHighLow('1');
return '1';
}
if (recordedMyAppFileFilterID == '2') {
await runFFMPEGHighLow('2');
return '1';
}
}
return '0';
}
Future<void> runFFMPEGHighLow(String filterID) async {
if (filterID != '1' && filterID != '2') {
return;
}
if (sendableMyAppFileExists == 1) {
if (filterID == '1') {
await FFmpegKit.executeAsync(/*...parms...*/);
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '1.mp3';
});
}
if (filterID == '2') {
await FFmpegKit.executeAsync(/*...parms...*/);
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '2.mp3';
});
}
}
}
//message_dao.dart
void sendMyAppFile(ChatData MyAppFile, String filepath, String filename) {
saveMyAppFileToCloudStorage(filepath, filename).then((value) {
if (value == true) {
saveMyAppFileToRTDB(MyAppFile);
}
});
}
Future<bool> saveMyAppFileToCloudStorage(String filepath, String filename) async {
//filepath: /data/user/0/com.example.MyApp/app_flutter/MyApp/MyAppAudioFiles/MyAppFiles/2d7af6ae-6361-4be5-8209-8498dd17d77d1.mp3
//filename: 2d7af6ae-6361-4be5-8209-8498dd17d77d1.mp3
_firebaseStoragePath = MyAppFileStorageDir + filename;
File file = File(filepath);
try {
await _firebaseStorage
.ref(_firebaseStoragePath)
.putFile(file);
return true;
} catch (e) {
print('##MyAppFile## saveMyAppFileToCloudStorage Error: ' + e.toString()); //ERROR COMES FROM THIS LINE
return false;
}
return true;
}
I assume you're using the package ffmpeg_kit_flutter.
First, why it's not working: execute and executeAsync return FFmpegSession objects. The run of FFmpeg doesn't need to be finished for these methods to complete. In fact, the returned session object has methods like getState to monitor whether the run of FFmpeg has completed.
A good way to fix this: The documentation for executeAsync has a hint for what to do here.
Note that this method returns immediately and does not wait the execution to complete. You must use an FFmpegSessionCompleteCallback if you want to be notified about the result.
You can set a completion callback by passing a function to executeAsync. Here's the full function signature from the docs:
Future<FFmpegSession> executeAsync(
String command,
[FFmpegSessionCompleteCallback? completeCallback = null,
LogCallback? logCallback = null,
StatisticsCallback? statisticsCallback = null]
)
FFmpegSessionCompleteCallback is just a function that accepts an FFmpegSession and returns nothing. You can provide your own.
void someCompletionFunction() {
setState(() {
currentMyAppFileFilename = currentMyAppFileFilename + '1.mp3';
});
}
await FFmpegKit.executeAsync(/*...parms...*/, someCompletionFunction);
Future vs callback: If you prefer to use Futures and async-await instead of callbacks, you'll need to create your own Future and update it in the callback. See Dart, how to create a future to return in your own functions? for an example.

How to assign values from an API call to a variable in flutter

I have the following method which is use dto verify a ticket/token
var ticketArray = ticket.split('|');
//First check to verify token using simple versification algo
if (widget.eventID.toString() != (ticketArray[0])) {
setState(() {
ticketMainMsg = 'This QR code is NOT VALID';
ticketsubtitle = ticketArray.length != 2
? 'The QR code is fake'
: 'QR code could belong to another event';
ticketStatus = false;
return;
});
}
//Make API call
ticketModel = HttpVerifyTicketPost(
eventId: widget.eventID,
ticket: ticket,
scannerId: widget.scannerId,
).verifyTicket();
}
From above, you can see I do a very simple check on the qr code/token if this simple step fails, I don't bother making an API call and I set the state based on these values.
However if the check passes, then I proceed to make an API call to the server to fully verify the token/code.
My issue is I am struggling to now assign the values from the API call to the ticketStatus, ticketMainMsgand ticketsubtitle parameters. Can anyone helo shed some light. I am quite new to flutter but I am aware that the TicketModel will be a type of Future. My background is PHP so forgive me!
EDIT: The httpVerifyTicket Class
class HttpVerifyTicketPost {
String ticket;
int someId;
int anotherId;
HttpVerifyTicketPost(
{required this.ticket, required this.someId, required this.anotherId});
String verifyURL =
'https://api.com/api/vendors/scanner/native/verify/ticket';
Future<TicketModel> verifyTicket() async {
var storage = await SharedPreferences.getInstance();
var code= storage.getString('code');
var client = http.Client();
var ticketModel = null;
var body = {
'ticket': ticket,
'scanner': scannerCode,
'someId': someId,
'anotherId': anotherId
};
try {
var url = Uri.parse(verifyURL);
var res = await client.post(url, body: jsonEncode(body));
if (res.statusCode == 200) {
var jsonString = res.body;
var jsonMap = json.decode(jsonString);
ticketModel = TicketModel.fromJson(jsonMap);
}
return ticketModel;
} catch (Exception) {
return ticketModel;
}
}
}
Try this please
HttpVerifyTicketPost(
eventId: widget.eventID,
ticket: ticket,
scannerId: widget.scannerId,
).verifyTicket().then((value){setState(() {
ticketModel=value
});
});
I don't quite understand what you want to achieve, but maybe you need to add an asynchronous method like
ticketModel = await HttpVerifyTicketPost( //add await eventId: widget.eventID, ticket: ticket, scannerId: widget.scannerId, ).verifyTicket();
and you must add async like Future Foo() async {your code...}

Prefix text to ASP.NET Core response body

I'm trying to prepend the string )]}',\n to any response body that's JSON. I thought that an IAsyncResultFilter would be what I needed to use, but I'm not having luck. If I use the below code, it appends the text to the response since calling await next() writes to the response pipe. If I try and look at the context before that though, I can't tell what the response will actually be to know if it's JSON.
public class JsonPrefixFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var executed = await next();
var response = executed.HttpContext.Response;
if (response.ContentType == null || !response.ContentType.StartsWith("application/json"))
return;
var prefix = Encoding.UTF8.GetBytes(")]}',\\n");
var bytes = new ReadOnlyMemory<byte>(prefix);
await response.BodyWriter.WriteAsync(bytes);
}
}
Thanks to timur's post I was able to come up with this working solution.
public class JsonPrefixFilter : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var response = context.HttpContext.Response;
// ASP.NET Core will always send the contents of the original Body stream back to the client.
var originalBody = response.Body;
// We want to write into a memory stream instead of the actual response body for now.
var ms = new MemoryStream();
response.Body = ms;
// After this call the body is written into the memory stream and the properties
// of the response object are populated.
await next();
if (response.ContentType != null && response.ContentType.StartsWith("application/json")) {
var prefix = Encoding.UTF8.GetBytes(")]}',\\n");
var prefixMemoryStream = new MemoryStream();
await prefixMemoryStream.WriteAsync(prefix);
await prefixMemoryStream.WriteAsync(ms.ToArray());
prefixMemoryStream.Seek(0, SeekOrigin.Begin);
// Now put the stream back that .NET wants to use and copy the memory stream to it.
response.Body = originalBody;
await prefixMemoryStream.CopyToAsync(response.Body);
} else {
// If it's not JSON, don't muck with the stream, so just put things back.
response.Body = originalBody;
ms.Seek(0, SeekOrigin.Begin);
await ms.CopyToAsync(response.Body);
}
}
}
Update:
I never liked the above, so I switched to this solution. Instead of calling AddJsonOptions, I took inspiration from ASP.NET's formatter to use this instead:
public class XssJsonOutputFormatter : TextOutputFormatter
{
private static readonly byte[] XssPrefix = Encoding.UTF8.GetBytes(")]}',\n");
public JsonSerializerOptions SerializerOptions { get; }
public XssJsonOutputFormatter()
{
SerializerOptions = new() {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
ReferenceHandler = ReferenceHandler.IgnoreCycles
};
SupportedEncodings.Add(Encoding.UTF8);
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json"));
}
public override sealed async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
{
ArgumentNullException.ThrowIfNull(context, nameof(context));
ArgumentNullException.ThrowIfNull(selectedEncoding, nameof(selectedEncoding));
var httpContext = context.HttpContext;
var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object);
var responseStream = httpContext.Response.Body;
try {
await responseStream.WriteAsync(XssPrefix);
await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
await responseStream.FlushAsync(httpContext.RequestAborted);
} catch (OperationCanceledException) when (context.HttpContext.RequestAborted.IsCancellationRequested) {
}
}
}
Now, when you call .AddControllers() you just set that as the first output formatter:
services.AddControllers(options => {
options.Filters.Add(new ProducesAttribute("application/json"));
options.OutputFormatters.Insert(0, new XssJsonOutputFormatter());
});
Obviously you could improve this to take serialization options in the constructor, but all my project would work exactly like the above so I just hardcoded it right in.
You could've used Seek on a steam to rewind it. Issue is, you can only keep adding onto default HttpResponseStream, it does not support seeking.
So you can employ the technique from this SO answer and temporarily replace it with MemoryStream:
private Stream ReplaceBody(HttpResponse response)
{
var originBody = response.Body;
response.Body = new MemoryStream();
return originBody;
}
private async Task ReturnBodyAsync(HttpResponse response, Stream originalBody)
{
response.Body.Seek(0, SeekOrigin.Begin);
await response.Body.CopyToAsync(originalBody);
response.Body = originalBody;
}
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var originalBody = ReplaceBody(context.HttpContext.Response); // replace the default stream with MemoryStream
await next(); // we probably dont care about the return of this call. it's all in the context
var response = context.HttpContext.Response;
if (response.ContentType == null || !response.ContentType.StartsWith("application/json"))
return;
var prefix = Encoding.UTF8.GetBytes(")]}',\\n");
var bytes = new ReadOnlyMemory<byte>(prefix);
response.Body.Seek(0, SeekOrigin.Begin); // now you can seek. but you will notice that it overwrites the response so you might need to make extra space in the buffer
await response.BodyWriter.WriteAsync(bytes);
await ReturnBodyAsync(context.HttpContext.Response, originalBody); // revert the reference, copy data into default stream and return it
}
this is further complicated by the fact that you need to restore reference to original stream, so you have to careful around that.
This SO answer has a bit more context.

clean future builder for new data

i'm using a builder for a search page in my application, basically i get data from a json file,
my issue is that if i try to search for a new word, the old result will still be shown and the new one are going to be shown under them.
here is how i get data from my website:
Future<List<Note>> fetchNotes() async {
var url = 'https://sample.com/';
var response = await http.get(url + _controller.text.trim());
var notes = List<Note>();
if (response.statusCode == 200) {
var notesJson = json.decode(response.body);
for (var noteJson in notesJson) {
notes.add(Note.fromJson(noteJson));
}
} else {
ercode = 1;
}
return notes;
}
fetchNotes().then((value) {
setState(() {
_notes.addAll(value);
});
});
if (_notes[0] == null) {
ercode = 2;
}
}
and i display data like this:
here is full example for showing that data
I think you should use "clear()".
fetchNotes().then((value) {
setState(() {
_notes.clear();
_notes.addAll(value);
});
});

for-in using dart Exception: type '_InternalLinkedHashMap<String, dynamic>

i am not familiar using For-in using dart, i use similar code in ts or angular and it work as expected, but get lost when use flutter and need more guidance to use it.
Future<int> datetransaction(String mid) async {
var url = 'http://callsomehttp.com/$mid';
var res = await http.get(url);
if (res.statusCode == 200) {
var dtpoint = json.decode(res.body);
var availabledate = [];
for (var key in dtpoint) {
var dateEle = dtpoint[key]['Balance']['date'];
if (availabledate.indexOf(dateEle) == -1) {
availabledate.add(dateEle);
totalSpending(dateEle, mid);
}
}
print('available data $availabledate');
var spen = totalTransaction.reduce((a, b) => a + b);
return spen ;
} else {
throw Exception(
"Request to $url failed with status ${res.statusCode}: ${res.body}");
}
}
Future totalSpending (String date, String ckey) async {
var total = 0 ;
final String url = 'http://otherurl.com/$date';
final res = await http.get(url);
if (res.statusCode == 200) {
var pdata = json.decode(res.body);
for (var key in pdata) {
var el = pdata[key]['Transaction']['customer_code'];
var ttl = int.parse(pdata[key]['Transaction']['total']);
if( el == ckey) {
totalTransaction.add(ttl);
total = ttl ;
}
}
return total ;
} else {
throw Exception(
"Request to $url failed with status ${res.statusCode}: ${res.body}");
}
}
any guidance to give a light , or other way to get the result really appreciate, thank you