samseer 0.3.1
samseer: ^0.3.1 copied to clipboard
Beautiful HTTP inspector for Flutter — modern Material 3 alternative to Alice with Dio, http, and HttpClient support, plus shake-to-open.
Samseer #
Beautiful HTTP inspector for Flutter — a modern, all-in-one alternative to Alice, in a single package.
Samseer captures every HTTP request your Flutter app makes — from Dio, the http package, and dart:io HttpClient — and presents it in a polished Material 3 inspector you can open with a shake, a tap on a floating bubble, or a single line of code.
If you've used Alice or Chuck, Samseer is the spiritual successor: same idea, modern UI, single dependency, no separate adapter packages.
✨ Features #
- 🎨 Material 3 UI with light & dark themes that follow the host app
- 🔌 Three HTTP clients out of the box — Dio,
http,dart:io HttpClient— no extra packages - 🌐 WebView traffic too — drop-in JS interceptor for XHR/
fetchinsideflutter_inappwebview, no extra dep on Samseer's side - 🔍 Powerful call list with live search, status & method filters
- 📑 Tabbed call detail — Overview · Request · Response · cURL
- 🌈 Syntax-highlighted JSON viewer built in
- 📊 Stats screen — totals, success rate, avg duration, status distribution
- 📱 Shake-to-open the inspector from anywhere in your app
- 💬 Floating bubble overlay with live call count (draggable)
- 📤 Export & share all calls as JSON, or copy any request as cURL
- 🪶 Single dependency —
samseerand you're done. Nosamseer_dio,samseer_httpetc.
🚀 Quick start #
# pubspec.yaml
dependencies:
samseer: ^0.1.0
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:samseer/samseer.dart';
final samseer = Samseer();
void main() {
// Dio
final dio = Dio()..interceptors.add(samseer.dioInterceptor);
// http package
final httpClient = samseer.httpClient();
// dart:io HttpClient (intercepts every HttpClient created globally)
HttpOverrides.global = samseer.httpOverrides;
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: samseer.navigatorKey, // required for shake & bubble
home: const HomeScreen(),
);
}
}
That's it. Shake your phone or call samseer.showInspector() to open the inspector.
🧩 Integrations #
Dio #
final dio = Dio();
dio.interceptors.add(samseer.dioInterceptor);
http package #
samseer.httpClient() returns a drop-in http.Client replacement. Pass an existing client to wrap it:
final client = samseer.httpClient();
// or wrap an existing one
final wrapped = samseer.httpClient(myExistingClient);
final response = await client.get(Uri.parse('https://api.example.com/me'));
dart:io HttpClient #
Install HttpOverrides once at app startup. Every HttpClient created anywhere in your app — including those used by package:http, Firebase, image libraries, etc. — will be recorded:
HttpOverrides.global = samseer.httpOverrides;
💡 If you set
HttpOverrides.globalyou typically don't needsamseer.httpClient()separately, since thehttppackage usesdart:ioHttpClient under the hood.
Multiple clients at once #
You can use all three integrations simultaneously. Samseer assigns a fresh ID per call so nothing is duplicated.
🎯 Opening the inspector #
| Trigger | How |
|---|---|
| Shake the device | enabled by default; configure with SamseerConfiguration(showInspectorOnShake: false) to disable |
| Floating bubble | wrap your app: MaterialApp(builder: (_, child) => samseer.overlay(child: child!)) |
| Programmatic | samseer.showInspector() (needs navigatorKey) or samseer.showInspectorFromContext(context) |
| From a debug button | wire onPressed: samseer.showInspector to any button or FAB |
🔔 Notifications (optional) #
Want to be pinged on every HTTP call without leaving your screen? Samseer exposes samseer.callsStream so you can drive any notification system you already use. The example below uses flutter_local_notifications, but the same pattern works with Firebase Messaging, Awesome Notifications, or your own in-app banner.
Samseer does not depend on
flutter_local_notifications— you stay in control of the notification stack.
A minimal bridge:
import 'dart:async';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:samseer/samseer.dart';
class SamseerNotificationBridge {
SamseerNotificationBridge({required this.samseer, required this.plugin});
final Samseer samseer;
final FlutterLocalNotificationsPlugin plugin;
static const int _id = 9999;
final Set<int> _seen = {};
StreamSubscription<List<SamseerHttpCall>>? _sub;
void start() {
for (final c in samseer.calls) {
if (!c.loading) _seen.add(c.id);
}
_sub = samseer.callsStream.listen((calls) {
for (final c in calls) {
if (!c.loading && _seen.add(c.id)) _emit(c);
}
_seen.retainAll(calls.map((c) => c.id).toSet());
});
}
bool handleTap(NotificationResponse r) {
if (r.id != _id) return false;
samseer.showInspector();
return true;
}
void _emit(SamseerHttpCall c) {
final status = c.status?.toString() ?? (c.hasError ? 'ERR' : '-');
plugin.show(
_id,
'[${c.method}] $status ${c.endpoint.isEmpty ? c.uri : c.endpoint}',
c.error?.message ?? c.uri,
const NotificationDetails(
android: AndroidNotificationDetails(
'samseer', 'Samseer HTTP calls',
importance: Importance.max, priority: Priority.high,
),
iOS: DarwinNotificationDetails(presentAlert: true, presentBanner: true),
),
);
}
Future<void> dispose() async => _sub?.cancel();
}
Wire it up at startup:
final notifications = FlutterLocalNotificationsPlugin();
final bridge = SamseerNotificationBridge(samseer: samseer, plugin: notifications);
await notifications.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
requestSoundPermission: true,
),
),
onDidReceiveNotificationResponse: bridge.handleTap,
);
bridge.start();
Why this design?
- Reuses one notification id (
9999) so the system replaces the previous notification — your tray doesn't fill up after a chatty screen. - Marks existing calls as already-notified in
start(), so re-attaching the bridge after a hot-reload doesn't spam you with a backlog. handleTapreturnsbool, so you can chain it with your own notification handlers — returntrueonly if it was Samseer's, otherwise fall through.
A complete, runnable version lives in example/lib/samseer_notification_bridge.dart.
💡 Heads up: on Android 13+ you'll need the
POST_NOTIFICATIONSruntime permission, and on iOS the system asks the user the first time the plugin initializes. Keep this integration debug-only — guard it behindkDebugModeor a flavor flag so it never ships in production.
🌐 WebView inspector (optional) #
Want to inspect XHR and fetch calls happening inside a WebView page — the way Chrome DevTools' Network tab shows them? Samseer ships a self-contained JavaScript snippet plus a tiny Dart bridge so you can wire it into flutter_inappwebview (or any WebView library that supports user scripts and JS-to-Dart channels).
Samseer does not depend on
flutter_inappwebview. You bring your own WebView and pass two things across: the script and the handler.
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:samseer/samseer.dart';
class MyWebViewPage extends StatelessWidget {
const MyWebViewPage({super.key, required this.samseer, required this.url});
final Samseer samseer;
final Uri url;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('WebView')),
body: InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(url.toString())),
initialUserScripts: UnmodifiableListView<UserScript>([
UserScript(
source: webViewInterceptorScript,
injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
),
]),
onWebViewCreated: (controller) {
controller.addJavaScriptHandler(
handlerName: 'samseer_webview',
callback: (args) {
if (args.isNotEmpty) samseer.recordWebViewEvent(args.first);
},
);
},
),
);
}
}
How it works:
webViewInterceptorScriptis a string constant that monkey-patchesXMLHttpRequestandfetchinside the page. Inject it at document start so it wraps the JS APIs before any application code runs.- Each request, response, and error is forwarded to the
samseer_webviewJavaScript handler.samseer.recordWebViewEvent(args.first)translates the payload into a regular Samseer call, indistinguishable from Dio/http/HttpCliententries (just labelledclient: WebView). - Re-installation is idempotent — guarded internally by a
__samseer_installedflag — so revisiting pages is safe.
Limitations to know:
- Only XHR and
fetchare captured. Static resources (images, CSS, fonts) and main-frame navigation are not — those don't typically appear in DevTools' Fetch/XHR filter either. - Streaming / opaque responses (CORS
no-cors, SSE) record withbody: null— Samseer doesn't try to read past the headers in those cases. - Each response body is held in memory once before being forwarded; if your page downloads very large payloads, consider whether you want this active in production.
💡 Like the notification bridge, treat this as a debug-only integration — guard the script injection behind
kDebugModeor a build flavor so it doesn't ship in release builds.
A complete, runnable demo lives in example/lib/samseer_webview_demo_page.dart.
⚙️ Configuration #
final samseer = Samseer(
configuration: const SamseerConfiguration(
maxCallsCount: 500,
showInspectorOnShake: true,
showFloatingBubble: false,
themeMode: ThemeMode.system,
shakeThreshold: 20,
),
);
| Option | Default | Description |
|---|---|---|
maxCallsCount |
1000 |
Older calls are evicted FIFO once the limit is hit |
showInspectorOnShake |
true |
Shake the device to open the inspector |
showFloatingBubble |
false |
Set to true and wrap with samseer.overlay(...) |
themeMode |
ThemeMode.system |
Forces light/dark theme of the inspector |
shakeThreshold |
20 (m/s²) |
Higher value = harder shake required |
directionality |
null |
Force RTL/LTR inside the inspector |
🔄 Migrating from Alice #
| Alice | Samseer |
|---|---|
Alice |
Samseer |
alice.getNavigatorKey() |
samseer.navigatorKey |
dio.interceptors.add(AliceDioAdapter(...)) |
dio.interceptors.add(samseer.dioInterceptor) |
AliceHttpAdapter |
samseer.httpClient() (drop-in http.Client) |
AliceHttpClientAdapter |
samseer.httpOverrides (global HttpOverrides) |
alice.showInspector() |
samseer.showInspector() |
Multiple packages (alice_dio, alice_http, ...) |
One package — samseer |
Why switch?
- 🪄 Single dependency instead of 3-5 separate adapter packages.
- 🎨 Modern Material 3 UI with proper light/dark mode and Google Fonts typography.
- 📑 Tabbed call detail with built-in JSON syntax highlighting and one-tap cURL copy.
- 💬 Floating bubble as a more ergonomic alternative to system notifications.
🧪 Example #
A complete example is in example/. Run it:
cd example
flutter run
Tap any of the buttons to fire requests — they'll appear in the inspector live.
📦 What's included vs not (yet) #
✅ Included in 0.1.x
- Dio +
http+HttpClientinterception - Material 3 inspector UI (list, detail, stats)
- JSON viewer, cURL export, file export
- Shake detection, floating bubble
- In-memory storage with FIFO eviction
🛣️ Roadmap
- Persistent storage (Hive/Isar) so calls survive app restart
- Chopper, GraphQL, Cronet integrations
- Mock-and-replay (intercept and override responses for testing)
- Web + Desktop platform polish (currently mobile-first)
- Built-in Sentry/Crashlytics breadcrumb hooks
🤝 Contribute with us #
Samseer is open and growing — and I'd love your help to make it the best HTTP inspector in the Flutter ecosystem. Whether you're squashing bugs, adding a new HTTP client integration, polishing the UI, or improving docs — your contribution is welcome.
Ways to contribute
- 🐛 Bug reports & feature requests — open an issue on GitHub
- 🚀 Code contributions — fork, branch, send a PR. Make sure these still pass:
flutter analyze flutter test - 🎨 Design feedback — share screenshots, mockups, or UX ideas. The bar is "fancier than Alice" 😉
- 🌐 HTTP client integrations — Chopper, GraphQL, Cronet, etc. are on the roadmap
- 📣 Spread the word — star the repo, share with your team, write a blog post
First-time contributors are very welcome. Pick anything from the roadmap, open an issue first to discuss your approach, and let's build it together.
☕ Support this project #
Samseer is built and maintained on personal time. If it saves you debugging hours, treat me to a coffee — every bit of support helps me keep building, polishing, and shipping new features.
Sponsors get a special thank-you in the next release notes. 🙏
Made with 💙 by Samuel Jiwandono — and hopefully you next.