netify 2.0.0 copy "netify: ^2.0.0" to clipboard
netify: ^2.0.0 copied to clipboard

A lightweight, debug-only network inspector for Flutter apps using Dio HTTP client. Features a modern UI, request grouping, favorites, dark mode, and share as image.

example/lib/main.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:netify/netify.dart';

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

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

  // Enable edge-to-edge
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
  SystemChrome.setSystemUIOverlayStyle(
    const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent,
      systemNavigationBarColor: Colors.transparent,
      systemNavigationBarDividerColor: Colors.transparent,
    ),
  );

  final dio = Dio(
    BaseOptions(
      headers: {
        'Content-Type': 'application/json; charset=UTF-8',
        'Accept': 'application/json',
      },
    ),
  );

  // Initialize Netify with configuration
  // Entry modes: bubble (default), none
  await Netify.init(
    dio: dio,
    config: const NetifyConfig(
      maxLogs: 500,
      entryMode: NetifyEntryMode.bubble,
    ),
  );

  runApp(MyApp(dio: dio));
}

class MyApp extends StatelessWidget {
  final Dio dio;

  const MyApp({super.key, required this.dio});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Netify Example',
      navigatorKey: Netify.navigatorKey,
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: HomePage(dio: dio),
    );
  }
}

class HomePage extends StatefulWidget {
  final Dio dio;

