Let them notice: delay your Futures in Flutter

Our apps must be fast. But sometimes it makes sense to slow them down on purpose.

Nikita Sirovskiy
3 min readJan 20, 2023

We often use loaders to indicate when an API call or a long asynchronous operation is being executed. These loaders let the user know that they need to wait for the task to complete.

However, if the loader is shown and hidden too quickly, it can be confusing and frustrating for the user. To make the loaders more effective, we can add a slight delay before they are hidden.

Users need time to understand what’s happening.

Photo by Brett Jordan on Unsplash

Quick example, or why we are doing it

Let’s pretend we have some cool pin page built with the power of the pinput package. When a user completes typing the pin code, we’ll verify it’s correct. If it’s not, we’ll show an error. Let’s do it!

The problem is that the verification process is very quick. Retrieving the pin code from local storage and confirming that it matches the user’s input is a swift task.

Without delay

The pin is verified without any delay.

The keyboard may be distracting the user from the main focus of the app, which is the pin dots. The result is not user friendly, because

Users need time to understand what’s happening.

Let’s delay our verification

Some delay makes it more user friendly.

Neat.

  • The keyboard has enough time to hide. Its animation won’t disturb the user from the pin animation.
  • The user has enough time to focus their eyes on the dots.
  • And enough time to observe that the dots have become lighter, indicating that the pin is being verified.

Let’s get to the code part

Our verification method looks like this:

Future<void> verify(String pin) async {
/// Pretending to fetch from local storage ...
await Future.delayed(const Duration(milliseconds: 100));

if (pin != '123456') {
throw const FormatException();
}
}

And here’s our onSubmitted method:

Future<void> onSubmitted(String pin) async {
try {
setState(() {
// Making the pin go a bit lighter indicating that the pin is being verified.
submittedColor = Colors.lightBlueAccent;
});

// Delaying the call 👇
await Future.delayed(
const Duration(milliseconds: 850),
() => verify(pin),
);

// If no exception — we're good!
setState(() {
submittedColor = Colors.lightGreen;
});

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

// Navigate the user further ...
} on Exception {
shakeController.shake();

setState(() {
error = "Pin is incorrect.";
showError = true;
});

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

controller.text = '';
setState(() {
showError = false;
submittedColor = Colors.blue;
});
}
}

We can use the same approach for any async operation during which we show loaders.

It may seem paradoxical, but when I press the ‘Save’ button, I expect the task to take at least a brief moment to complete. Simply showing me a success or error message and then navigating me away does not seem sufficient.

That’s it, folks!

Let me know what you think about it.

You can find the source code on GitHub.

Reach out to me in Telegram or join my technical blog there. I will be happy to have you there.

Peace and light to your house.

--

--

Nikita Sirovskiy

A value creator who tries to enhance the world. Thinker & writer, lead mobile developer at Genieology and, hopefully, a good human.