Flutter web upload file cancel event - flutter

I develop a web app in Flutter and I want to load a file from file system. In order to do that I use the following code:
static Future<Uint8List> chooseImage(dynamic parent, dynamic provider) async {
Uint8List uploadedImage;
final completer = Completer<List<String>>();
InputElement uploadInput = FileUploadInputElement();
uploadInput.accept = 'image/*';
uploadInput.click();
uploadInput.addEventListener('change', (e) async {
final files = uploadInput.files;
Iterable<Future<String>> resultsFutures = files.map((file) {
final reader = FileReader();
reader.readAsDataUrl(file);
reader.onError.listen((error) => completer.completeError(error));
return reader.onLoad.first.then((_) async {
String result = reader.result as String;
uploadedImage = base64Decode(result.substring(22, result.length));
return reader.result as String;
});
});
final results = await Future.wait(resultsFutures);
completer.complete(results);
});
document.body.append(uploadInput);
final List<String> images = await completer.future;
parent.setState(() {
parent.pickedImage = uploadedImage;
});
uploadInput.remove();
return uploadedImage;
}
In my app I need to handle the case when the user press the Cancel button in this pop-up:
I have added listener for: onFocus, onSuspen, onSubmit, onEnded, onAbort but none of these events are triggered when that cancel button is pressed.
How can I handle the pop-up cancelation?

I've already answered to a similar question here
You can already find a way to manage this kind of event in the web implementation of the package file_picker.
Because depending of the browser you are using the cancel event might be managed differently the most generic solution would be to rely on a listener on the focus event from the window so when you will lose the focus on your file picker window without any data loaded, it will complete your future with a null value.
Code Sample
import 'dart:html' as html;
import 'dart:async';
Future<html.File?> pickFile(String type) async {
final completer = Completer<List<html.File>?>();
final input = html.FileUploadInputElement() as html.InputElement;
input.accept = '$type/*';
var changeEventTriggered = false;
void changeEventListener(html.Event e) {
if (changeEventTriggered) return;
changeEventTriggered = true;
final files = input.files!;
final resultFuture = files.map<Future<html.File>>((file) async {
final reader = html.FileReader();
reader.readAsDataUrl(file);
reader.onError.listen(completer.completeError);
return file;
});
Future.wait(resultFuture).then((results) => completer.complete(results));
}
void cancelledEventListener(html.Event e) {
html.window.removeEventListener('focus', cancelledEventListener);
// This listener is called before the input changed event,
// and the `uploadInput.files` value is still null
// Wait for results from js to dart
Future.delayed(Duration(milliseconds: 500)).then((value) {
if (!changeEventTriggered) {
changeEventTriggered = true;
completer.complete(null);
}
});
}
input.onChange.listen(changeEventListener);
input.addEventListener('change', changeEventListener);
// Listen focus event for cancelled
html.window.addEventListener('focus', cancelledEventListener);
input.click();
final results = await completer.future;
if (results == null || results.isEmpty) return null;
return results.first;
}
Sources
file_picker implementation

Related

setState() called after dispose() error after calling async function

I have this button that uploads to Firestore a picture that the user selects and stores the picture url into a varialble to be used to update the user's information.
SELECTION BUTTON calls selectFile().
// SELECTING FILE FOR UPLOAD
Future selectFile() async {
final result = await FilePicker.platform
.pickFiles(allowMultiple: false, type: FileType.image, withData: true);
if (result == null) return;
setState(() {
pickedFile = result.files.first;
texto = Text(pickedFile!.name);
});
}
This successfully changes the state of pickedFiles and Texto variable.
Then I have this other button later in the code that calls uploadFile() and then exits the page with navigator.pop(context).
// UPLOADING FILE AND RETRIEVING DOWNLOAD LINK
Future uploadFile() async {
var fileBytes = pickedFile?.bytes;
var fileName = pickedFile?.name;
var ref = FirebaseStorage.instance.ref().child('UserImages/$fileName');
if (fileBytes == null) {
return '';
}
TaskSnapshot uploadedFile = await ref.putData(fileBytes);
url = await ref.getDownloadURL();
log(url);
if (uploadedFile.state == TaskState.success) {
setState(() { <<<<<<<<--------- setState() called after dispose() ERROR HERE
_petImage = url;
});
}
}
The function does upload the picture to FireStore and even produces a link (tested by using log(url)) but when it reaches the set state it fails.
I have no idea why this is not updating the state of the _petImage variable which stored outside of the main build(context) together with the other variables suck as pickedFile and texto. the setState work fine in other functions but in this function is not working .
what could I be doing wrong here?
It is safe to check if the state is mounted on async and then perform setState.
_() async {
if (mounted) {
setState(() {});
}
}

How do I properly listen for sms code flutter