  const HomePage({super.key, required this.dio});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool _isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Netify Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.bug_report),
            onPressed: () =>
                Netify.show(context), // Use Netify.show() for manual access
            tooltip: 'Open Netify',
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const Text(
              'Test API Requests',
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              'Tap the floating bubble to open Netify',
              style: TextStyle(
                fontSize: 14,
                color: Colors.grey[600],
              ),
            ),
            const SizedBox(height: 16),

            // Success requests
            _buildSectionTitle('Success Requests'),
            _buildRequestButton(
              'GET Post',
              Colors.green,
              () => _makeRequest(
                  'GET', 'https://jsonplaceholder.typicode.com/posts/1'),
            ),
            const SizedBox(height: 8),
            _buildRequestButton(
              'GET User',
              Colors.green,
              () => _makeRequest(
                  'GET', 'https://jsonplaceholder.typicode.com/users/1'),
            ),
            const SizedBox(height: 16),

            // Error requests
            _buildSectionTitle('Error Requests'),
            _buildRequestButton(
              'GET 404 Error',
              Colors.red,
              () => _makeRequest(
                  'GET', 'https://jsonplaceholder.typicode.com/posts/99999999'),
            ),
            const SizedBox(height: 8),
            _buildRequestButton(
              'Network Error',
              Colors.red,
              () => _makeRequest(
                  'GET', 'https://invalid-domain-that-does-not-exist.com/api'),
            ),
            const SizedBox(height: 16),

            // CRUD operations
            _buildSectionTitle('CRUD Operations'),
            _buildRequestButton(
              'POST Create',
              Colors.blue,
              () => _makeRequest(
                'POST',
                'https://jsonplaceholder.typicode.com/posts',
                data: {
                  'title': 'New Post Title',
                  'body': 'This is the post body content with some details.',
                  'userId': 1,
                },
              ),
            ),
            const SizedBox(height: 8),
            _buildRequestButton(
              'PUT Update',
              Colors.orange,
              () => _makeRequest(
                'PUT',
                'https://jsonplaceholder.typicode.com/posts/1',
                data: {
                  'id': 1,
                  'title': 'Updated Title',
                  'body': 'Updated body content',
                  'userId': 1,
                },
              ),
            ),
            const SizedBox(height: 8),
            _buildRequestButton(
              'DELETE Remove',
              Colors.red.shade700,
              () => _makeRequest(
                  'DELETE', 'https://jsonplaceholder.typicode.com/posts/1'),
            ),
            const SizedBox(height: 16),

            // Batch requests (for testing grouping)
            _buildSectionTitle('Batch Requests (Test Grouping)'),
            _buildRequestButton(
              'Multiple Domains',
              Colors.purple,
              _makeMultipleDomainRequests,
            ),
            const SizedBox(height: 8),
            _buildRequestButton(
              'Same Domain Batch',
              Colors.indigo,
              _makeSameDomainRequests,
            ),
            const SizedBox(height: 24),

            if (_isLoading) const Center(child: CircularProgressIndicator()),

            const SizedBox(height: 16),
            StreamBuilder<List<NetworkLog>>(
              stream: Netify.logsStream,
              initialData: Netify.logs,
              builder: (context, snapshot) {
                final count = snapshot.data?.length ?? 0;
                final errorCount =
                    snapshot.data?.where((l) => l.isError).length ?? 0;
                return Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      children: [
                        Row(
                          children: [
                            const Icon(Icons.wifi, color: Colors.blue),
                            const SizedBox(width: 12),
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Text(
                                    '$count requests captured',
                                    style: const TextStyle(
                                        fontSize: 16,
                                        fontWeight: FontWeight.w500),
                                  ),
                                  if (errorCount > 0)
                                    Text(
                                      '$errorCount errors',
                                      style: TextStyle(
                                          fontSize: 12, color: Colors.red[600]),
                                    ),
                                ],
                              ),
                            ),
                            TextButton(
                              onPressed: () => Netify.show(context),
                              child: const Text('View'),
                            ),
                          ],
                        ),
                        if (count > 0) ...[
                          const Divider(),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                            children: [
                              TextButton.icon(
                                onPressed: Netify.clearLogs,
                                icon:
                                    const Icon(Icons.delete_outline, size: 18),
                                label: const Text('Clear'),
                              ),
                            ],
                          ),
                        ],
                      ],
                    ),
                  ),
                );
              },
            ),
            const SizedBox(height: 80), // Space for floating bubble
          ],
        ),
      ),
    );
  }

  Widget _buildSectionTitle(String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 8),
      child: Text(
        title,
        style: TextStyle(
          fontSize: 14,
          fontWeight: FontWeight.w600,
          color: Colors.grey[700],
        ),
      ),
    );
  }

  Widget _buildRequestButton(
      String label, Color color, VoidCallback onPressed) {
    return ElevatedButton(
      onPressed: _isLoading ? null : onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(vertical: 16),
      ),
      child: Text(label),
    );
  }

  Future<void> _makeRequest(
    String method,
    String url, {
    Map<String, dynamic>? data,
  }) async {
    setState(() => _isLoading = true);

    try {
      switch (method) {
        case 'GET':
          await widget.dio.get(url);
          break;
        case 'POST':
          await widget.dio.post(url, data: data);
          break;
        case 'PUT':
          await widget.dio.put(url, data: data);
          break;
        case 'DELETE':
          await widget.dio.delete(url);
          break;
      }
    } catch (e) {
      // Error is captured by Netify
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _makeMultipleDomainRequests() async {
    setState(() => _isLoading = true);

    try {
      await Future.wait([
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/1'),
        widget.dio.get('https://jsonplaceholder.typicode.com/users/1'),
        widget.dio.get('https://httpbin.org/get'),
        widget.dio.get('https://dummyjson.com/products/1'),
      ]);
    } catch (e) {
      // Errors captured by Netify
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _makeSameDomainRequests() async {
    setState(() => _isLoading = true);

    try {
      await Future.wait([
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/1'),
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/2'),
        widget.dio.get('https://jsonplaceholder.typicode.com/posts/3'),
        widget.dio.get('https://jsonplaceholder.typicode.com/users/1'),
        widget.dio.get('https://jsonplaceholder.typicode.com/comments/1'),
        widget.dio.get('https://jsonplaceholder.typicode.com/albums/1'),
      ]);
    } catch (e) {
      // Errors captured by Netify
    } finally {
      setState(() => _isLoading = false);
    }
  }
}
1
likes
160
points
133
downloads
screenshot

Publisher

unverified uploader

Weekly Downloads

A lightweight, debug-only network inspector for Flutter apps using Dio HTTP client. Features a modern UI, request grouping, favorites, dark mode, and share as image.

Repository (GitHub)
View/report issues

Topics

#network #debugging #dio #http #inspector

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

dio, flutter, screenshot

More

Packages that depend on netify

Packages that implement netify