Skip to main content

snappy_snag 1.1.1 copy "snappy_snag: ^1.1.1" to clipboard
snappy_snag: ^1.1.1 copied to clipboard

An easy-to-use in-app bug reporting and user feedback SDK for Flutter, allowing users to send screenshots, device info, and custom logs seamlessly.

SnappySnag SDK #

CI pub package License: MIT

SnappySnag is a visual bug reporting and AI auto-fix suggestion tool for Flutter applications. With a single shake or a tap of a button, developers and QA teams can capture screenshots, dump widget tree layouts, and get instant, detailed AI-powered code fix suggestions on their dashboard.

🌐 Website | 📖 Official Docs | 🛡️ Security & Trust | 🤖 llms.txt (AI Agents) | 📦 pub.dev


Features #

  • 📸 Prioritized Instant Screenshots: Captured immediately on-press to freeze the screen state, even during fast page transitions.
  • 📍 Multi-Point Pin Annotations: Tap anywhere on the captured screen to drop numbered pins (1–5) and attach itemized feedback/bug details for each specific area.
  • 🎨 Clean & Annotated Screenshot Preservation: Pin coordinates are stored as normalized vectors, keeping the original screenshot crystal clear without destructive image stamping.
  • 🌳 Widget Tree Dumper: Automatically dumps the widget hierarchy (up to a depth of 10 levels) for precise widget mapping.
  • 🤖 AI Auto-Fix suggestions: Generates human-focused technical guides and cursor-compatible agent prompts.
  • 💬 Interactive AI Discussion & Version Sync: Directly ask follow-up questions to Gemini about architecture, alternative fixes, or trade-offs. Seamlessly sync discussion consensus into refined Version 2/3 technical guides and agent prompts with syntax-highlighted code snippets.
  • 🔔 Real-Time Team Notifications (Slack & Discord): Receive instant webhook alerts in your team's Slack or Discord channels whenever new feedback is submitted, featuring category, severity, and authenticated deep-links directly to tickets.
  • 💾 Offline Draft & Resilience: Automatically saves drawing annotations, pins, and comments locally on connection failure or accidental dismissal. Seamlessly resume or discard drafts on the next capture without wasting user efforts.
  • ⚡ Pre-Validation Error Guard: Instantly validates API key configuration and bundle identifiers before users spend time drawing or writing, preventing post-submit authentication surprises.
  • 🔒 Strict Write-Only API Key & Dual Operation Modes: Built on a zero-read, write-only publishable key architecture so internal data can never be fetched from client builds, while supporting dedicated user (public end-user) and dev (internal QA) submission workflows.
  • 🌐 Multi-Language & Localization (i18n): Out-of-the-box support for English (en) and Japanese (ja) with automatic system locale matching and customizable dictionaries via copyWith().
  • 🛡️ Package Name Lock: Prevents unauthorized API requests by locking your API Key to your registered bundle identifier.
  • 🚫 Store Production Safe: Easily disable the overlay button and sensor listeners completely in App Store/Google Play builds using the enabled configuration.

Getting started #

Add snappy_snag to your Flutter project's dependencies:

flutter pub add snappy_snag

Usage #

0. Quick Try (Instant Sandbox / Demo Mode) #

Want to test without registering or getting an API key? You can immediately run in local Sandbox mode:

SnappySnag().initialize(
  apiKey: 'demo', // Instant local sandbox without account or server setup
  showTriggerButton: true,
);

Capture screens, annotate with pins, and watch the widget tree + pin payload output directly in your debug console!

1. Initialize and Wrap MaterialApp #

Configure the SDK in your main.dart. We highly recommend using bool.fromEnvironment to enable SnappySnag only during internal testing (e.g., TestFlight or Google Play Internal Testing) and disabling it completely for App Store/Google Play production builds.

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:snappy_snag/snappy_snag.dart';

