dev_log_client 0.2.1
dev_log_client: ^0.2.1 copied to clipboard
Flutter/Dart client for dev_log_viewer. Forwards logs to the local viewer with a Dio interceptor, AppLog shortcuts, and raw log sender.
import 'package:dev_log_client/dev_log_client.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
// Option B: add DevLogInterceptor so every API request/response streams to the
// viewer automatically — no other changes needed.
final _dio = Dio(BaseOptions(baseUrl: 'https://jsonplaceholder.typicode.com'))
..interceptors.add(DevLogInterceptor());
void main() {
// Connect to the Dev Log Viewer server running on localhost.
// Auto-discovers on ports 8181–8185; pass port: 8182 to be explicit.
// Pass project: on iOS/Android — the sandbox has no pubspec.yaml to read.
LogForwarder.init(project: 'example');
// This log is sent immediately — before discovery finishes.
// It gets buffered and delivered once a viewer is found (usually < 500 ms).
LogForwarder.send(tag: 'STARTUP', message: 'App launched');
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dev Log Client Example',
theme: ThemeData(
colorSchemeSeed: Colors.indigo,
useMaterial3: true,
),
home: const _HomePage(),
);
}
}
class _HomePage extends StatelessWidget {
const _HomePage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dev Log Viewer — Example')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// ── Option A ──────────────────────────────────────────────────────
_SectionHeader('Option A — LogForwarder.send()'),
_DemoTile(
label: 'Send info log',
onTap: () => LogForwarder.send(
tag: 'DEMO',
message: 'Button tapped at ${DateTime.now()}',
),
),
_DemoTile(
label: 'Send error log',
onTap: () => LogForwarder.send(
tag: 'DEMO',
message: 'Something went wrong',
level: 'error',
error: 'StateError: value was null',
body: {'context': 'checkout', 'attempt': 2},
),
),
_DemoTile(
label: 'Send structured JSON (try searching isSettled:true)',
onTap: () => LogForwarder.send(
tag: 'PAYMENT',
message: 'Transaction completed',
body: {
'transactionId': 'txn_abc123',
'amount': 49.99,
'currency': 'USD',
'isSettled': true,
'metadata': {'retries': 0, 'gateway': 'stripe'},
},
),
),
_DemoTile(
label: 'Send pending transaction (isSettled:false)',
onTap: () => LogForwarder.send(
tag: 'PAYMENT',
message: 'Transaction pending',
body: {
'transactionId': 'txn_xyz789',
'amount': 120.00,
'currency': 'USD',
'isSettled': false,
'metadata': {'retries': 1, 'gateway': 'stripe'},
},
),
),
const SizedBox(height: 24),
// ── Option B ──────────────────────────────────────────────────────
_SectionHeader('Option B — DevLogInterceptor (Dio)'),
_DemoTile(
label: 'GET /posts/1',
onTap: () => _dio.get('/posts/1'),
),
_DemoTile(
label: 'POST /posts',
onTap: () => _dio.post('/posts', data: {
'title': 'Hello from dev_log_client',
'body': 'Example post',
'userId': 1,
}),
),
_DemoTile(
label: 'GET /404 (triggers error log)',
onTap: () async {
try {
await _dio.get('/this-route-does-not-exist');
} on DioException catch (_) {}
},
),
const SizedBox(height: 24),
// ── Option C ──────────────────────────────────────────────────────
_SectionHeader('Option C — AppLog shortcuts'),
_DemoTile(
label: 'AppLog.auth()',
onTap: () => AppLog.auth('User signed in: demo@example.com'),
),
_DemoTile(
label: 'AppLog.nav()',
onTap: () => AppLog.nav('Pushed /checkout'),
),
_DemoTile(
label: 'AppLog.store()',
onTap: () => AppLog.store('Cart updated — 3 items'),
),
_DemoTile(
label: 'AppLog.log() — custom tag',
onTap: () => AppLog.log('PAYMENT', 'Stripe session created'),
),
_DemoTile(
label: 'AppLog.error()',
onTap: () {
try {
throw StateError('Charge declined');
} catch (e, s) {
AppLog.error('PAY', 'Charge failed', error: e, stack: s);
}
},
),
],
),
);
}
}
class _SectionHeader extends StatelessWidget {
const _SectionHeader(this.title);
final String title;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Text(title, style: Theme.of(context).textTheme.labelLarge),
);
}
}
class _DemoTile extends StatelessWidget {
const _DemoTile({required this.label, required this.onTap});
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return Card.outlined(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
title: Text(label),
trailing: const Icon(Icons.send_outlined),
onTap: onTap,
),
);
}
}