brevwick 0.4.1 copy "brevwick: ^0.4.1" to clipboard
brevwick: ^0.4.1 copied to clipboard

Brevwick SDK for Flutter — send QA feedback from your app in one widget wrap.

example/lib/main.dart

/// Reference integration of the Brevwick Flutter SDK.
///
/// Exercises every public-surface entry point: `Brevwick.install`,
/// `runGuarded`, `routeObserver`, `dioInterceptor`, the
/// `BrevwickScreenshotScope` + `BrevwickOverlay` widgets, programmatic
/// `captureScreenshot` / `submit`, and ring snapshots.
///
/// Run with the project key + endpoint your tenant provided:
///
/// ```sh
/// flutter run --dart-define=BREVWICK_KEY=pk_test_... \
///             --dart-define=BREVWICK_ENDPOINT=http://localhost:8080
/// ```
library;

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

const String _projectKey = String.fromEnvironment(
  'BREVWICK_KEY',
  defaultValue: 'pk_test_demoexamplekey00000',
);

const String _endpointRaw = String.fromEnvironment(
  'BREVWICK_ENDPOINT',
  defaultValue: 'https://api.brevwick.com',
);

void main() {
  Brevwick.runGuarded<void>(() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Brevwick.install(
      BrevwickConfig(
        projectKey: _projectKey,
        endpoint: Uri.parse(_endpointRaw),
        environment: 'dev',
        release: '0.1.0-example',
        user: const BrevwickUser(id: 'demo-user', email: 'demo@brevwick.com'),
        userContext: () => const <String, Object?>{
          'route': '/example/home',
          'feature_flag': 'beta',
        },
      ),
    );
    runApp(const ExampleApp());
  });
}

/// Root of the example app. Wires the SDK's overlay + screenshot scope +
/// route observer, and exposes a Dio configured with the SDK's
/// `BrevwickDioInterceptor` to the home page so failed HTTP calls land in
/// the network ring.
class ExampleApp extends StatefulWidget {
  /// Creates an [ExampleApp].
  const ExampleApp({super.key});

  @override
  State<ExampleApp> createState() => _ExampleAppState();
}

class _ExampleAppState extends State<ExampleApp> {
  // Long-lived Dio: shared across the home page so the interceptor is
  // attached exactly once. Disposed in [dispose] when the example app
  // is torn down.
  late final Dio _dio = Dio()
    ..interceptors.add(Brevwick.instance.dioInterceptor());

  @override
  void dispose() {
    _dio.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BrevwickScreenshotScope(
      child: MaterialApp(
        title: 'Brevwick Example',
        navigatorObservers: <NavigatorObserver>[
          Brevwick.instance.routeObserver(),
        ],
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
          useMaterial3: true,
        ),
        // Wrap the overlay at the [MaterialApp.builder] level so the FAB
        // persists across every named route. Per-route wrapping
        // (`/`-only) unmounts the overlay on `Navigator.pushNamed` and
        // ships an inconsistent UX — the canonical multi-route shape is
        // app-shell-level wrapping.
        builder: (BuildContext _, Widget? child) =>
            BrevwickOverlay(child: child ?? const SizedBox.shrink()),
        initialRoute: '/',
        routes: <String, WidgetBuilder>{
          '/': (_) => HomePage(dio: _dio),
          '/details': (_) => const DetailsPage(),
        },
      ),
    );
  }
}

/// Home — four buttons, each demonstrating one ring or one programmatic
/// SDK entry point.
class HomePage extends StatelessWidget {
  /// Creates a [HomePage] bound to [dio] for the failing-HTTP demo.
  const HomePage({required this.dio, super.key});