void main() {
  // 1. Initialize the SDK
  SnappySnag().initialize(
    apiKey: 'snag_live_your_api_key_here',
    // packageName: 'your.package.name', // Optional: Lock API key usage to your app's bundle ID
    // Optional: Attach reporter identity (email, id, name) to feedback reports
    user: const SnappySnagUser(
      email: 'developer@example.com',
    ),
    // Optional: Enable detailed console debug logs during development (defaults to false)
    // enableLogging: kDebugMode,
    // Safely enable SnappySnag only when ENABLE_SNAPPY_SNAG=true is passed at build time.
    // It will automatically bypass overlay rendering and sensor listeners in production builds.
    enabled: const bool.fromEnvironment('ENABLE_SNAPPY_SNAG', defaultValue: false) || kDebugMode,
  );

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 2. Simply pass SnappySnag's defaultNavigatorKey
      navigatorKey: SnappySnag.defaultNavigatorKey,
      // 3. Wrap your screen with SnappySnagOverlay in builder
      builder: (context, child) => SnappySnagOverlay(
        child: child ?? const SizedBox.shrink(),
      ),
      home: const MyHomePage(),
    );
  }
}

💡 Tip: Dynamic User Info: If users log in after app startup, call SnappySnag().setUser(SnappySnagUser(id: user.id, email: user.email)) anywhere in your authentication flow. Call SnappySnag().clearUser() on logout.

2. Trigger Button Visibility & Control #

By default, the floating capture button is hidden across all modes (showTriggerButton: false) to keep your application's UI completely undisturbed.

You can display or control the trigger button as needed:

  • Enable Floating Button for Rapid Testing: Pass showTriggerButton: true in initialize() to display the floating overlay button immediately:

    SnappySnag().initialize(
      apiKey: '...',
      showTriggerButton: true, // Display floating button immediately
    );
    
  • Hide on Specific Screens (Declarative): Wrap any screen (e.g. camera, video player, payment screen) with SnappySnagHideButton:

    class CameraScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return SnappySnagHideButton(
          child: Scaffold(
            body: YourCameraView(),
          ),
        );
      }
    }
    
  • Programmatic Visibility Control:

    SnappySnag().showTriggerButton();
    SnappySnag().hideTriggerButton();
    SnappySnag().setTriggerButtonVisibility(true);
    

3. Theme Customization (SnappySnagTheme) #

Seamlessly adapt SnappySnag's feedback overlay to your application's brand identity:

SnappySnag().initialize(
  apiKey: '...',
  theme: const SnappySnagTheme(
    primaryColor: Color(0xFF6366F1), // Brand accent color for buttons and highlights
    pinColor: Color(0xFFF95738),     // Pin marker badge color
    borderRadius: 16.0,              // Corner radius for dialog cards and toolbars
    themeMode: ThemeMode.system,     // ThemeMode.system, ThemeMode.dark, or ThemeMode.light
  ),
);
  • Adaptive Contrast: Buttons and toolbars automatically compute optimal text/icon foreground colors for maximum legibility.
  • Smart Modern Toasts: Status notifications (success, error, warning) match the configured theme and automatically hover above the floating trigger button and device safe area.

4. Tactile Haptic Feedback (enableHaptics) #

Provides crisp, native tactile haptic feedback for user interactions (e.g. dropping pins, switching tools, submitting feedback):

SnappySnag().initialize(
  apiKey: '...',
  enableHaptics: true, // Enabled by default; set to false to disable vibrations
);

5. Multi-Language & Custom Localization (SnappySnagLocalization) #

SnappySnag provides native internationalization out of the box with complete dictionaries for English (en) and Japanese (ja).

  • Automatic Locale Detection: By default, SnappySnag automatically follows your app's current locale.
  • Explicit Locale Override:
    SnappySnag().initialize(
      apiKey: '...',
      locale: const Locale('ja'), // Force Japanese UI
    );
    
  • Custom Wording & Branding (copyWith): Fine-tune any button labels, hints, or status messages to match your product's voice without redefining the entire dictionary:
    SnappySnag().initialize(
      apiKey: '...',
      localization: SnappySnagLocalization.ja().copyWith(
        send: 'フィードバック送信!',
        pinCommentTitle: '気になるポイント',
        statusSuccess: '送信完了!ご協力ありがとうございます。',
      ),
    );
    
  • Dynamic Language Switching: Update UI language dynamically at runtime (e.g. when user changes language inside app settings):
    SnappySnag().setLocale(const Locale('en'));
    // Or apply a new custom dictionary:
    SnappySnag().setLocalization(customLocalization);
    

6. Widget Tree Hierarchy & Privacy Opt-Out (enableWidgetTree) #

