Thunder ⚡️
A powerful Flutter debug overlay for monitoring network requests in real-time. Thunder provides a convenient slide-out panel that shows all network interactions from your package:http-based clients and WebSockets.
Features
- 📱 Simple Integration - Add a single widget to your app
- 📈 Network Monitoring - Track all requests and responses via a middleware for
package:http-based clients - 🔌 WebSocket Monitoring - Reconnecting
SocketClientplus a Socket tab with a live per-connection event timeline - 🔎 Search & Filter - Easily find specific network calls
- 🗑️ Clear Logs - One-tap to remove all logs
- 👆 Interactive UI - Slide-out panel with intuitive controls
- 🛠️ Debug Mode Only - Automatically disabled in release builds
- 📊 Request Details - View headers, payloads, and responses
Platform Support
| Android | iOS | MacOS | Web | Linux | Windows |
|---|---|---|---|---|---|
| ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Installation
Add Thunder to your pubspec.yaml:
dependencies:
thunder: ^1.1.0-dev.4
Then run:
flutter pub get
Usage
Basic Setup
Wrap your app with the Thunder widget and plug Thunder.middleware into
your package:http-based client's middleware chain. Thunder exports the
middleware types (ApiClientMiddleware, ApiClientHandler,
ApiClientRequest, ApiClientResponse); the client itself is yours — see
the example app's ApiClient
for a complete implementation:
import 'package:thunder/thunder.dart';
void main() {
final client = ApiClient(
baseUrl: 'https://jsonplaceholder.typicode.com',
middlewares: <ApiClientMiddleware>[Thunder.middleware],
);
runApp(MyApp(client: client));
}
class MyApp extends StatelessWidget {
const MyApp({required this.client, super.key});
final ApiClient client;
@override
Widget build(BuildContext context) => MaterialApp(
title: 'My App',
home: const HomePage(),
builder: (context, child) =>
Thunder(child: child ?? const SizedBox.shrink()),
);
}
Thunder.middleware works even before a Thunder widget is created, so the
client can be constructed anywhere — requests made before the overlay mounts
are captured once it appears.
Pausing collection
Set Thunder.middlewareEnabled = false to turn the middleware into a pure
pass-through: requests flow unchanged and nothing is collected or shown in
the panel. Set it back to true to resume. Socket interceptors have the
same switch — Thunder.socketClient(enabled: false) /
Thunder.webSocketInterceptor(enabled: false) at creation time, or flip
enabled on a ThunderWebSocketInterceptor at runtime.
How to Use
- Run your app in debug mode
- Tap the handle on the left side of the screen to reveal the Thunder panel
- Make network requests in your app to see them appear in the panel
- Use the search button to find specific requests
- Use the filter button to sort requests
- Use the delete button to clear all logs
WebSocket Monitoring
Thunder ships a reconnecting WebSocket client built on
web_socket_channel ^3.0.3.
Create it through Thunder.socketClient and the whole connection lifecycle —
sent/received frames, state transitions, errors — is recorded as a session in
the Socket tab of the overlay:
final socket = Thunder.socketClient(
uri: Uri.parse('wss://echo.websocket.org'),
label: 'Echo demo', // optional session name
reconnectInterval: const Duration(seconds: 3),
connectTimeout: const Duration(seconds: 10),
);
socket.states.listen((state) => print('state: $state'));
socket.messages.listen((message) => print('message: $message'));
await socket.connect();
socket.send('hello');
// close() is final: the client stops reconnecting and both streams
// complete. Create a new client to connect again.
await socket.close();
The client reconnects indefinitely (spaced by reconnectInterval) until
close() is called, and connect() never throws on a failed dial — watch
the states stream (SocketConnecting, SocketConnected,
SocketReconnecting, SocketDisconnected) for the outcome.
Monitoring a self-managed WebSocket
If you already manage your own channel (plain web_socket_channel, STOMP,
GraphQL subscriptions, ...), attach only the logging hook. One interceptor
equals one session row in the Socket tab:
final logger = Thunder.webSocketInterceptor(uri: uri);
final channel = WebSocketChannel.connect(uri);
logger.logState(const SocketConnecting());
await channel.ready;
logger.logState(const SocketConnected());
channel.stream.listen(logger.logReceived);
logger.logSent('hello');
channel.sink.add('hello');
The Socket tab
The overlay's HTTP and Socket tabs work independently, each with its
own empty state. The Socket tab shows one row per connection — URI, a live
state dot, ↑ sent / ↓ received counters, total bytes and the last
activity time. Tapping a session opens its chronological timeline where
frames render as aligned cards and state/error events as centered pills.
Long-press any timeline row to copy its message text; the floating button
copies the whole session transcript. The toolbar is tab-aware: search
filters the visible section and the delete button clears only it.
Platform note:
headersandpingIntervalonly apply ondart:ioplatforms (Android, iOS, desktop) — browser WebSockets don't support them.
Configuration
Thunder can be customized with these parameters:
Thunder(
// Optional: Enable/disable the overlay (defaults to kDebugMode)
enabled: true,
// Optional: Animation duration for the slide-out panel
duration: const Duration(milliseconds: 250),
// Optional: Color of the handle
color: Colors.green,
// Required: Your app's main widget
child: yourAppWidget,
);
How It Works
Every request flowing through Thunder.middleware is captured — request,
response, error and timing — and displayed in a user-friendly interface that
can be accessed by tapping the handle on the side of your app.
The overlay shows:
- Request method (GET, POST, PUT, DELETE, etc.)
- URL
- Status code
- Response time
- Request and response headers
- Request and response bodies (HTML bodies are detected and flagged, not rendered)
Example Project
For a complete working example, check the example directory.
Contributing
Contributions are welcome! If you find a bug or want a feature, please:
- Check if an issue already exists
- Create a new issue if needed
- Fork the repo
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.