Good night everyone. I am having some trouble with a simple dialog to update a file transfer in my app. Every time I get a new progress update I call a function that takes the amount of bytes read and the size of the video. With these I have the percentage of the download. I calculate that and call setState
in order to update my dialog with the correct percentage.
Here's the code I am talking about:
class DownloadProgressDialog extends StatefulWidget {
final SftpClient sftpServer;
final String remoteFilePath;
DownloadProgressDialog(
{required this.sftpServer, required this.remoteFilePath});
@override
_DownloadProgressDialogState createState() => _DownloadProgressDialogState();
}
class _DownloadProgressDialogState extends State<DownloadProgressDialog> {
double progress = 0;
bool _downloading = false;
int i = 0;
@override
void initState() {
super.initState();
_startDownload();
}
@override
Widget build(BuildContext context) {
log('Rebuilt');
return AlertDialog(
title: Text('Downloading vídeo...'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(height: 16),
LinearProgressIndicator(value: progress),
SizedBox(height: 16),
Text('${(progress * 100).toInt().toString()}%'),
],
),
actions: <Widget>[
if (_downloading)
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Close'),
),
],
);
}
void updateState(int bytesRead, int size) {
var newProgressValue = bytesRead / size;
i++;
if(i == 100) {
setState(() {
progress = newProgressValue;
});
log(progress.toString());
i = 0;
}
}
void _startDownload() async {
setState(() {
_downloading = true;
});
var file = await widget.sftpServer.open(
widget.remoteFilePath,
mode: SftpFileOpenMode.read,
);
var stat = await file.stat();
var size = stat.size ?? 0;
List<List<int>> byteList = [];
var stream = file.read(
onProgress: (bytesRead) {
updateState(bytesRead, size);
}
);
stream.listen((event) {
byteList.add(event);
}, onDone: () {
List<int> flattenedList = [];
byteList.forEach((list) => flattenedList.addAll(list));
var fullListUint8 = Uint8List.fromList(flattenedList);
Navigator.pop(context, fullListUint8);
}, onError: (error) {
FirebaseCrashlytics.instance.log('Error downloading video: ${error.toString()}');
Navigator.pop(context, null);
});
}
}
I start the download in initState
, open the SFTP file, check for the file's stats and subscribe to the download stream. However, my widget does not update at all (or updates once or twice) during the download. I know that the function updateState
is working because of the logs, however the widget still does not update.
Any insight in what might be missing?
EDIT
It seems that setState is being called but the interval between updates is so short that the device is not concerned in updating the screen. Any ideia how to force this behavior?
I modified the code as follows:
class VideoDownloadPage extends StatefulWidget {
const VideoDownloadPage(
{Key? key, required this.sftpServer, required this.remoteFilePath})
: super(key: key);
final SftpClient sftpServer;
final String remoteFilePath;
@override
State<VideoDownloadPage> createState() => _VideoDownloadPageState();
}
class _VideoDownloadPageState extends State<VideoDownloadPage> {
int _progress = 0;
@override
void initState() {
_startDownload();
super.initState();
}
@override
Widget build(BuildContext context) {
log('Widget rebuilt');
return Scaffold(
appBar: AppBar(
title: Text('Baixando vídeo'),
),
body: Center(
child: Text('$_progress'),
),
);
}
void _startDownload() async {
var file = await widget.sftpServer.open(
widget.remoteFilePath,
mode: SftpFileOpenMode.read,
);
List<List<int>> byteList = [];
var stream = file.read();
stream.listen((Uint8List event) {
byteList.add(event);
setState(() {
log('setState called');
_progress++;
});
}, onDone: () {
List<int> flattenedList = [];
byteList.forEach((list) => flattenedList.addAll(list));
var fullListUint8 = Uint8List.fromList(flattenedList);
Navigator.pop(context, fullListUint8);
}, onError: (error) {
FirebaseCrashlytics.instance
.log('Error downloading video: ${error.toString()}');
Navigator.pop(context, null);
});
}
}
The output logs look something like this:
[log] Widget rebuilt
[log] setState called
[log] setState called
[log] setState called
.
.
.
[log] File downloaded: 2023-04-28-17-01-46.avi
[log] Widget rebuilt