By default (enableWidgetTree: true), SnappySnag extracts the Flutter UI hierarchy (up to 10 levels deep) to give Gemini AI complete context on nested layouts and styling:

  • Automatic On-Device PII Redaction:
    • Text fields, passwords, and user input widgets are automatically redacted before sending.
    • Long text labels (> 15 characters) are truncated.
  • Complete Hierarchy Opt-Out for Strict Compliance: If your application operates under strict security or regulatory compliance (e.g. healthcare, banking) where sending UI component structures is restricted, you can completely opt out:
    SnappySnag().initialize(
      apiKey: '...',
      enableWidgetTree: false, // Disables widget tree traversal completely
    );
    

    💡 When enableWidgetTree: false, SnappySnag skips tree traversal entirely and relies strictly on screenshot visual cues and user annotations for AI analysis. The dashboard will automatically reflect this as Visual Screenshot Analysis (UI Tree Excluded).

7. Logging & Console Output (enableLogging) #

By default, SnappySnag suppresses all debug output (enableLogging: false) to keep your host app's debug and release console output completely clean.

To enable detailed console logs for debugging during development:

SnappySnag().initialize(
  apiKey: '...',
  enableLogging: kDebugMode, // Only enable detailed debug logs in debug mode
);

⚠️ Security & Privacy Note: Disable logging (enableLogging: false) before releasing your application to production. Debug logs may contain sensitive operational details, including screen class names, UI widget attributes, and reporter user metadata.

Note: Critical configuration errors (such as missing apiKey, security guard dev-mode fallbacks, and macOS sandbox permission guides) will always be output regardless of enableLogging.

8. Multi-Point Pin Drop & Itemized Feedback #

When users capture a screenshot, they can annotate specific UI elements with numbered pins (1–5) and write separate notes for each pin:

  • Drop Pins: Tap anywhere on the captured screenshot to place a pin marker.
  • Itemized Notes: Add targeted feedback or repro notes per pin, helping developers address multiple UI issues in a single report without confusing clutter.
  • Non-Destructive Vectors: Pin coordinates (x, y percentages) are stored separately from the image. The original screenshot remains intact and clean on your dashboard.

9. Launch Feedback Mode Manually (e.g. from Settings or In-App Menu) #

When the floating button is hidden (via showTriggerButton: false or SnappySnagHideButton), or if you prefer triggering feedback through your own custom UI (such as a "Report Bug" button in a Settings or Help drawer), call SnappySnag.startFeedbackMode:

ListTile(
  leading: const Icon(Icons.feedback_outlined),
  title: const Text('Report a Bug / Feedback'),
  onTap: () {
    // Starts Feedback Mode with a guidance banner and temporary capture trigger
    SnappySnag.startFeedbackMode(context: context);
  },
)

💡 When invoked, it displays a guided prompt modal and temporarily reveals the capture button, allowing the user to navigate anywhere in the app to capture and highlight the issue.

10. Build for TestFlight / Internal Testing #

To compile your app with SnappySnag enabled, build with the --dart-define flag:

flutter build ipa --dart-define=ENABLE_SNAPPY_SNAG=true
flutter build appbundle --dart-define=ENABLE_SNAPPY_SNAG=true

11. Build for App Store / Google Play Store (Production) #

To compile your app for production release, build normally. SnappySnag will automatically be disabled, will not render the overlay button, and will not register any shake listeners:

flutter build ipa

Write-Only API Key Architecture & Multi-Layer Safety Guards #

SnappySnag is engineered so you can safely embed the SDK in both internal QA builds and public production releases without risking data leaks or unauthorized access.

1. Strict Write-Only (Publishable) API Key Model #

The client API key (snag_live_...) operates strictly as a Write-Only publishable key:

  • 🔒 Zero Read Surface: The SDK API key has no permission to read, list, or query existing tickets, comments, or screenshots from the backend. Even if an application binary is reverse-engineered and the API key is extracted, zero internal project data or user reports can ever be retrieved.
  • 🛡️ Package Name Lock: Lock your API key to your registered bundle identifier (e.g., com.example.app) in the Web Dashboard so requests from unauthorized packages are rejected (403 Forbidden).

2. Dual Operation Modes (user vs dev) & Automatic Release Guard (kReleaseMode) #

