Skip to main content

flawsnap 0.4.0 copy "flawsnap: ^0.4.0" to clipboard
flawsnap: ^0.4.0 copied to clipboard

In-app bug reports for Flutter: shake or tap, annotate a screenshot and send it with device details, logs and network calls. Plus Insights: user flow, API timing and errors.

flawsnap #

Flawsnap's in-app bug reporting for Flutter apps. Users shake the phone or tap a floating button, mark up a screenshot of the app, and send. Reports land in the same Flawsnap inbox as your website's, tagged ios or android.

Setup #

Apps released with the old https://api-snaplog.mmitra.space/api/v1 keep working while that host is still served; new builds should use https://backend.flawsnap.com/api/v1.

Requires Dart 3.13 / Flutter 3.47 or newer.

  1. In the Flawsnap portal, open your project's Security settings and turn on Accept reports from mobile apps. Copy the project key.

  2. Add the package:

    flutter pub add flawsnap
    
  3. Initialise and wrap your app:

    import 'package:flawsnap/flawsnap.dart';
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Flawsnap.init(apiUrl: 'https://backend.flawsnap.com/api/v1', projectKey: 'pk_...');
      runApp(Flawsnap.wrap(const MyApp()));
    }
    
  4. Optional extras:

    MaterialApp(navigatorObservers: [Flawsnap.navigatorObserver])  // report the current screen
    final client = Flawsnap.httpClient();                           // record network calls
    Flawsnap.identify(name: user.name, email: user.email);          // prefill the reporter
    Flawsnap.log('Payment failed', level: 'error');                 // custom log lines
    Flawsnap.setMetadata({'plan': 'pro'});                          // extra details
    Flawsnap.show();                                                // open the reporter yourself
    

    Choose triggers with Flawsnap.init(..., triggers: {FlawsnapTrigger.button}) (default: shake and button). Shake needs a physical device.

    Admins can hide the floating button from the project's settings in the portal, or set its position (left/right) and colour; the label set there is used as the button's accessibility label. Shake and Flawsnap.show() keep working when the button is hidden.

Upgrading from snaplog_flutter #

The package was called snaplog_flutter up to 0.3.x. To move to flawsnap 0.4.0:

  1. Remove the snaplog_flutter dependency from pubspec.yaml and add flawsnap:

    flutter pub remove snaplog_flutter
    flutter pub add flawsnap
    

    snaplog_flutter gets no more releases.

  2. Change the imports: package:snaplog_flutter/snaplog_flutter.dart → package:flawsnap/flawsnap.dart.

  3. Rename Snaplog → Flawsnap (and SnaplogTrigger, SnaplogHttpClient, SnaplogNavigatorObserver, SnaplogLogEntry, SnaplogNetworkEntry → Flawsnap...). The old names still work as deprecated aliases, but flutter analyze reports each use and exits with an error code, so a CI step running it fails until you rename (or run flutter analyze --no-fatal-infos).

Users stay recognised across the upgrade: the stored user id (snaplog.uid.<project key>), a team member's sign-in and anything queued for Insights are kept, and what's sent doesn't change.

Team sign-in #

When the project allows it (portal: project settings, "Let team members sign in from the widget and app"), the report screen shows Team member? Sign in. The SDK shows a code like ABCD-EFGH and opens Flawsnap's approval page in the browser. The member signs in there (or on another device, with the code) and presses Allow. From then on their reports go in under their Flawsnap account ("Reporting as Jane · Sign out"), without the name and email fields. Everyone else reports anonymously as before.

  • Flawsnap.signIn() / Flawsnap.signOut() do the same from your own UI.
  • The app gets a reporter token, not a Flawsnap session. It can only send reports to this project and lasts 30 days. If the API rejects it (expired, revoked, or the setting turned off), the name and email fields come back.
  • The token is kept in shared_preferences. To keep it encrypted at rest, implement ReporterTokenStore with flutter_secure_storage and pass it as Flawsnap.init(..., tokenStore: MyStore()).
  • On Android, auto backup copies shared_preferences to the user's cloud backup by default. To keep the token out of it, exclude FlutterSharedPreferences.xml in your backup rules (android:fullBackupContent / android:dataExtractionRules), set android:allowBackup="false", or use the secure-storage option above.