  /// Dio with the Brevwick interceptor installed; used by the
  /// "Fail HTTP call" button.
  final Dio dio;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Brevwick Example')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: ListView(
          children: <Widget>[
            _DemoButton(
              key: const ValueKey<String>('demo-throw-error'),
              icon: Icons.error_outline,
              label: 'Throw Flutter error',
              onPressed: () =>
                  throw StateError('Demo: simulated Flutter error'),
            ),
            _DemoButton(
              key: const ValueKey<String>('demo-fail-http'),
              icon: Icons.cloud_off_outlined,
              label: 'Fail HTTP call',
              onPressed: () => _failHttpCall(context),
            ),
            _DemoButton(
              key: const ValueKey<String>('demo-navigate-details'),
              icon: Icons.arrow_forward_ios,
              label: 'Navigate to details',
              onPressed: () => Navigator.of(context).pushNamed('/details'),
            ),
            _DemoButton(
              key: const ValueKey<String>('demo-capture-submit'),
              icon: Icons.send_outlined,
              label: 'Capture + submit',
              onPressed: () => _captureAndSubmit(context),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _failHttpCall(BuildContext context) async {
    final messenger = ScaffoldMessenger.maybeOf(context);
    try {
      await dio.get<Object?>(
        'https://httpbin.org/status/500',
        options: Options(validateStatus: (_) => true),
      );
    } on Object catch (e) {
      messenger?.showSnackBar(SnackBar(content: Text('HTTP error: $e')));
      return;
    }
    messenger?.showSnackBar(
      const SnackBar(content: Text('Captured 500 into network ring')),
    );
  }

  Future<void> _captureAndSubmit(BuildContext context) async {
    // Capture the messenger BEFORE the awaits so we don't need a
    // [context.mounted] guard afterwards (the messenger reference
    // survives even if the surrounding widget is unmounted).
    final messenger = ScaffoldMessenger.maybeOf(context);
    final bytes = await Brevwick.instance.captureScreenshot(context);
    final attachments = <FeedbackAttachment>[
      if (bytes != null)
        FeedbackAttachment(
          bytes: bytes,
          mime: 'image/png',
          filename: 'screenshot.png',
        ),
    ];
    final result = await Brevwick.instance.submit(
      FeedbackInput(
        title: 'Demo',
        description: 'Programmatic capture + submit',
        attachments: attachments,
      ),
    );
    switch (result) {
      case SubmitOk(:final issueId):
        messenger?.showSnackBar(SnackBar(content: Text('Submitted: $issueId')));
      case SubmitFailed(:final code, :final message):
        messenger?.showSnackBar(
          SnackBar(content: Text('${code.wireName}: $message')),
        );
    }
  }
}

class _DemoButton extends StatelessWidget {
  const _DemoButton({
    required this.icon,
    required this.label,
    required this.onPressed,
    super.key,
  });

  final IconData icon;
  final String label;
  final VoidCallback onPressed;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 6),
      child: FilledButton.icon(
        onPressed: onPressed,
        icon: Icon(icon),
        label: Text(label),
      ),
    );
  }
}

/// Visual ring inspector — reads the current SDK ring snapshots and lays
/// them out as collapsible sections so a manual smoke run can confirm
/// each ring populated.
class DetailsPage extends StatefulWidget {
  /// Creates a [DetailsPage].
  const DetailsPage({super.key});

  @override
  State<DetailsPage> createState() => _DetailsPageState();
}

class _DetailsPageState extends State<DetailsPage> {
  // Ring snapshots are read in [initState] (NOT at field-declaration
  // time): if a copy-paster calls `runApp` before `Brevwick.install`,
  // touching `Brevwick.instance` during widget-construction would crash
  // with a StateError. Reading in [initState] keeps the failure surface
  // localised to the route push and easier to diagnose.
  late List<Map<String, Object?>> _console;
  late List<Map<String, Object?>> _network;
  late List<Map<String, Object?>> _route;

  @override
  void initState() {
    super.initState();
    _console = Brevwick.instance.snapshotConsole();
    _network = Brevwick.instance.snapshotNetwork();
    _route = Brevwick.instance.snapshotRoute();
  }

  void _refresh() {
    setState(() {
      _console = Brevwick.instance.snapshotConsole();
      _network = Brevwick.instance.snapshotNetwork();
      _route = Brevwick.instance.snapshotRoute();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Ring snapshots'),
        actions: <Widget>[
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: 'Refresh',
            onPressed: _refresh,
          ),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(12),
        children: <Widget>[
          _RingSection(
            title: 'Console (${_console.length})',
            entries: _console,
          ),
          _RingSection(
            title: 'Network (${_network.length})',
            entries: _network,
          ),
          _RingSection(title: 'Route (${_route.length})', entries: _route),
        ],
      ),
    );
  }
}

class _RingSection extends StatelessWidget {
  const _RingSection({required this.title, required this.entries});

  final String title;
  final List<Map<String, Object?>> entries;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 6),
      child: ExpansionTile(
        title: Text(title),
        children: <Widget>[
          if (entries.isEmpty)
            const Padding(padding: EdgeInsets.all(12), child: Text('Empty'))
          else
            for (final entry in entries)
              Padding(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 4,
                ),
                child: Align(
                  alignment: Alignment.centerLeft,
                  child: Text(entry.toString()),
                ),
              ),
        ],
      ),
    );
  }
}
0
likes
120
points
0
downloads

Documentation

API reference

Publisher

verified publishertatlacas.com

Weekly Downloads

Brevwick SDK for Flutter — send QA feedback from your app in one widget wrap.

Homepage
Repository (GitHub)
View/report issues

Topics

#feedback #bug-reports #qa #observability

License

MIT (license)

Dependencies

characters, crypto, device_info_plus, dio, file_picker, flutter, package_info_plus, screenshot

More

Packages that depend on brevwick