I used PinPut package for my flutter app for users to validate their phone's code after I send it from my custom backend. Everything works perfectly, but I wanted an auto-fill mechanism such that they don't have to place it manually everytime. So,i tried switching to Sms Autofill package but I didn't like the way its input field looks as I wanted more customisations. (The auto-fill didn't work for me anyways).
Now, am using SMS receiver package alongside my preferred PinPut widget. The idea is that I want to listen for the sms code and pass it to my PinPut's controller. But sadly, the listener don't work as I don't get the messages with my code implementation.
This is my code:
String _smsCode = "";
bool isListening = false;
getCode(String sms) {
print("Snskndksdnn");
if (sms != null) {
final intRegex = RegExp(r'\d+', multiLine: true);
final code = intRegex.allMatches(sms).first.group(0);
return code;
}
return "NO SMS";
}
Future<void> onSubmitPhone() async {
///close keyboard
FocusScope.of(context).requestFocus(FocusNode());
errorPhone = _cv.contentValidator(
phone.text.toString().trim(), '[0-9]{10,11}', errorPhone);
if(errorPhone == null){
var _api = SmsRetrieverApi();
setState(() {});
await _api.startListening(onReceived: (smsCode){
_smsCode = getCode(smsCode);
isListening = false;
});
Map<String, String> data = new Map();
data = {
"phoneNumber": phoneUpdate
};
try{
Map<String, dynamic> _res = await authenticationService.sendOTP(data);
if(_res['success'] ==true){
AlertManager.showToast(_res['data']['smsStatus']);
setState(() {
isSendIngOTP = false;
pinId = '${_res['data']['pinId']}';
});
_pageController.nextPage(
duration: Duration(milliseconds: 500),
curve: Curves.ease,
);
}
}
}
}
Any ideas to get my desired result?

How to listen("close" event) to the file download window in flutter web?

My code allows to open the file upload window using universal_html.InputElement uploadInput = universal_html.FileUploadInputElement(); web Flutter and select the necessary files to load them into the project. If the user does not select any photo and clicks on close/cancel the window, I want react to this. How can i understand that user close window?
final completer = Completer<List<String>>();
universal_html.InputElement uploadInput = universal_html.FileUploadInputElement();
uploadInput.multiple = true;
uploadInput.accept = 'image/*';
uploadInput.click();
uploadInput.addEventListener('change', (e) async {
final files = uploadInput.files;
Iterable<Future<String>> resultsFutures = files.map((file) {
final reader = universal_html.FileReader();
reader.readAsDataUrl(file);
reader.onError.listen((error) => completer.completeError(error));
return reader.onLoad.first.then((_) => reader.result as String);
});
final results = await Future.wait(resultsFutures);
completer.complete(results);
});
universal_html.document.body.append(uploadInput);
final List<String> images = await completer.future;
uploadInput.remove();
A way to manage this kind of event is used in the web implementation of the package file_picker.
Here is a code sample to help you (you can also find the full implementation from the package here):
import 'dart:html' as html;
import 'dart:async';
Future<html.File?> pickFile(String type) async {
final completer = Completer<List<html.File>?>();
final input = html.FileUploadInputElement() as html.InputElement;
input.accept = '$type/*';
var changeEventTriggered = false;
void changeEventListener(html.Event e) {
if (changeEventTriggered) return;
changeEventTriggered = true;
final files = input.files!;
final resultFuture = files.map<Future<html.File>>((file) async {
final reader = html.FileReader();
reader.readAsDataUrl(file);
reader.onError.listen(completer.completeError);
return file;
});
Future.wait(resultFuture).then((results) => completer.complete(results));
}
void cancelledEventListener(html.Event e) {
html.window.removeEventListener('focus', cancelledEventListener);
// This listener is called before the input changed event,
// and the `uploadInput.files` value is still null
// Wait for results from js to dart
Future.delayed(Duration(milliseconds: 500)).then((value) {
if (!changeEventTriggered) {
changeEventTriggered = true;
completer.complete(null);
}
});
}
input.onChange.listen(changeEventListener);
input.addEventListener('change', changeEventListener);
// Listen focus event for cancelled
html.window.addEventListener('focus', cancelledEventListener);
input.click();
final results = await completer.future;
if (results == null || results.isEmpty) return null;
return results.first;
}
The idea is to rely on a listener on the focus event so when you will lose the focus on your file picker window without any data loaded it will complete your future with a null value.

How to synchronize a call from the asynchronous function in dart

