How to use progress indicators in Flutter

Share this post

There are 2 dedicated widgets in Flutter that can help you display progress in your Flutter apps, namely LinearProgressIndicator and CircularProgressIndicator. You can use both of them just the way you use any other widget and you can position them however you want.

Progress indicators in Flutter

How to use LinearProgressIndicator in Flutter

A LinearProgressIndicator is basically a progress bar that shows the progress in a line. By default, all properties are optional, which means that the indicator will be an indeterminate progress bar. That means that we don’t really know when the process will be finished, as opposed to a determinate LinearProgressIndicator of which the value property should be updated.

The following code shows a simple usage of (indeterminate) LinearProgressIndicator that simulates a download (but we don’t know when it will be finished). A button simply changes the state from not downloading to downloading. When we are not downloading, we show a text instructing the user and otherwise we show the progress indicator:

bool _loading;

  @override
  void initState() {
    super.initState();
    _loading = false;
  }
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("LinearProgressIndicator"),
      ),
      body: Center(
        child: Container(
          padding: EdgeInsets.all(16.0),
          child: _loading ? LinearProgressIndicator() : Text("Press button to download"),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _loading = !_loading;
          });
        },
        tooltip: 'Download',
        child: Icon(Icons.cloud_download),
      ),
    );
  }

This is how it will look:

What if you want to show make a determinate progress indicator, that means you want to show how much we have progressed or how long it will take more to finish downloading something. We can simulate a download that takes a certain amount of time and updates the value of LinearProgressIndicator as follows:

  bool _loading;
  double _progressValue;

  @override
  void initState() {
    super.initState();
    _loading = false;
    _progressValue = 0.0;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("LinearProgressIndicator"),
      ),
      body: Center(
        child: Container(
          padding: EdgeInsets.all(16.0),
          child: _loading
              ? Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    LinearProgressIndicator(
                      value: _progressValue,
                    ),
                    Text('${(_progressValue * 100).round()}%'),
                  ],
                )
              : Text("Press button to download"),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _loading = !_loading;
            _updateProgress();
          });
        },
        tooltip: 'Download',
        child: Icon(Icons.cloud_download),
      ),
    );
  }

  // we use this function to simulate a download
  // by updating the progress value
  void _updateProgress() {
    const oneSec = const Duration(seconds: 1);
    new Timer.periodic(oneSec, (Timer t) {
      setState(() {
        _progressValue += 0.2;
        // we "finish" downloading here
        if (_progressValue.toStringAsFixed(1) == '1.0') {
          _loading = false;
          t.cancel();
          _progressValue: 0.0;
          return;
        }
      });
    });
  }

And it will look like this:

How to use CircularProgressIndicator in Flutter

CircularProgressIndicator works exactly the same way as LinearProgressIndicator, so we can just replace that first one with the latter:

  bool _loading;
  double _progressValue;

  @override
  void initState() {
    super.initState();
    _loading = false;
    _progressValue = 0.0;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("LinearProgressIndicator"),
      ),
      body: Center(
        child: Container(
          padding: EdgeInsets.all(16.0),
          child: _loading
              ? Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: <Widget>[
                    CircularProgressIndicator(
                      value: _progressValue,
                    ),
                    Text('${(_progressValue * 100).round()}%'),
                  ],
                )
              : Text("Press button to download"),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _loading = !_loading;
            _updateProgress();
          });
        },
        tooltip: 'Download',
        child: Icon(Icons.cloud_download),
      ),
    );
  }

  // we use this function to simulate a download
  // by updating the progress value
  void _updateProgress() {
    const oneSec = const Duration(seconds: 1);
    new Timer.periodic(oneSec, (Timer t) {
      setState(() {
        _progressValue += 0.2;
        // we "finish" downloading here
        if (_progressValue.toStringAsFixed(1) == '1.0') {
          _loading = false;
          t.cancel();
          _progressValue: 0.0;
          return;
        }
      });
    });
  }

Which looks like this:

If you don’t care about how much progress is left and want to have an indeterminate one, just remove the value property and the _updateProgress function. Have fun developing with Flutter.