brevwick

pub package license: MIT

Flutter SDK for Brevwick — send rich QA feedback from inside your app by wrapping it in a single widget. Bug reports arrive with a screenshot, the recent console / network / route history, and device context already attached, so you spend less time asking "what were you doing when it broke?".

Runs on all six Flutter platforms — Android, iOS, macOS, Linux, Windows, and web.

Install

flutter pub add brevwick

Quick start

Wrap your app once. Brevwick.runGuarded captures uncaught errors, BrevwickScreenshotScope enables screenshot capture, and BrevwickOverlay adds the feedback affordance.

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

void main() {
  Brevwick.runGuarded<void>(() async {
    WidgetsFlutterBinding.ensureInitialized();
    await Brevwick.install(
      BrevwickConfig(
        projectKey: 'pk_live_...',
        environment: 'prod',
        // Optional: forward the SDK's internal diagnostics into your logger.
        onLog: (level, message, {error, stackTrace}) =>
            debugPrint('[brevwick] ${level.name}: $message'),
      ),
    );
    runApp(const MyApp());
  });
}

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

  @override
  Widget build(BuildContext context) {
    // BrevwickScreenshotScope wraps the whole app so any route can be
    // captured.
    return BrevwickScreenshotScope(
      child: MaterialApp(
        navigatorObservers: [Brevwick.instance.routeObserver()],
        // BrevwickOverlay must sit BELOW a Navigator: its launcher shows a
        // Tooltip and opens a modal bottom sheet, both of which need a
        // Navigator + Overlay ancestor. Wrap your screen (`home`) — or, for
        // an overlay that persists across routes, a go_router `ShellRoute`
        // body. Do NOT place it in `MaterialApp.builder`, which sits ABOVE
        // the Navigator (the launcher would throw `No Overlay widget found`).
        home: BrevwickOverlay(child: const HomePage()),
      ),
    );
  }
}

Placement. BrevwickOverlay needs a Navigator + Overlay ancestor. home: / a routed screen / a ShellRoute body all qualify; MaterialApp.builder does not. BrevwickScreenshotScope has no such requirement — keep it at the top (around MaterialApp or in its builder) so every route is capturable.

Launcher presentation

⚠️ Default change. The zero-config launcher is now a vertical tab flush against the right screen edge, vertically centered, with a rotated "Feedback" label — not the old bottom-right corner bubble. The bubble remains fully supported: any call site that passes an explicit corner position (e.g. Alignment.bottomRight) keeps compiling and keeps the bubble at that corner. To keep the bubble with no other configuration, pass variant: BrevwickLauncherVariant.bubble.

BrevwickOverlay(child: app)                          // NEW default: right-edge tab
BrevwickOverlay(side: BrevwickLauncherSide.left, child: app)   // left-edge tab
BrevwickOverlay(compact: true, child: app)           // icon-only edge tab
BrevwickOverlay(offset: 80, child: app)              // tab nudged 80 px down
BrevwickOverlay(
  variant: BrevwickLauncherVariant.bubble,           // legacy corner bubble
  child: app,
)
BrevwickOverlay(
  position: Alignment.bottomLeft,                    // legacy call site: still a bubble
  child: app,
)

How variant / position / side resolve:

variant position / side Result
null both null tab, right edge (new default)
null position non-null bubble at exactly that Alignment (legacy behaviour, including non-corner alignments)
null side non-null tab on that edge
.tab any tab; edge = side if set, else the position alignment's horizontal side (default right). Tabs are always vertically centered (± offset)
.bubble any bubble at position ?? Alignment.bottomRight; side and offset are ignored

An explicit variant always wins. label (default 'Feedback') is the tab's rotated text and its tooltip/semantics label; compact drops the visible text (the Flutter bubble is already icon-only, so compact is a no-op there).

To capture failing HTTP calls in the network ring, add the interceptor to your Dio instance:

final dio = Dio()..interceptors.add(Brevwick.instance.dioInterceptor());

What gets attached

Every report carries, automatically:

  • Screenshot of the screen the user was on when they tapped the feedback button (captured before the composer opens, so the sheet itself isn't in the frame).
  • Breadcrumb rings — the recent console logs, network requests, and route transitions, each toggleable via RingFlags on BrevwickConfig.
  • Device context — platform, OS, locale, viewport, and SDK version.
  • User context — anything you supply via BrevwickConfig.user / userContext.

Users can add a title, expected/actual notes, and up to a handful of attachments (10 MB each), and optionally let AI format the report. The issue title is derived from the first line of the description (the API requires one); AI formatting, when enabled, refines it server-side.

App-id allowlist

If your project restricts submissions to known apps (an AllowedAppIDs list), the SDK sends the host app's platform package id — the Android applicationId / iOS bundle id, read from package_info_plus — as the X-Brevwick-App-Id header on every ingest call. Register that id (e.g. com.acme.app, or com.acme.app.dev for a flavored build) in the project's allowlist, otherwise the API responds 403 ORIGIN_NOT_ALLOWED (app_id_mismatch). Projects without an app-id allowlist need no setup — the header is informational and the value is omitted when unavailable (e.g. some web builds).

Forwarding SDK diagnostics

Set BrevwickConfig.onLog to route the SDK's own diagnostics — project-config fetch failures, screenshot-capture warnings, uncaught zone errors, and submit failures — into your app's logger. Messages are redacted at the boundary, and a throwing sink can never break the SDK.

await Brevwick.install(
  BrevwickConfig(
    projectKey: 'pk_live_...',
    onLog: (level, message, {error, stackTrace}) {
      switch (level) {
        case BrevwickLogLevel.error:
          myLogger.error(message, error, stackTrace);
        case BrevwickLogLevel.warn:
          myLogger.warn(message);
        case BrevwickLogLevel.info:
        case BrevwickLogLevel.debug:
          myLogger.info(message);
      }
    },
  ),
);

Privacy & redaction

Every payload runs through a redaction pass before it leaves the device — emails, tokens, and other sensitive values are scrubbed client-side. Set fingerprintOptOut: true on BrevwickConfig to suppress device-fingerprint signals.

Platform view caveat

The screenshot layer cannot capture AndroidView / UiKitView (maps, webviews, and other embedded platform views), which appear as gaps in the captured frame. Document this for any screen that embeds them.

Contract

The canonical SDK contract lives in brevwick-ops/docs/brevwick-sdd.md § 12. The wire format mirrors the brevwick-sdk-js SDK byte-for-byte.

License

MIT — see LICENSE.

Libraries

brevwick
Brevwick Flutter SDK.