You’ve probably encountered this scenario before: you click a button, you’re not sure if it worked, so you spam it a few times to make sure.
What’s happening behind the scenes is most likely a dumpster fire. Those requests then all go to your API, some fail, and some go through, which can mess up systems when not handled properly. This phenomenon is commonly referred to as granny clicking, spamming, or button mashing.
In Flutter, we can fix this by using Mixins.
Primary Button with Loading State
This StatefulWidget
is a primary button. By using the keyword with
, we can add the LoadingStateMixin
to our widget. This provides us with an isLoading
notifier and a function called withLoading
.
We will pass our callback into the withLoading
function, which will trigger the loading state and block any other button taps until the loading is complete.
class PrimaryButton extends StatefulWidget { final String text; final Future<void> Function() onPressed;
const PrimaryButton({ super.key, required this.text, required this.onPressed, });
@override State<PrimaryButton> createState() => _PrimaryButtonState();}
class _PrimaryButtonState extends State<PrimaryButton> with LoadingStateMixin { @override Widget build(BuildContext context) { return ValueListenableBuilder<bool>( valueListenable: isLoading, builder: (context, loading, child) { return InkWell( onTap: withLoading(widget.onPressed), child: Container( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), child: Text( widget.text, ), ) ); }, ); }}
If the withLoading
method is called while it’s already in the process of loading, it will simply return early. Otherwise, it will call the callback function once it’s done running and toggle the loading state on the notifier.
mixin LoadingStateMixin<T extends StatefulWidget> on State<T> { final ValueNotifier<bool> isLoading = ValueNotifier(false);
Future<void> withLoading(Future<void> Function() callback) async { if (isLoading.value) { return; }
isLoading.value = true; try { await callback(); } finally { isLoading.value = false; } }
@override void dispose() { isLoading.dispose(); super.dispose(); }}
In our button scenario, you would want to show a loading indication when there are asynchronous tasks running. You have access to the loading state, so you can change the UI however you prefer. The nice aspect of using this mixin is that the logic and granny taps will never occur while it is in use. A fancier UI, while recommended, is entirely optional.

Want to learn Flutter?
We teach you all you need to confidently build apps at hungrimind.com/learn/flutter
Using a Mixin
The main reason we use a mixin for this is to avoid reimplementing the functionality of both loading indication and spam tapping on buttons for every button. All this is accomplished with a single mixin.
Thank you for reading!
YouTube Video
Get articles right in your inbox
No spam, unsubscribe anytime. We treat your inbox with respect.