droido 1.1.0 copy "droido: ^1.1.0" to clipboard
droido: ^1.1.0 copied to clipboard

A lightweight debug-only network inspector for Flutter apps. Works with Dio, HTTP package, and Retrofit. Features persistent notification UI and clean modern design. Zero impact on release builds.

example/lib/main.dart

import 'package:dio/dio.dart';
import 'package:droido/droido.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'api_client.dart';

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

// Shared clients - initialized in main()
late final Dio dio;
late final http.Client httpClient;
late final ApiClient retrofitClient;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Create Dio instance (used for both Dio and Retrofit)
  dio = Dio(
    BaseOptions(
      baseUrl: 'https://jsonplaceholder.typicode.com',
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
    ),
  );

  // Create raw HTTP client
  final rawHttpClient = http.Client();

  // ✨ Single init - pass clients and navigatorKey!
  // Droido handles everything: interceptors + notification callback
  await Droido.init(
    dio: dio,
    httpClient: rawHttpClient,
    navigatorKey: navigatorKey,  // Auto-handles notification tap!
    config: const DroidoConfig(
      maxLogs: 500,
      enableNotification: true,
      notificationTitle: 'Droido Example',
    ),
  );

  // Get the wrapped HTTP client for use
  httpClient = Droido.httpClient!;

  // Create Retrofit client using the same Dio instance
  // Requests are automatically intercepted!
  retrofitClient = ApiClient(dio);

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Droido Multi-Client Example',
      navigatorKey: navigatorKey,
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Droido Demo'),
          backgroundColor: Theme.of(context).colorScheme.inversePrimary,
          actions: [
            IconButton(
              icon: const Icon(Icons.bug_report),
              tooltip: 'Open Debug Panel',
              onPressed: () => Navigator.push(
                context,
                MaterialPageRoute(builder: (_) => const DroidoPanel()),
              ),
            ),
            IconButton(
              icon: const Icon(Icons.delete_outline),
              tooltip: 'Clear Logs',
              onPressed: () async {
                await Droido.clearLogs();
                if (context.mounted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Logs cleared')),
                  );
                }
              },
            ),
          ],
          bottom: const TabBar(
            tabs: [
              Tab(icon: Icon(Icons.bolt), text: 'Dio'),
              Tab(icon: Icon(Icons.http), text: 'HTTP'),
              Tab(icon: Icon(Icons.api), text: 'Retrofit'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            DioTab(),
            HttpTab(),
            RetrofitTab(),
          ],
        ),
      ),
    );
  }
}

// ============ DIO TAB ============
class DioTab extends StatelessWidget {
  const DioTab({super.key});

  @override
  Widget build(BuildContext context) {
    return _RequestsPanel(
      title: 'Dio Requests',
      subtitle: 'Using Dio HTTP client with interceptor',
      color: Colors.blue,
      icon: Icons.bolt,
      requests: [
        _RequestAction(
          'GET Post',
          'Fetch a single post',
          Icons.download,
          () => dio.get('/posts/1'),
        ),
        _RequestAction(
          'POST Data',
          'Create a new post',
          Icons.upload,
          () => dio.post(
            '/posts',
            data: {
              'title': 'Test from Dio',
              'body': 'This is a test post from Dio client',
              'userId': 1,
            },
          ),
        ),
        _RequestAction(
          'GET Users',
          'Fetch all users',
          Icons.people,
          () => dio.get('/users'),
        ),
        _RequestAction(
          'PUT Update',
          'Update an existing post',
          Icons.edit,
          () => dio.put(
            '/posts/1',
            data: {'title': 'Updated Title', 'body': 'Updated body'},
          ),
        ),
        _RequestAction(
          'DELETE Post',
          'Delete a post',
          Icons.delete,
          () => dio.delete('/posts/1'),
        ),
        _RequestAction(
          'Error 404',
          'Trigger a 404 error',
          Icons.error,
          () => dio.get('/posts/99999'),
        ),
      ],
    );
  }
}

// ============ HTTP TAB ============
class HttpTab extends StatelessWidget {
  const HttpTab({super.key});

  @override
  Widget build(BuildContext context) {
    const baseUrl = 'https://jsonplaceholder.typicode.com';

    return _RequestsPanel(
      title: 'HTTP Package Requests',
      subtitle: 'Using dart:http package with Droido wrapper',
      color: Colors.green,
      icon: Icons.http,
      requests: [
        _RequestAction(
          'GET Post',
          'Fetch a single post',
          Icons.download,
          () => httpClient.get(Uri.parse('$baseUrl/posts/1')),
        ),
        _RequestAction(
          'POST Data',
          'Create a new post',
          Icons.upload,
          () => httpClient.post(
            Uri.parse('$baseUrl/posts'),
            headers: {'Content-Type': 'application/json'},
            body: '{"title": "Test from HTTP", "body": "HTTP package test", "userId": 1}',
          ),
        ),
        _RequestAction(
          'GET Comments',
          'Fetch comments for a post',
          Icons.comment,
          () => httpClient.get(Uri.parse('$baseUrl/comments?postId=1')),
        ),
        _RequestAction(
          'GET Albums',
          'Fetch all albums',
          Icons.album,
          () => httpClient.get(Uri.parse('$baseUrl/albums')),
        ),
        _RequestAction(
          'Error 404',
          'Trigger a 404 error',
          Icons.error,
          () => httpClient.get(Uri.parse('$baseUrl/posts/99999')),
        ),
      ],
    );
  }
}