I am working on my first app in Flutter, I have a bit of experience with Java and js, but I never worked with flutter before so sorry if my question will seem ridiculous to you.
The app is the voice assistant chatbot, and it is supposed to perform text to speech on each new message that customer receives, my problem is that since I am using firebase messaging all of the requests that I receive are in the asynchronous call, but I need to synchronize the access to the text to speech service otherwise I run into problem of having one text interrupt another.
This is what my code looks like at the moment:
Firebase messaging:
onMessage: (Map<String, dynamic> message) {
return this.handleBotMessage(appState, message);
},
Method that desides how to handle each particular message:
Future handleBotMessage(
Store<AppState> store,
Map<String, dynamic> dataJson,
) {
#logic that convert the message into json and extracts the message type
if (type == MessageType.CHAT_MESSAGE) {
return handleChatMessage(store, subtype, messageMap);
}
}
The method that handles text messages:
Future<dynamic> handleChatMessage(
Store<AppState> store,
MessageSubtype subtype,
Map<String, dynamic> message,
) {
#Text to speach is build as a singleton and this always returns the same instance
TextToSpeech tts = TextToSpeech();
if (subtype == MessageSubtype.TEXT) {
TextMessage textMessage = TextMessage.fromJson(message);
return tts
.speak(textMessage.text)
.then((result) => store.dispatch(NewBotMessageAction(textMessage)));
} else if (subtype == MessageSubtype.QUICK_REPLY) {
QuickReplyMessage qrMessage = QuickReplyMessage.fromJson(message);
return tts
.speak(qrMessage.text)
.then((result) => store.dispatch(NewQrOptionsAction(qrMessage)));
} else {
throw new Exception('Unexpected message subtype!');
}
}
The method that actually performs the text to speech
Future<dynamic> speak(String text) async {
return flutterTts.speak(text).then((resp) {
ttsRunning = false;
print(resp);
return resp;
}, onError: (obj, st) {
ttsRunning = false;
print(obj);
print(st.toString());
});
}
Text to speech initialization
Future init() async {
await flutterTts.setLanguage("en-US");
var res = await flutterTts.isLanguageAvailable("en-US");
print(res);
return res;
}
https://pub.dev/packages/flutter_tts
Ok, I have found the solution, the issue was as frank06 pointed out with the fact that flutter tts completes the future immediately rather than after the whole phrase was spoken.
So here is my solution, it is not perfect, but it works:
Completer completer;
Future<dynamic> speak(String text) {
print('Started speeking');
print(new DateTime.now().toIso8601String());
if (TextToSpeech.lastRequest == null) {
lastRequest = _executeSpeech(text);
} else {
lastRequest = lastRequest.then((resp) {
return _executeSpeech(text);
});
}
return lastRequest;
}
Future<dynamic> _executeSpeech(String text) {
completer = Completer();
flutterTts.speak(text).then((resp) {
ttsRunning = false;
print(resp);
return resp;
}, onError: (obj, st) {
ttsRunning = false;
print(obj);
print(st.toString());
});
return completer.future;
}
flutterTts.setCompletionHandler(() {
print('Finished speeking');
print(new DateTime.now().toIso8601String());
ttsState = TtsState.stopped;
completer.complete(ttsState);
});
flutterTts.setErrorHandler((msg) {
ttsState = TtsState.stopped;
completer.complete(ttsState);
});
If you don't want new messages interrupting those being spoken, you can queue them up. This way the new messages will wait for the current message to finish. Check out this approach:
Queue of Future in dart

Flutter method blocking, need to isolate

When I click a button and run some set state items and then call the send function, it waits until the send function is done before the set state items take effect. I have tried to make the called function async with await on the item that takes so much time BASE64.encode of image and/or video, but still it waits.
Looking for a way to not have this function block, someone mentioned isolate, but have no idea how to work that in and examples show how to work it in the entire application not just a long running function.
onPressed: () async {
setState(() {
submitting = true;
_imageFile = null;
TextInputAction.done;
});
await _sendReply();
},
Above is what I run on a ImageButton. The _sendReply is below.
_sendReply() async {
if (_newreplycontroller.text.isNotEmpty || myimagefile != null) {
//_doShowSubmitting();
DateTime dateSubmit = new DateTime.now();
if (myimagefile != null) {
if (isImage) {
List<int> imageBytes = myimagefile.readAsBytesSync();
myimage = await BASE64.encode(imageBytes);
myvideo = 'NONE';
}
if (isVideo) {
List<int> imageBytes = myvidfile.readAsBytesSync();
myvideo = await BASE64.encode(imageBytes);
myimage = 'NONE';
}
} else {
myimage = 'NONE';
myvideo = 'NONE';
}
var mymessage = _newreplycontroller.text;
ChatServerMessage mychat = new ChatServerMessage(
widget.mychat.msgkey,
'message',
widget.mychat.refid,
widget.mychat.referralname,
replysub,
oid,
oname,
pid,
pname,
sender,
sendname,
receiver,
receivename,
mymessage,
dateSubmit.toString(),
widget.mychat.grpid.toString(),
widget.mychat.prid.toString(),
myfcmtoken,
myimage,
myvideo,
myext);
_doSendReply(mychat);
} else {
}
}
From debugging I know all the time is spent on the BASE64.encode. Any ideas would be great.