Insights (user flow, API timing and errors) #

Flawsnap Insights draws your app's user flow: which screens people open, in what order, where they go back or leave, how long they stay, and which API calls each screen makes (and which are slow). It also groups Dart errors into issues. It's off unless both of these are on:

  1. The project's Insights switch in the portal (Insights → Setup).

  2. insights: true in your app, plus the navigator observer:

    await Flawsnap.init(
      apiUrl: 'https://backend.flawsnap.com/api/v1',
      projectKey: 'pk_...',
      insights: true,
    );
    // ...
    MaterialApp(navigatorObservers: [Flawsnap.navigatorObserver], ...)
    

Screen names #

Screens are named after their route's RouteSettings.name, so give every page route a name; unnamed routes show up as (unnamed). Only full-screen page routes count; dialogs and bottom sheets don't.

  • Named routes / Navigator.pushNamed: names come from your routes: table or onGenerateRoute. For MaterialPageRoute, pass settings: const RouteSettings(name: 'Cart').

  • GoRouter: add the observer and give each route a name:

    GoRouter(
      observers: [Flawsnap.navigatorObserver],
      routes: [GoRoute(name: 'Cart', path: '/cart', builder: ...)],
    )
    

    If your routes use pageBuilder, pass the name on the page too: MaterialPage(name: state.name, child: ...). A ShellRoute's nested navigator needs its own observer, and one observer can only watch one navigator, so inside shells call Flawsnap.screen('Orders') from the screen instead.

  • auto_route: pass the observer to the router config; screens are named after your route classes (CartRoute):

    MaterialApp.router(
      routerConfig: _appRouter.config(
        navigatorObservers: () => [Flawsnap.navigatorObserver],
      ),
    )
    
  • Anything else (tabs, page views, custom navigation): Flawsnap.screen('Settings') names the current screen from your code.

What's sent:

  • Screens: each screen view with how the user got there (opened, pushed, went back, replaced), the previous screen, and time on screen while the app is in the foreground. A visit ends when the app is closed or after 30 minutes in the background.
  • API calls made through Flawsnap.httpClient() (method, URL without query string or fragment, status, duration), each tied to the screen it was made on. Use it as your package:http client, or wrap your own: Flawsnap.httpClient(myClient). Calls through other clients (e.g. Dio) aren't seen yet.
  • Errors caught by FlutterError.onError and PlatformDispatcher.onError (type, message up to 500 characters, top 30 stack frames). Native iOS/Android crashes aren't included. Stacks are as compiled; obfuscated builds show obfuscated names.
  • With each batch: your app version, ios or android, and a random visit id. The visit id isn't tied to the device or user, and every launch starts a new one; it's saved only alongside events waiting to be sent, and deleted with them once they're sent.
  • The user id, only when the project recognises returning users (below). Unlike the visit id, it stays on the device across launches.

Never sent: request or response bodies, headers, query strings, your app's account ids, or anything given to Flawsnap.identify(). URLs inside error messages and stacks lose their query strings, and email addresses are masked, before anything is saved or sent. Screen names are masked the same way (ids, UUIDs, emails). Events go in batches (every 15 s, at 20 events, and when the app goes to the background). Unsent events (at most 200, about 256 KB) wait in shared_preferences and go on the next launch; when Insights is off or consent is withdrawn, that buffer is cleared.

Returning users and actions #

