0

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
Raphael Sauer
  • 630
  • 1
  • 6
  • 27
  • Does this answer your question? [How to refresh an AlertDialog in Flutter?](https://stackoverflow.com/questions/51962272/how-to-refresh-an-alertdialog-in-flutter) – Christopher Moore May 16 '23 at 00:18
  • @ChristopherMoore sadly the answer above does not work. I tried taking the component to a separate stateful widget, not just a dialog, and I am still facing the same issue. It seems that the onProgress function is called a lot, resulting in setState not completely updating before being called again. – Raphael Sauer May 16 '23 at 15:47
  • Share what you've tried. – Christopher Moore May 16 '23 at 16:20
  • @ChristopherMoore updated the post with what I've tried. – Raphael Sauer May 16 '23 at 21:12
  • As far as I know, it's not possible to call `setState` too fast as long as it's called with some async delay between calls like you have. Can you make a [mcve] (as in something that doesn't rely on your Sftp library)? Please also strip any extraneous code like your Firebase calls and such just to make it as minimal as possible. – Christopher Moore May 17 '23 at 01:53

0 Answers0