Let them notice: delay your Futures in Flutter
Our apps must be fast. But sometimes it makes sense to slow them down on purpose.
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.
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 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
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.
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.