Want to Code Like a Senior Flutter Dev? Try These Extensions! (Part 3)

Want to Code Like a Senior Flutter Dev? Try These Extensions! (Part 3)

TB

Teqani Blogs

Writer at Teqani

August 14, 20254 min read
This article explores essential Flutter extensions for efficient development. We'll cover debouncing TextEditingController, waiting for widget size, safe setState, and auto-retry futures, enhancing your coding workflow. Learn to avoid common pitfalls and write cleaner, more robust Flutter code. These tools will significantly boost your productivity and reduce boilerplate.

Debounced TextEditingController

Ever built a search box and realized your app is making a network call on every keystroke? A debounced TextEditingController waits for the user to stop typing before triggering the action, optimizing API usage and improving user experience.

It’s like sending a pizza order for each topping individually — inefficient and annoying.

That’s where a debounced TextEditingController comes in.

It waits for the user to stop typing before doing the thing, so your API (and your sanity) stay happy.

extension DebouncedTextController on TextEditingController {

void onChangedDebounced(void Function(String) callback,

{Duration delay = const Duration(milliseconds: 300)}) {

Timer? _debounce;

addListener(() {

if (_debounce?.isActive ?? false) _debounce!.cancel();

_debounce = Timer(delay, () => callback(text));

});

}

}

Usage

controller.onChangedDebounced((value) {

print('Searching for $value');

});

Wait for Widget Size

Have you ever tried to get a widget’s size in initState only to get 0.0? Sometimes you need to wait until Flutter finishes laying things out before you can measure them. With the WaitForSize extension, you can retrieve the widget size after Flutter finishes rendering.

It’s like asking someone their height while they’re still putting on their shorts .

Sometimes you need to wait until Flutter finishes laying things out before you can measure them.

With a handy extension, you can say “Hey widget, tell me your size when you’re ready”  — no more layout timing headaches.

extension WaitForSize on GlobalKey {

void onWidgetSize(Function(Size size) callback) {

WidgetsBinding.instance.addPostFrameCallback((_) {

final context = currentContext;

if (context != null) {

final renderBox = context.findRenderObject() as RenderBox?;

if (renderBox != null) {

callback(renderBox.size);

}

}

});

}

}

Usage

void main() {

runApp(const MyApp());

}

class MyApp extends StatelessWidget {

const MyApp({super.key});

@override

Widget build(BuildContext context) {

final key = GlobalKey();

return MaterialApp(

home: Scaffold(

appBar: AppBar(title: const Text('Wait for Widget Size')),

body: Center(

child: Container(

key: key,

color: Colors.amber,

width: 200,

height: 100,

child: const Center(child: Text('Measure me!')),

),

),

floatingActionButton: FloatingActionButton(

onPressed: () {

key.onWidgetSize((size) {

debugPrint('Widget size: $size');

});

},

child: const Icon(Icons.straighten),

),

),

);

}

}

Safe SetState

Avoid the dreaded "setState() called after dispose()" error. The SafeSetState extension ensures setState is only called if the widget is still mounted, preventing crashes and simplifying asynchronous operations.

We’ve all been there — you fire off an async call, the user navigates away, and bam!

Flutter yells at you: setState() called after dispose()

This tiny extension is like a bouncer for your widget’s state — it only lets setState in if the widget is still mounted. No more late-night debugging because you forgot to check.

extension SafeSetState on State {

void setStateSafe(VoidCallback fn) {

if (mounted) setState(fn);

}

}

Usage

setStateSafe(() => counter++);

Auto-Retry Futures with Delay

Handle transient API failures gracefully with the Auto-Retry Futures extension. This extension automatically retries failing API calls with a configurable delay, improving resilience and user experience. Use cautiously to avoid infinite loops!

Sometimes your API call just… fails.

Not because your code is bad (of course not ), but maybe the Wi-Fi took a coffee break.

This extension politely says, “Don’t worry, I’ll try again… and again… and again,”

with a delay between retries — until it finally works or gives up gracefully.

But a word of caution — this is your go-to for a single API call .

If you slap it on nested API calls, you’ll have retries inside retries, and soon your code will feel like Inception with network requests.

extension RetryFuture<T> on Future<T> {

Future<T> retry({

int retries = 3,

Duration delay = const Duration(seconds: 1),

}) async {

assert(retries > 0, "Retries must be greater than 0");

late Object lastError;

for (int attempt = 0; attempt < retries; attempt++) {

try {

return await this;

} catch (e) {

lastError = e;

if (attempt < retries - 1) {

await Future.delayed(delay);

}

}

}

throw lastError;

}

}

Usage.

try {

final result = await fakeApiCall(data)

.retry(retries: 3, delay: const Duration(seconds: 2));

setState(() => status = result);

} catch (e) {

setState(() => status = " Failed after retries: $e");

}

May your builds be fast, your widgets be stateless, and your extensions be magical.Until Next Time.

TB

Teqani Blogs

Verified
Writer at Teqani

Senior Software Engineer with 10 years of experience

August 14, 2025
Teqani Certified

All blogs are certified by our company and reviewed by our specialists
Issue Number: #4870d4ca-2d00-4765-947c-cdc5cd239282