When the project also turns on Recognise returning visitors (Insights → Setup), the SDK keeps a random id (u_ + 26 characters) in shared_preferences (snaplog.uid.<project key>) and sends it with Insights events, so one person's sessions can be followed as a journey. It isn't your account id and isn't linked to a name or email: Flawsnap.identify() stays for bug reports only and never reaches Insights. A reinstall gives a new id, and turning the setting off deletes it at the next launch.

  • Flawsnap.track('signup_completed') records an action on the current screen. Names are lowercase letters, digits and _ . : - (1 to 64); no properties. Invalid names are ignored, including names with a part that looks like an id (6+ digits in a row, a long hex id, a UUID). At most 200 actions are sent per launch (20 of the same name).
  • Flawsnap.resetUser() deletes the id (say, on sign-out); the rest of this launch is anonymous and a new id is made on the next launch.
  • Flawsnap.consent(false) deletes the id. consent(true) after a withdrawal starts with a fresh one; calling consent(true) again while allowed keeps the current id.
  • Consent first (as in the EU, where storing this id needs consent): Flawsnap.init(..., insights: true, consentPending: true). Insights then sends nothing and keeps no id until your consent flow calls Flawsnap.consent(true); a returning user keeps their id.

Flawsnap.consent(false) stops Insights and deletes everything not sent yet, in memory and on the device. Flawsnap.consent(true) turns it back on with a new visit; nothing captured in between is sent. It's safe to call before Flawsnap.init, and the choice isn't remembered, so call it from your consent flow on every launch (or only pass insights: true once the user agreed).

App store privacy labels #

If you turn Insights on, declare this data. Flawsnap collects it on your behalf, it isn't linked to the user's identity (the user id is a random id, not your account id), and it isn't used for tracking.

  • App Store (App Privacy):
    • Diagnostics → Crash Data: app functionality, not linked, no tracking.
    • Diagnostics → Performance Data: app functionality / analytics, not linked, no tracking.
    • Usage Data → Product Interaction (screens opened and time on them): analytics / app functionality, not linked, no tracking.
    • With Recognise returning visitors on: Identifiers → User ID (the random Flawsnap id): analytics, not linked to the user's identity, not used for tracking.
  • Google Play (Data safety):
    • App info and performance → Crash logs and Diagnostics: collected, not shared, for app functionality / analytics, processed ephemerally: no.
    • App activity → App interactions (screens opened and time on them): collected, not shared, for analytics.
    • With Recognise returning visitors on: Device or other IDs (the random Flawsnap id): collected, not shared, for analytics.
    • Data is encrypted in transit (HTTPS). Users can opt out if you wire Flawsnap.consent(false) to a setting or consent prompt.

What a report includes #

  • Screenshot of the app with the user's marks (box, draw, five colours).
  • Title, description, priority, and "Who should look?" from your project's categories.
  • Device model, OS and version, app name, version and build, screen size, pixel ratio, locale, light/dark mode.
  • The current route (with navigatorObserver).
  • The last 100 log lines: Flutter errors, uncaught exceptions, debugPrint output and Flawsnap.log calls.
  • The last 100 requests made with Flawsnap.httpClient() (method, URL, status, timing; bodies are never recorded).

If the key is wrong or mobile reporting is off for the project, init logs why and the SDK stays hidden; your app is unaffected.

Local development #

The example app talks to a local API:

cd example
cp dart_defines.example.env dart_defines.env   # then put in your project key
flutter run --dart-define-from-file=dart_defines.env

The iOS example allows plain HTTP to local addresses (NSAllowsLocalNetworking) for development. Production apps should use an https API URL. The Android emulator reaches your machine at http://10.0.2.2:8080, and needs a cleartext network-security exception for HTTP.

Tests #

flutter test
3
likes
160
points
853
downloads

Documentation

Documentation
API reference

Publisher

verified publisherflawsnap.com

Weekly Downloads

In-app bug reports for Flutter: shake or tap, annotate a screenshot and send it with device details, logs and network calls. Plus Insights: user flow, API timing and errors.

Homepage
View/report issues

Topics

#bug-reporting #feedback #debugging #screenshot #analytics

License

MIT (license)

Dependencies

device_info_plus, flutter, http, package_info_plus, sensors_plus, shared_preferences, url_launcher

More

Packages that depend on flawsnap