Skip to main content

endpoint_lens 0.1.0 copy "endpoint_lens: ^0.1.0" to clipboard
endpoint_lens: ^0.1.0 copied to clipboard

A Flutter developer tool that reveals the API endpoints connected to UI elements using long press inspection.

example/lib/main.dart

import 'package:endpoint_lens/endpoint_lens.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

import 'example_api.dart';

/// Example client, created in [main] after [ApiLens.init].
late ExampleApi exampleApi;

void main() {
  ApiLens.init(
    enabled: const bool.fromEnvironment('API_LENS', defaultValue: true),
  );
  exampleApi = ExampleApi();
  runApp(const ExampleApp());
}

/// Host application for the three sample screens.
class ExampleApp extends StatelessWidget {
  /// Creates the example app.
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    const seed = Color(0xFF0B6E4F);
    return MaterialApp(
      title: 'API Lens example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: seed),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: seed,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      home: const ExampleShell(),
    );
  }
}

class ExampleShell extends StatefulWidget {
  const ExampleShell({super.key});

  @override
  State<ExampleShell> createState() => _ExampleShellState();
}

class _ExampleShellState extends State<ExampleShell> {
  int _index = 0;

  static const _pages = <Widget>[
    LoginScreen(),
    ProductsScreen(),
    ProfileScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Scaffold(
          body: IndexedStack(index: _index, children: _pages),
          bottomNavigationBar: NavigationBar(
            selectedIndex: _index,
            onDestinationSelected: (index) => setState(() => _index = index),
            destinations: const <Widget>[
              NavigationDestination(icon: Icon(Icons.login), label: 'Login'),
              NavigationDestination(
                icon: Icon(Icons.shopping_bag_outlined),
                label: 'Products',
              ),
              NavigationDestination(
                icon: Icon(Icons.person_outline),
                label: 'Profile',
              ),
            ],
          ),
        ),
        const ApiLensInspectorButton(bottom: 120),
      ],
    );
  }
}

/// Sign-in form backed by `POST /login`.
class LoginScreen extends StatefulWidget {
  /// Creates the login screen.
  const LoginScreen({super.key});

  @override
  State<LoginScreen> createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {
  final TextEditingController _email = TextEditingController(
    text: 'demo.user@example.com',
  );
  final TextEditingController _password = TextEditingController();
  String? _message;
  bool _busy = false;

  @override
  void dispose() {
    _email.dispose();
    _password.dispose();
    super.dispose();
  }

  Future<void> _submit() async {
    setState(() {
      _busy = true;
      _message = null;
    });
    try {
      await exampleApi.login(
        email: _email.text.trim(),
        password: _password.text,
      );
      if (!mounted) return;
      setState(
        () => _message = 'Signed in. Long press this card to see POST /login.',
      );
    } on DioException catch (error) {
      if (!mounted) return;
      final code = error.response?.statusCode;
      setState(() => _message = 'Failed${code == null ? '' : ' ($code)'}.');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: <Widget>[
          const Text(
            'Any password except "wrong" succeeds. The password is redacted in API Lens.',
          ),
          const SizedBox(height: 16),
          ApiLensWidget(
            feature: 'login',
            label: 'Login',
            child: Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  children: <Widget>[
                    TextField(
                      controller: _email,
                      decoration: const InputDecoration(labelText: 'Email'),
                      keyboardType: TextInputType.emailAddress,
                    ),
                    const SizedBox(height: 12),
                    TextField(
                      controller: _password,
                      decoration: const InputDecoration(labelText: 'Password'),
                      obscureText: true,
                      onSubmitted: (_) => _submit(),
                    ),
                    const SizedBox(height: 16),
                    FilledButton(
                      onPressed: _busy ? null : _submit,
                      child: Text(_busy ? 'Signing in…' : 'Sign in'),
                    ),
                    if (_message != null) ...<Widget>[
                      const SizedBox(height: 12),
                      Text(_message!),
                    ],
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Catalog backed by `GET /products`.
class ProductsScreen extends StatefulWidget {
  /// Creates the products screen.
  const ProductsScreen({super.key});

  @override
  State<ProductsScreen> createState() => _ProductsScreenState();
}

class _ProductsScreenState extends State<ProductsScreen> {
  late Future<List<DemoProduct>> _products = exampleApi.loadProducts();

  void _reload() {
    setState(() => _products = exampleApi.loadProducts());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
        actions: <Widget>[
          IconButton(
            tooltip: 'Reload products',
            onPressed: _reload,
            icon: const Icon(Icons.refresh),
          ),
        ],
      ),
      body: FutureBuilder<List<DemoProduct>>(
        future: _products,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return const Center(child: Text('Could not load products.'));
          }
          final products = snapshot.data ?? const <DemoProduct>[];
          return ApiLensWidget(
            feature: 'products',
            label: 'Products',
            child: ListView.separated(
              padding: const EdgeInsets.all(16),
              itemCount: products.length,
              separatorBuilder: (context, index) => const SizedBox(height: 8),
              itemBuilder: (context, index) {
                final product = products[index];
                return Card(
                  child: ListTile(
                    title: Text(product.name),
                    subtitle: const Text('GET /products'),
                    trailing: Text('\$${product.price}'),
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

/// Profile backed by the user, posts, and avatar endpoints.
class ProfileScreen extends StatefulWidget {
  /// Creates the profile screen.
  const ProfileScreen({super.key});

  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  late Future<DemoProfile> _profile = exampleApi.loadProfile();

  void _reload() {
    setState(() => _profile = exampleApi.loadProfile());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Profile'),
        actions: <Widget>[
          IconButton(
            tooltip: 'Reload profile',
            onPressed: _reload,
            icon: const Icon(Icons.refresh),
          ),
        ],
      ),
      body: FutureBuilder<DemoProfile>(
        future: _profile,
        builder: (context, snapshot) {
          if (snapshot.connectionState != ConnectionState.done) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return const Center(child: Text('Could not load the profile.'));
          }
          final profile = snapshot.data!;
          return ListView(
            padding: const EdgeInsets.all(16),
            children: <Widget>[
              ApiLensWidget(
                feature: 'profile',
                label: 'User Profile',
                child: Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Text(
                          profile.name,
                          style: Theme.of(context).textTheme.headlineSmall,
                        ),
                        const SizedBox(height: 4),
                        Text(profile.email),
                        const SizedBox(height: 16),
                        Text(
                          'Posts',
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const SizedBox(height: 8),
                        for (final post in profile.posts)
                          Padding(
                            padding: const EdgeInsets.only(bottom: 8),
                            child: Text(post.title),
                          ),
                      ],
                    ),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}
0
likes
0
points
575
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter developer tool that reveals the API endpoints connected to UI elements using long press inspection.

Repository (GitHub)
View/report issues

Topics

#flutter #debugging #api #dio #developer-tools

License

unknown (license)

Dependencies

dio, flutter

More

Packages that depend on endpoint_lens