SnappySnag supports two operation modes via the mode parameter:

  • 👤 SnappySnagMode.user (Default): Designed for public end-users. Displays a privacy confirmation modal before sending and instructs Gemini AI to generate a polite User Reply draft alongside technical fix guides.
  • 💻 SnappySnagMode.dev: Designed for internal developers and QA testers. Skips the end-user confirmation modal for rapid reporting and instructs Gemini AI to generate a Team Share summary optimized for engineering triage.

When you build your application in Release Mode (flutter build ipa / flutter build appbundle), SnappySnag automatically checks Flutter's kReleaseMode. Even if mode: SnappySnagMode.dev was inadvertently left configured in your source code, SnappySnag automatically falls back to SnappySnagMode.user.

💡 Internal Dogfooding in Release Builds: If you intentionally wish to use SnappySnagMode.dev in a release-compiled build (e.g. TestFlight distribution to internal QA), explicitly set forceDevInRelease: true:

SnappySnag().initialize(
  apiKey: 'YOUR_KEY',
  packageName: 'com.example.app',
  mode: SnappySnagMode.dev,
  forceDevInRelease: const bool.fromEnvironment('FORCE_DEV_MODE', defaultValue: false),
);

3. Client & Server Rate Limiting (Spam & Flood Protection) #

Applies to both user and dev modes out of the box:

  • Client-Side: The send button enforces a mandatory 3-second cooldown between successive submissions to prevent accidental or malicious double-taps.
  • Server-Side: The backend API limits continuous messages to a maximum of 5 messages per minute per user/device. Exceeding requests automatically receive 429 Too Many Requests with an in-app notice.

4. Zero-Friction Pre-Validation & Offline Draft Protection #

  • Instant Pre-Validation: When the capture button is triggered, SnappySnag performs a lightweight authorization check in the background. If an invalid API key or package name mismatch (401/403) occurs, it immediately halts and alerts you without letting the user waste time annotating or typing a memo.
  • On-Device Offline Draft (Zero-Loss Resilience):
    • If a feedback submission fails due to an unstable internet connection or if the user cancels with unsaved edits, SnappySnag offers to save the current progress as a draft.
    • Privacy & Storage Safe: Drafts are stored strictly on the device's local sandbox (SharedPreferences). No draft data is sent to external servers until explicitly submitted. Only 1 active draft is retained, ensuring zero unnecessary storage overhead.
    • On the next capture attempt, users are prompted to either Resume Draft or Discard & Start New Capture.

5. Local Storage (SharedPreferences) Transparency #

SnappySnag values user privacy and transparency. The SDK uses on-device local storage (SharedPreferences) exclusively for the following two purposes:

  1. snappy_snag_device_id: A randomly generated anonymous UUID used exclusively for backend spam and rate-limit enforcement (429 Too Many Requests). Contains no personal identity or hardware fingerprints.
  2. snappy_snag_draft_v1: Temporary offline draft data (at most 1 active draft) saved strictly when the user chooses to retain unsaved edits after a failed submission or cancellation. Cleared immediately upon submission or explicit discard.

6. On-Device Privacy & PII (Personally Identifiable Information) Redaction #

When capturing feedback, user privacy is safeguarded at the client device before any payload is dispatched:

  • 🙈 Automatic Sensitive Input Detection (findSensitiveFieldBounds): Active TextField and EditableText elements, profile avatars (CircleAvatar), and text matching patterns for emails, phone numbers, credit card numbers, or postal codes are automatically localized on-device.
  • 🛡️ No Plaintext Password Dumps: Flutter widget tree traversal strictly ignores password fields and internal framework states, preventing credentials, tokens, or app states from being exposed to the AI model or dashboard.
  • 🔒 Zero AI Model Training Guarantee: Customer screenshots and widget trees are processed strictly for code synthesis inference and are never used to train public or proprietary AI models.
  • 📜 Full Security Policy: Review our comprehensive technical safeguards at https://snappysnag.com/security or review SECURITY.md.

Documentation & AI Agents #

1
likes
150
points
681
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

An easy-to-use in-app bug reporting and user feedback SDK for Flutter, allowing users to send screenshots, device info, and custom logs seamlessly.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#bug-reporting #feedback #developer-tools #screenshot #ai

License

MIT (license)

Dependencies

flutter, http, screenshot, sensors_plus, shared_preferences

More

Packages that depend on snappy_snag