Flutter method blocking, need to isolate - flutter

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.

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 to handle a loop in flutter? need can be canceled?

I want to use flutter fulfilled a mqtt client. the client need send message to serve for loop.
I used "while" keyword for loop. but the flutter UI is pending during the while function.
if use isolate to work it, sending the cancel flag failed.
Does there anybody have the experience on it?
----------code------
onpressed -----> Future Function ----> use while loop---->the app pending cannot press anywhere---> the loop stop, the app recovery
I want start a mqtt client to send mqtt message for loop.
but when pressed the function, the while loop pended the app.
Expected results: the loop works in an async work, and the app can work rightly.
Actual results: the app is pending.
###UI
children: [
SizedBox(width: 200),
MaterialButton(
child: Text('发送'),
onPressed: () {
BtnsendMsg();
},
####onpressed function
mc = new MqttClient(false);
BtnsendMsg() async {
mc.MsgSend(clientid, topic, msgname, '3');
print("back");
}
####loop function
class MqttClient {
bool isStop;
MqttClient(this.isStop);
Future MsgSend(clientid, topic, msgname, interval) async {
isStop = false;
var cc = await clientGet(clientid);
var msg = await msgGet(msgname);
String host = "1.1.1.1";
String msgdata = "1111";
if (cc != null) {
host = cc.host!;
}
if (msg != null) {
msgdata = msg.msgdata!;
}
Future future = Future(() {
while (isStop == false) {
SendMsgOnce(host, clientid, topic, msgdata);
sleep(Duration(seconds: 3));
}
});
sleep(Duration(seconds: 30));
isStop = true;
}
This is because you are putting load on main thread by not using async and await while sending request to the server. Do the following changes to your code then it should get work.
class MqttClient {
bool isStop;
MqttClient(this.isStop);
Future MsgSend(clientid, topic, msgname, interval) async {
isStop = false;
var cc = await clientGet(clientid);
var msg = await msgGet(msgname);
String host = "1.1.1.1";
String msgdata = "1111";
if (cc != null) {
host = cc.host!;
}
if (msg != null) {
msgdata = msg.msgdata!;
}
Future future = Future(() async {
while (isStop == false) {
await SendMsgOnce(host, clientid, topic, msgdata);
sleep(Duration(seconds: 3));
}
});
sleep(Duration(seconds: 30));
isStop = true;
}
In your on pressed function you are using async but now awaiting for that
mc = new MqttClient(false);
BtnsendMsg() async {
await mc.MsgSend(clientid, topic, msgname, '3');
print("back");
}

Flutter Multiple setState in Async Function

I'm trying to create a Login Page for my app. I want it to say 'Logging In' then update if there's an issue.
When run, the app doesn't appear to do anything for the timeout duration. I'm guessing the 'Logging In' status is appearing microseconds before the AlertDialog shows as both the status and AlertDialog seem to appear at the same time. Once I click OK, the status updates to 'Retry Login'.
What I want is the 'Logging In' to appear as the text for my status box as soon as the button is pressed.
Button onPressed
ElevatedButton(
onPressed: () async {
setState((){
statusText = 'Logging In';
});
LoginNow();
TimeOutCheck();
},
child: Text('Log In'),
),
TimeOutCheck()
Future<void> TimeOutCheck() async{
hasTimedOut = false;
Duration timeoutDuration = GLOBAL_CONFIG.DEFAULT_TIMEOUT;
final endTime = DateTime.now().add(timeoutDuration);
while (!isComplete && DateTime.now().isBefore(endTime)){
sleep(Duration(milliseconds: 500));
print('Waiting ${DateTime.now()}');
}
hasTimedOut = !isComplete;
if (hasTimedOut){
await _showAlert('Timeout', 'Login Attempt Timed Out.\n\nPlease try again.');
setState(() {
_StatusText = 'Retry Login';
});
}
}
LoginNow()
Future<void> LoginNow(BuildContext context) async {
final String funcName = 'LoginNow';
bool doLogin = false;
_LoginForm.currentState!.validate();
setState(() {
if (LoginInfo['username'] == null || LoginInfo['username'] == ''){
_StatusText = 'Please Enter your User Name';
isComplete = true;
}
else if (LoginInfo['password'] == null || LoginInfo['password'] == '') {
_StatusText = 'Please Enter your Password';
isComplete = true;
}
else {
logger.wtf('[${className}.${funcName}]\t Setting Status to:\t "Logging In"\n');
//_StatusText = 'Logging In';
doLogin = true;
}
});
if (doLogin){
logger.d('[$className.$funcName\t Do Login: $doLogin');
logger.d('[$className.$funcName\t _StatusText Value: $_StatusText');
String URL = GetLoginRequest();
Uri uri = Uri.parse(URL);
var response = await http.get(uri);
isComplete = true;
// Do the rest of login stuff
}
I think what you need is to simply await the functions.
...
await LoginNow();
await TimeOutCheck();
// Make sure to update the status text at the end.
setState(() {
statusText = "Finished";
}
The problem is in the TimeOutCheck(). During the while, I'm using Sleep. Sleep is a synchronous function. Sleep is holding up all changes that may be completed by other async processes.
while (!isComplete && DateTime.now().isBefore(endTime)){
sleep(Duration(milliseconds: 500));
}
Instead of Sleep, I should've used Future.delayed(). Future.delayed() allows other threads to run while the thread it's called in waits for the delay.
while (!isComplete && DateTime.now().isBefore(endTime)){
await Future.delayed(Duration(milliseconds: 500));
}
The working code looks like:
Future<void> TimeOutCheck() async{
this.hasTimedOut = false;
Duration timeoutDuration = GLOBAL_CONFIG.DEFAULT_TIMEOUT;
final endTime = DateTime.now().add(timeoutDuration);
while (!(this.isComplete) && DateTime.now().isBefore(endTime)){
await Future.delayed(Duration(milliseconds: 500));
print('Waiting ${DateTime.now()}\n\t isComplete\t ${this.isComplete}');
}
this.hasTimedOut = !this.isComplete;
if (this.hasTimedOut && !this.isComplete){
await _showAlert('Timeout', 'Login Attempt Timed Out.\n\nPlease try again.');
setState(() {
this._StatusText = 'Retry Login';
});
}
}

Flutter web upload file cancel event

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

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.