1

when i try to use CircularProgressIndicator with slow async method, indicator is not shown. When i replace slow custom method with Timer.pereodic() that works fine. I am new in Flutter and do not understand what i am doing wrong

class _MyHomePageState extends State<MyHomePage> {
  bool _inProgress = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          width: 200,
          height: 200,
          child: Column(
            children: [
              _inProgress ? CircularProgressIndicator() : Text("ready"),
              FloatingActionButton(onPressed: _slowMethod)
            ],
          ),
        ),
      ),
    );
  }

  int fibonacci(int n) {
    return n <= 2 ? 1 : fibonacci(n - 2) + fibonacci(n - 1);
  }

  _slowMethod() async {
    setState(() {
      _inProgress = true;
    });

    for (int i = 20; i <= 100; ++i) {
      print(fibonacci(i));
    }

    setState(() {
      _inProgress = false;
    });
  }
}
ohlushenok
  • 13
  • 2
  • why is the method async ? – Obum May 03 '22 at 13:18
  • seems like I found the answer, you used async but there is not await – Obum May 03 '22 at 13:31
  • it does not depend, synchronously it works the same. Actually i want to have "clickable" UI while method executing – ohlushenok May 03 '22 at 13:39
  • okay, you know that when there is await inside the code, execution will pause at that point – Obum May 03 '22 at 13:40
  • In Dart there is just one main thread of execution, so during the while of your `for` loop that single thread is completely compute bound and unable to run `build` to update the UI. In other words, you set in progress, do some computations and clear in progress, but at no time in that do you allow Flutter to build the UI - your computation is consuming the available thread. Just because the method is async doesn't make the VM magically make it yield. However, *you* can yield it by, for example, `await Future.delayed()` with a short delay of a few milliseconds between each calculation.... – Richard Heap May 03 '22 at 14:06
  • 1
    ... note that even with this yielding behaviour you may find the UI sluggish as you are still doing computation on the main Dart thread. Dart Isolates are the way to instantiate additional threads if you actually have heavy computations to perform. (I'm guessing this is just an example.) – Richard Heap May 03 '22 at 14:09
  • very correct, I'm mentioning you too in the answer. Thank you – Obum May 03 '22 at 14:13
  • Thank you very much for the answers. As you guessed correctly, my code is just an example. in the production code, I still need threads. Thanks for explaining how it works, I thought the asynchronous method is executed in a separate thread. I've already tried it, "Isolate" works great for me – ohlushenok May 03 '22 at 15:44

1 Answers1

1

An async function runs synchronously until the first await keyword.

In the first place, there is no await keyword in the _slowMethod, and that technically means you need to wrap the "what-should-be-asynchronous" operation in a Future and await for it.

So what should be your solution is something like the following for the _slowMethod():

  _slowMethod() async {
    setState(() {
      _inProgress = true;
    });

    await Future(() {
      for (int i = 20; i <= 100; ++i) {
        print(fibonacci(i));
      }
    });

    setState(() {
      _inProgress = false;
    });
  }

But then as Richard Heap (@Richard Heap) pointed in the comments, the above would have issues working. If you run the code, the CircularProgressIndicator will have problems displaying because the main Dart thread has been hijacked by the demanding fibonacci sequence and your UI won't render properly.

I'm supposing that you really might not have a Fibonacci of up to 100 in production code. That probably, you used it to show us the problem. But even if it is the case or you have complex asynchronous operations, you could use Isolates as Richard mentioned.

If the asynchronous operation is not very demanding on the main thread, (like simply doing Future.delayed), awaiting the future should work.

The following snippet will behave as you expect.

  _slowMethod() async {
    setState(() {
      _inProgress = true;
    });

    await Future.delayed(const Duration(seconds: 3));

    setState(() {
      _inProgress = false;
    });
  }
Josip Domazet
  • 2,246
  • 2
  • 14
  • 37
Obum
  • 1,485
  • 3
  • 7
  • 20