// ============ RETROFIT TAB ============
class RetrofitTab extends StatelessWidget {
  const RetrofitTab({super.key});

  @override
  Widget build(BuildContext context) {
    return _RequestsPanel(
      title: 'Retrofit Requests',
      subtitle: 'Type-safe API calls via Retrofit (uses Dio)',
      color: Colors.purple,
      icon: Icons.api,
      requests: [
        _RequestAction(
          'GET Post',
          'Fetch post with typed response',
          Icons.download,
          () => retrofitClient.getPost(1),
        ),
        _RequestAction(
          'GET All Posts',
          'Fetch all posts as List<Post>',
          Icons.list,
          () => retrofitClient.getPosts(),
        ),
        _RequestAction(
          'CREATE Post',
          'Create post with typed body',
          Icons.add,
          () => retrofitClient.createPost(
            Post(
              title: 'Retrofit Test',
              body: 'Created via Retrofit with typed model',
              userId: 1,
            ),
          ),
        ),
        _RequestAction(
          'GET User',
          'Fetch user with typed response',
          Icons.person,
          () => retrofitClient.getUser(1),
        ),
        _RequestAction(
          'GET Comments',
          'Fetch comments with query param',
          Icons.comment,
          () => retrofitClient.getComments(1),
        ),
        _RequestAction(
          'UPDATE Post',
          'Update post with PUT',
          Icons.edit,
          () => retrofitClient.updatePost(
            1,
            Post(title: 'Updated via Retrofit', body: 'Updated content'),
          ),
        ),
      ],
    );
  }
}

// ============ SHARED WIDGETS ============

class _RequestAction {
  final String label;
  final String description;
  final IconData icon;
  final Future<dynamic> Function() action;

  const _RequestAction(this.label, this.description, this.icon, this.action);
}

class _RequestsPanel extends StatelessWidget {
  final String title;
  final String subtitle;
  final Color color;
  final IconData icon;
  final List<_RequestAction> requests;

  const _RequestsPanel({
    required this.title,
    required this.subtitle,
    required this.color,
    required this.icon,
    required this.requests,
  });

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        // Header card
        Card(
          color: color.withAlpha((0.1 * 255).round()),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                Icon(icon, size: 40, color: color),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        title,
                        style: Theme.of(context).textTheme.titleLarge?.copyWith(
                          fontWeight: FontWeight.bold,
                          color: color,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        subtitle,
                        style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                          color: Colors.grey[600],
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
        const SizedBox(height: 16),

        // Request buttons
        ...requests.map(
          (r) => Padding(
            padding: const EdgeInsets.only(bottom: 12),
            child: _RequestButton(
              label: r.label,
              description: r.description,
              icon: r.icon,
              color: color,
              onPressed: () => _executeRequest(context, r),
            ),
          ),
        ),

        // Log count indicator
        const SizedBox(height: 16),
        StreamBuilder<List<NetworkLog>>(
          stream: Droido.logsStream,
          builder: (context, snapshot) {
            final count = snapshot.data?.length ?? 0;
            return Center(
              child: Chip(
                avatar: const Icon(Icons.history, size: 18),
                label: Text('$count requests logged'),
              ),
            );
          },
        ),
      ],
    );
  }

  Future<void> _executeRequest(
    BuildContext context,
    _RequestAction request,
  ) async {
    try {
      await request.action();
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('${request.label} - Success!'),
            backgroundColor: Colors.green,
            behavior: SnackBarBehavior.floating,
          ),
        );
      }
    } catch (e) {
      if (context.mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('${request.label} - Error logged'),
            backgroundColor: Colors.orange,
            behavior: SnackBarBehavior.floating,
          ),
        );
      }
    }
  }
}

class _RequestButton extends StatelessWidget {
  final String label;
  final String description;
  final IconData icon;
  final Color color;
  final VoidCallback onPressed;

  const _RequestButton({
    required this.label,
    required this.description,
    required this.icon,
    required this.color,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return Material(
      color: color,
      borderRadius: BorderRadius.circular(12),
      child: InkWell(
        onTap: onPressed,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
          child: Row(
            children: [
              Icon(icon, color: Colors.white),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      label,
                      style: const TextStyle(
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    Text(
                      description,
                      style: TextStyle(
                        color: Colors.white.withAlpha((0.8 * 255).round()),
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
              const Icon(Icons.chevron_right, color: Colors.white),
            ],
          ),
        ),
      ),
    );
  }
}
15
likes
140
points
172
downloads

Publisher

verified publisherbrooky.in

Weekly Downloads

A lightweight debug-only network inspector for Flutter apps. Works with Dio, HTTP package, and Retrofit. Features persistent notification UI and clean modern design. Zero impact on release builds.

Repository (GitHub)
View/report issues

Topics

#debug #network #inspector #dio #logging

Documentation

API reference

License

MIT (license)

Dependencies

dio, flutter, flutter_local_notifications, freezed_annotation, http, intl, json_annotation, rxdart, share_plus

More

Packages that depend on droido