Flutter apps for firewall season: making your app usable when Pakistani internet is crawling
Timeouts, polite retries, cache first, an outbox for writes, and the splash screen that kills your app
Contents
Yaar, if you build apps in Pakistan you know the seasons. Summer, monsoon, wedding season, and firewall season. When the internet slows down "for reasons", VPNs crawl, WhatsApp images do not load, and my clients in Dubai think our app is broken. Our app is not broken. The pipe is.
So this is what we changed in our Flutter apps. We are a two person studio, the clients are in Dubai and Riyadh, but most of our test users are in Lahore. Chalo.
1. connectivity_plus tells you the network type, not whether the internet works
It says "wifi" while the wifi is connected to a router that is connected to nothing. Do not show "You are online ✅" based on it. If you really need to know, ping your own health endpoint with a short timeout.
2. Set real timeouts
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 8),
receiveTimeout: const Duration(seconds: 25),
sendTimeout: const Duration(seconds: 25),
));
Short connect timeout, because a TLS handshake that hangs is usually dead. Longer receive timeout, because a slow response is still a response.
3. Retry, but like a gentleman
Future<T> withRetry<T>(Future<T> Function() fn, {int maxAttempts = 4}) async {
final rng = Random();
var attempt = 0;
while (true) {
try {
return await fn();
} on DioException catch (e) {
attempt++;
final retryable = e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.connectionError;
if (!retryable || attempt >= maxAttempts) rethrow;
final base = 500 * (1 << attempt); // 1s, 2s, 4s
await Future.delayed(Duration(milliseconds: base + rng.nextInt(base ~/ 2)));
}
}
}
Only retry GETs and requests that carry an idempotency key. Retrying a payment POST without a key is how you charge someone twice, ask any fintech dev.
4. Cache first, network second
Show the last data you had immediately, with "updated 12 min ago", then refresh in the background. We use drift (SQLite) for lists and a small key-value store for settings. Users forgive old data. They do not forgive a spinner.
5. An outbox for writes
When the user submits a form with no network, put it in a local outbox table with a UUID, show it as "sending", and sync when the network comes back. The UUID goes out as the idempotency key, so syncing twice does no harm.
6. Images will kill you
CachedNetworkImage(
imageUrl: product.thumbUrl,
memCacheWidth: 300,
placeholder: (_, __) => const ColoredBox(color: Color(0xFFEDEDED)),
errorWidget: (_, __, ___) => const Icon(Icons.image_not_supported_outlined),
)
Ask the backend for thumbnails. A 4MB photo from the client's iPhone in a product list is a crime. memCacheWidth decodes it smaller so cheap phones do not run out of memory.
7. Do not block startup on the network
If your splash screen waits for remote config, in firewall season your app never opens. Ship defaults, start, update the config in the background.
8. Test with a bad network on purpose
The Android emulator has network speed and latency settings under Extended controls. Set it to EDGE and use your own app for ten minutes. You will fix five things the same day. Guaranteed.
Koi gal nahi, the internet always comes back. Your users will remember which apps still worked when it did not.
Written by
Hamza Butt
Flutter dev, Lahore. Apps for clients in Dubai and Riyadh. Internet slow ho to samajh jao firewall ka mausam hai. Chai, cricket, Lahori nashta.
Get the best of #thumb_zone weekly
One email each Monday with the most useful posts. No spam; unsubscribe in one click.
Comments
Sign in to join the conversation.
Sign in to comment