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

Rolling-buffer screen recording for bug reports. Continuously keeps the last N seconds of screen activity, so a tester who shakes the phone after noticing a bug captures the steps that caused it - not [...]

example/lib/main.dart

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

import 'screens/checkout_screen.dart';
import 'screens/home_screen.dart';
import 'widgets/recording_indicator.dart';
import 'widgets/recording_sheet.dart';

/// Demonstrates the full rolling-buffer flow:
///
/// ```
/// start buffering -> tester uses the app -> a bug happens -> tester shakes
///   -> [20s before the shake] + [10s after] -> one MP4 -> preview and share
/// ```
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final recorder = BugRecorder.instance;

  // Initialising never throws on an unsupported platform; it reports
  // capabilities instead, so no Platform.isX guard is needed here.
  final capabilities = await recorder.initialize(
    config: const BugRecorderConfig(
      bufferDuration: Duration(seconds: 20),
      postTriggerDuration: Duration(seconds: 10),
      // 2s segments: ~10 files for the window, and at most 2s of video lost if
      // the app crashes before the tester shakes.
      segmentDuration: Duration(seconds: 2),
      maxDimension: 720,
      frameRate: 24,
      notification: AndroidNotificationConfig(
        title: 'Bug recorder is on',
        body: 'Shake your phone when you see something wrong.',
      ),
    ),
  );

  // Ask about a crashed previous run *before* starting a new session, otherwise
  // the new session's directory is the newest one and the old one is pruned.
  final recovered = await _tryRecover(recorder);

  runApp(ExampleApp(capabilities: capabilities, recovered: recovered));
}

Future<RecordingResult?> _tryRecover(BugRecorder recorder) async {
  if (!(recorder.capabilities?.supported ?? false)) return null;
  try {
    return await recorder.recoverPreviousSession();
  } on BugRecorderException catch (error) {
    debugPrint('crash recovery skipped: $error');
    return null;
  }
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key, required this.capabilities, this.recovered});

  final PlatformCapabilities capabilities;
  final RecordingResult? recovered;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bug Recorder',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6750A4)),
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF6750A4),
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      routes: {CheckoutScreen.route: (_) => const CheckoutScreen()},
      home: _Root(capabilities: capabilities, recovered: recovered),
    );
  }
}

/// Hosts the [BugRecorderScope] so a finished recording can be surfaced from one
/// place no matter which screen the tester was on when they shook the phone.
class _Root extends StatefulWidget {
  const _Root({required this.capabilities, this.recovered});

  final PlatformCapabilities capabilities;
  final RecordingResult? recovered;

  @override
  State<_Root> createState() => _RootState();
}

class _RootState extends State<_Root> {
  final _navigatorKey = GlobalKey<NavigatorState>();

  @override
  void initState() {
    super.initState();
    final recovered = widget.recovered;
    if (recovered != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _present(recovered, crashRecovery: true);
      });
    }
  }

  void _present(RecordingResult result, {bool crashRecovery = false}) {
    final context = _navigatorKey.currentContext ?? this.context;
    showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      useSafeArea: true,
      builder: (_) =>
          RecordingSheet(result: result, crashRecovery: crashRecovery),
    );
  }

  @override
  Widget build(BuildContext context) {
    return BugRecorderScope(
      onRecording: _present,
      child: Stack(
        children: [
          Navigator(
            key: _navigatorKey,
            onGenerateRoute: (settings) => MaterialPageRoute<void>(
              builder: (_) => switch (settings.name) {
                CheckoutScreen.route => const CheckoutScreen(),
                _ => HomeScreen(capabilities: widget.capabilities),
              },
              settings: settings,
            ),
          ),
          // The system provides disclosure on both platforms (a mandatory
          // notification on Android, a consent alert on iOS), but an in-app
          // indicator is the honest thing to add: it is visible at the moment
          // recording is actually happening.
          const Positioned(
            top: 0,
            left: 0,
            right: 0,
            child: SafeArea(child: RecordingIndicator()),
          ),
        ],
      ),
    );
  }
}
2
likes
140
points
90
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Rolling-buffer screen recording for bug reports. Continuously keeps the last N seconds of screen activity, so a tester who shakes the phone after noticing a bug captures the steps that caused it - not just the aftermath.

Topics

#screen-recording #bug-reporting #debugging #testing #test-in-past

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on bug_recorder

Packages that implement bug_recorder