flutter_user_recorder 0.2.10 copy "flutter_user_recorder: ^0.2.10" to clipboard
flutter_user_recorder: ^0.2.10 copied to clipboard

Record and replay user interactions in Flutter apps. Automatically captures taps, text input, scrolls, navigation, and gestures. Perfect for automated testing, user flow analysis, and interaction repl [...]

flutter_user_recorder #

pub package License: MIT GitHub

flutter_user_recorder - A comprehensive Flutter package for recording and replaying user interactions. Automatically captures taps, text input, scrolls, navigation, and gestures. Perfect for automated testing, user flow analysis, and interaction replay. Features automatic persistence, session management, and a modern UI.

🎉 What's New in v0.2.7 #

  • 🔧 Fixed go_router Detection: Fixed issue where go_router wasn't detected when RecorderLayer wraps MaterialApp.router. Now works reliably!
  • 🚀 Automatic go_router Integration: go_router integration is fully automatic - just wrap MaterialApp.router and it works!
  • 📚 Enhanced Documentation: Improved package description and step-by-step installation guide
  • 📖 Better README: Clearer organization and more comprehensive examples

🎉 Previous Updates (v0.2.6) #

  • 🚀 Automatic go_router Integration: go_router integration is now fully automatic
  • 📚 Enhanced Documentation: Improved package description and step-by-step installation guide

🎉 Previous Updates (v0.2.5) #

  • Fixed Persistence: Sessions now properly save and load on app restart
  • 📱 Responsive UI: Dialog and bottom sheet layouts adapt to all screen sizes
  • 💾 Auto-Save: All operations (save, delete, clear) are automatically persisted

🎉 Previous Updates (v0.2.4) #

  • 🎯 Automatic Persistence: Sessions are automatically saved and persist across app restarts - no data loss!
  • 🏷️ Session Naming: Give meaningful names to your recording sessions for better organization
  • 🎨 Modern UI: Beautiful glassmorphism design with responsive layouts that work on all screen sizes
  • 🔄 Smart Replay: Replay automatically navigates to the initial screen before executing events
  • 🏗️ Clean Architecture: Refactored with Clean Architecture and BLoC pattern for better maintainability
  • 💾 Auto-Save: All changes (stop recording, delete session) are automatically persisted

✨ Features #

  • 🎯 Easy Integration: Wrap your app with RecorderLayer and start recording
  • 📝 Comprehensive Recording: Records taps, text input, scrolls, navigation, drag/swipe gestures
  • 🔄 Accurate Replay: Replays interactions exactly as recorded, starting from the initial screen
  • 💾 Automatic Persistence: Sessions are automatically saved to local storage and persist across app restarts
  • 🏷️ Session Naming: Give meaningful names to your recording sessions
  • 📦 Export/Import: Export recordings as JSON for sharing or analysis
  • 🎨 Modern UI: Beautiful glassmorphism design with responsive layouts
  • 🎛️ Built-in Widgets: Ready-to-use widgets for common interactions
  • 🚀 Speed Control: Adjust replay speed (0.5x, 1x, 2x, 5x)
  • 🔂 Script Looping: Repeat actions seamlessly with loop functionality
  • 🎛️ Floating Control Panel: Beautiful FAB with all controls
  • 🏗️ Clean Architecture: Built with Clean Architecture and BLoC pattern
  • 🔒 Null-Safe: Fully null-safety enabled

📦 Installation #

Step 1: Add dependency #

Add flutter_user_recorder to your pubspec.yaml file:

dependencies:
  flutter_user_recorder: ^0.2.10

Step 2: Install the package #

Run this command in your terminal:

flutter pub get

Step 3: Import the package #

Add this import to your Dart files:

import 'package:flutter_user_recorder/flutter_user_recorder.dart';

Step 4: Start using it! #

Wrap your app with RecorderLayer and you're ready to go. See Quick Start below for examples.

🚀 Quick Start #

The simplest way to get started:

import 'package:flutter/material.dart';
import 'package:flutter_user_recorder/flutter_user_recorder.dart';
import 'package:go_router/go_router.dart';

// Create global instances (optional - can be created automatically)
final recorderController = RecorderController();
final replayer = Replayer();

void main() {
  // That's it! No manual setup needed.
  // RecorderLayer will automatically configure go_router integration.
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    // Create GoRouter with RecorderNavigatorObserver
    final router = GoRouter(
      initialLocation: '/',
      observers: [RecorderNavigatorObserver(controller: recorderController)],
      routes: [
        GoRoute(
          path: '/',
          name: 'home',
          builder: (context, state) => HomePage(),
        ),
      ],
    );

    // Wrap MaterialApp.router with RecorderLayer
    // go_router integration is automatic - no manual setup needed!
    return RecorderLayer(
      controller: recorderController,
      replayer: replayer,
      recordNavigation: true,
      recordGlobalScroll: true,
      recordDrag: true,
      showControls: true, // Set to false to hide global FAB
      child: MaterialApp.router(
        title: 'My App',
        routerConfig: router,
      ),
    );
  }
}

Key Points:

  • ✅ Sessions are automatically saved to local storage when you stop recording
  • ✅ Sessions are automatically loaded when the app starts (no data loss!)
  • ✅ You can name your sessions when starting a recording
  • ✅ The floating control panel (RecorderFAB) provides all controls
  • ✅ Modern, responsive UI with glassmorphism design
  • ✅ Replay automatically starts from the initial screen of the session
  • go_router integration is automatic - no manual setup needed!

2. go_router integration (Automatic! 🎉) #

go_router integration is now fully automatic! Just wrap your MaterialApp.router with RecorderLayer and everything works:

import 'package:flutter/material.dart';
import 'package:flutter_user_recorder/flutter_user_recorder.dart';
import 'package:go_router/go_router.dart';

// Create global instances
final recorderController = RecorderController();
final replayer = Replayer();

void main() {
  // That's it! No manual setup needed.
  // RecorderLayer will automatically configure go_router integration.
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    // Create GoRouter with RecorderNavigatorObserver
    final router = GoRouter(
      initialLocation: '/',
      observers: [RecorderNavigatorObserver(controller: recorderController)],
      routes: [
        GoRoute(
          path: '/',
          name: 'home',
          builder: (context, state) => HomePage(),
          routes: [
            GoRoute(
              path: 'second',
              name: 'second',
              builder: (context, state) => SecondPage(),
            ),
          ],
        ),
      ],
    );

    // Wrap MaterialApp.router with RecorderLayer
    // go_router integration is automatic - no manual setup needed!
    return RecorderLayer(
      controller: recorderController,
      replayer: replayer,
      recordNavigation: true,
      recordGlobalScroll: true,
      recordDrag: true,
      showControls: true, // Set to false to hide global FAB
      child: MaterialApp.router(
        title: 'My App',
        routerConfig: router,
      ),
    );
  }
}

No manual setup needed! RecorderLayer automatically:

  • ✅ Detects MaterialApp.router
  • ✅ Configures go_router navigation for replay
  • ✅ Handles all navigation types (push, pop, replace, etc.)
  • ✅ Works with both named routes and paths

3. Show/Hide built-in controls #

The floating control panel (RecorderFAB) is now auto-injected by default.

// Default (auto show controls)
RecorderLayer(
  controller: recorder,
  replayer: replayer,
  child: MaterialApp(...),
);

// Hide global controls (you can place RecorderFAB manually)
RecorderLayer(
  controller: recorder,
  replayer: replayer,
  showControls: false,
  child: MaterialApp(...),
);

4. Use built-in recorder widgets #

class HomePage extends StatelessWidget {
  final RecorderController recorderController;
  final Replayer replayer;

  const HomePage({
    super.key,
    required this.recorderController,
    required this.replayer,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      body: Column(
        children: [
          // Record text input
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: RecorderTextField(
              id: 'username_field',
              decoration: const InputDecoration(
                labelText: 'Username',
                border: OutlineInputBorder(),
              ),
            ),
          ),

          // Record button taps
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: RecorderTap(
              id: 'submit_button',
              child: ElevatedButton(
                onPressed: () {
                  print('Submitted!');
                },
                child: const Text('Submit'),
              ),
            ),
          ),

          // Record scrolls
          Expanded(
            child: RecorderScroll(
              id: 'my_list',
              child: ListView.builder(
                itemCount: 50,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text('Item $index'),
                  );
                },
              ),
            ),
          ),
        ],
      ),
      // Add floating control panel
      floatingActionButton: RecorderFAB(),
    );
  }
}

5. Control Recording Programmatically #

// Get the controller
final controller = RecorderLayer.of(context);

// Start recording with optional session name
controller.start(sessionName: 'Login Flow Test');

// Stop recording (automatically saves to local storage)
controller.stop();

// Get current events
final events = controller.events;

// Get all sessions (automatically loaded from storage)
final sessions = controller.sessions;

// Sessions are automatically saved, but you can manually save/load:
await controller.save();  // Manually save (usually not needed)
await controller.load();  // Manually reload (usually not needed)

// Export current session as JSON
final json = controller.export();

// Export all sessions as JSON
final allSessionsJson = controller.exportAllSessions();

// Replay events (starts from initial screen automatically)
await replayer.replay(events, context: context);

// Set replay speed
replayer.setSpeed(2.0);  // 2x speed

// Set loop count
replayer.setLoopCount(5);  // Repeat 5 times

// Delete a session (automatically saves changes)
controller.deleteSession(sessionId);

// Delete all sessions (automatically saves changes)
controller.clearAllSessions();

Important Notes:

  • ✅ Sessions are automatically saved when you stop recording
  • ✅ Sessions are automatically loaded when the app starts
  • ✅ All changes (delete, clear) are automatically persisted
  • ✅ No need to manually call save() or load() in most cases

📖 Core Components #

RecorderLayer #

Wraps your widget tree and provides RecorderController to descendant widgets. Similar to BlocProvider.

RecorderLayer(
  controller: recorderController,  // Optional - created automatically if not provided
  replayer: replayer,              // Optional - created automatically if not provided
  recordNavigation: true,          // Record route changes
  recordGlobalScroll: true,        // Record full-screen scrolls
  recordDrag: true,                // Record drag/swipe gestures
  minScrollDelta: 10.0,            // Minimum scroll delta to record
  child: YourApp(),
)

Important: Make sure to add the navigator observer to your MaterialApp:

MaterialApp(
  navigatorObservers: [
    RecorderLayer.navigatorObserver(RecorderLayer.of(context)),
  ],
  // ...
)

Note: When using routes, make sure to pass recorderController and replayer to your page widgets as shown above.

RecorderController #

Manages recording state and events. Provides methods to:

  • start({String? sessionName}) - Start recording with optional session name (creates a new session)
  • stop() - Stop recording (automatically saves the current session to local storage)
  • record() - Record an event (called automatically by widgets)
  • export() - Export current session events as JSON string
  • exportAllSessions() - Export all sessions as JSON string
  • import() - Import events from JSON string
  • importSessions() - Import sessions from JSON string
  • save() - Manually save all sessions to local storage (usually not needed - auto-save is enabled)
  • load() - Manually load all sessions from local storage (usually not needed - auto-load on init)
  • clear() - Clear current session events
  • deleteSession(String id) - Delete a specific session (automatically saves changes)
  • clearAllSessions() - Delete all sessions (automatically saves changes)
  • getSession(String id) - Get a specific session

Auto-Persistence:

  • Sessions are automatically saved when you stop recording
  • Sessions are automatically loaded when the app starts
  • All changes (delete, clear) are automatically persisted

Replayer #

Replays recorded events in order, with state-based readiness and timing controls.

final replayer = Replayer(
  minDelayMs: 150,            // Minimum delay between events
  maxInterEventDelayMs: 800,  // Cap long gaps to avoid stalls
  respectTiming: true,        // Use original timing
  awaitNavigationReady: true, // Wait for route & tree readiness after navigation
  awaitTextCommit: true,      // Wait a short moment after text input
  maxConditionWaitMs: 1200,   // Max time to await readiness conditions
  onReplayStart: () => print('Started'),
  onReplayComplete: () => print('Done'),
  onEventReplayed: (event) => print('Replayed: $event'),
  onEventFailed: (event, error) => print('Failed: $error'),
);

// Replay events
await replayer.replay(events, context: context);

// Set replay speed
replayer.setSpeed(2.0);  // 2x speed

// Set loop count
replayer.setLoopCount(5);  // Repeat 5 times

// Replay single event
await replayer.replaySingleEvent(event, context: context);

Built-in Widgets #

RecorderTap

Wraps any widget with tap gesture recording.

RecorderTap(
  id: 'my_button',
  onTap: () {
    // Your tap handler
  },
  onLongPress: () {
    // Optional long press handler
  },
  recordLongPress: true,  // Record long press events
  child: ElevatedButton(
    onPressed: () {},
    child: Text('Tap me'),
  ),
)

RecorderTextField

Wraps TextField/TextFormField with text input recording.

RecorderTextField(
  id: 'my_text_field',
  controller: textController,  // Optional
  decoration: InputDecoration(labelText: 'Enter text'),
  // Recording is debounced, and a final value is captured on blur
  onChanged: (value) {
    print('Text: $value');
  },
)

RecorderScroll

Wraps scrollable widgets with scroll position recording.

RecorderScroll(
  id: 'my_list',
  minScrollDelta: 10.0,  // Minimum scroll delta to record
  recordScroll: true,     // Enable scroll recording
  child: ListView(
    children: [/* ... */],
  ),
)

RecorderTabBar

Record tab selections as discrete events (no need to record swipes).

final tabController = TabController(length: 3, vsync: this);

RecorderTabBar(
  id: 'main_tabs',
  controller: tabController,
  tabs: const [
    Tab(text: 'Home'),
    Tab(text: 'Feed'),
    Tab(text: 'Profile'),
  ],
)

RecorderPageView

Record page changes with a PageController, replay animates to the recorded page.

final pageController = PageController();

RecorderPageView(
  id: 'main_pager',
  controller: pageController,
  children: const [
    HomeView(),
    FeedView(),
    ProfileView(),
  ],
)

Control Widgets #

RecorderFAB

A floating action button that provides a control panel for recording and replaying.

RecorderFAB(
  controller: recorderController,  // Optional - retrieved from RecorderLayer if not provided
  replayer: replayer,              // Optional - retrieved from RecorderLayer if not provided
)

Features:

  • Start/Stop recording
  • Replay current session
  • View events list
  • View sessions list
  • Speed control (0.5x, 1x, 2x, 5x)
  • Loop control (Once, 2x, 5x, 10x)

RecorderEventsList

Displays a list of recorded events for the current session.

RecorderEventsList(
  controller: recorderController,
  replayer: replayer,
)

RecorderSessionsList

Displays a list of all saved sessions.

RecorderSessionsList(
  controller: recorderController,
  replayer: replayer,
)

🎯 Advanced Usage #

Custom RecorderTarget #

Create custom recordable widgets by implementing RecorderTarget:

class MyCustomWidget extends StatefulWidget {
  final String id;

  @override
  State<MyCustomWidget> createState() => _MyCustomWidgetState();
}

class _MyCustomWidgetState extends State<MyCustomWidget>
    implements RecorderTarget {
  @override
  String get id => widget.id;

  @override
  void initState() {
    super.initState();
    RecorderRegistry().register(this);
  }

  @override
  void dispose() {
    RecorderRegistry().unregister(id);
    super.dispose();
  }

  @override
  Future<bool> perform(RecorderEventType type, dynamic value) async {
    // Perform the action based on type and value
    if (type == RecorderEventType.tap) {
      // Handle tap
      return true;
    }
    return false;
  }

  void _handleInteraction() {
    final controller = RecorderLayer.maybeOf(context);
    if (controller != null && controller.isRecording) {
      final currentRoute = RecorderLayer.currentRouteOf(context);
      controller.record(
        RecorderEventType.tap,
        id,
        null,
        route: currentRoute,  // Automatically stores route with event
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _handleInteraction,
      child: Container(/* ... */),
    );
  }
}

Event Model #

Events are represented by RecorderEvent:

class RecorderEvent {
  final RecorderEventType type;      // tap, textInput, scroll, navigation, etc.
  final String targetId;              // Widget ID
  final dynamic value;                // Event value (text, scroll position, etc.)
  final int timestamp;                // When it occurred (milliseconds since epoch)
  final Map<String, dynamic>? metadata;  // Optional metadata (route, arguments, etc.)
}

Session Model #

Sessions are represented by RecorderSession:

class RecorderSession {
  final String id;                    // Unique session ID
  final String name;                  // Session name
  final List<RecorderEvent> events;   // Events in this session
  final int startTimestamp;           // When recording started
  final int? stopTimestamp;           // When recording stopped
}

The package automatically records navigation events with arguments:

// Navigate with data
Navigator.pushNamed(
  context,
  '/second',
  arguments: {
    'name': 'John',
    'from': 'home',
  },
);

// The navigation event is automatically recorded with the arguments
// During replay, the arguments are passed to the route

Export/Import Events #

// Export current session to JSON
final jsonString = controller.export();
print(jsonString);

// Export all sessions to JSON
final allSessionsJson = controller.exportAllSessions();

// Import events from JSON
controller.import(jsonString);

// Import sessions from JSON
controller.importSessions(allSessionsJson);

// Save to device storage
await controller.save();

// Load from device storage
await controller.load();

📱 Example App #

See the example/ folder for a complete demo app that demonstrates:

  • ✅ Recording text input
  • ✅ Recording button taps
  • ✅ Recording scrolls
  • ✅ Recording navigation with data transfer
  • ✅ Replaying interactions (starts from initial screen)
  • ✅ Managing multiple sessions with names
  • ✅ Automatic persistence (sessions survive app restarts)
  • ✅ Speed control and looping
  • ✅ Modern UI with glassmorphism design
  • ✅ Using the floating control panel

Run the example:

cd example
flutter run

The example app shows:

  • How to integrate with go_router
  • How to use the built-in widgets (RecorderTap, RecorderTextField, RecorderScroll)
  • How sessions are automatically saved and loaded
  • How to name sessions when starting a recording
  • How replay automatically navigates to the initial screen

🏗️ Architecture #

The package follows Clean Architecture with BLoC pattern:

Domain Layer #

  • entities/: RecorderEventEntity, RecorderSessionEntity
  • repositories/: RecorderRepository interface
  • use_cases/: Business logic (StartRecording, StopRecording, RecordEvent, ReplayEvents)

Data Layer #

  • repositories/: RecorderRepositoryImpl - implements repository with SharedPreferences storage
  • mappers/: Converts between entities and JSON
  • use_cases/: ReplayEventsUseCaseImpl - replay implementation

Presentation Layer #

  • bloc/: RecorderBloc - manages state with BLoC pattern
  • compatibility/: Wrappers for backward compatibility (RecorderController, Replayer)

Core Components #

  • core/: Storage abstraction, registry
  • recording/: Navigation tracking, event recording
  • replay/: Event replay logic
  • widgets/: UI components (RecorderFAB, RecorderSessionsList, etc.)

Key Features:

  • ✅ Automatic persistence using SharedPreferences
  • ✅ Clean separation of concerns
  • ✅ Backward compatible API
  • ✅ Easy to test and extend

📋 Requirements #

  • Flutter >= 3.0.0
  • Dart >= 3.0.0

📦 Dependencies #

  • shared_preferences: ^2.2.2 - For automatic persistent storage
  • flutter_bloc: ^8.1.6 - For state management
  • equatable: ^2.0.5 - For value equality
  • go_router (optional) - For routing integration via navigationDelegate

🤝 Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📝 License #

This project is licensed under the MIT License - see the LICENSE file for details.

👤 Author #

Created with ❤️ for the Flutter community.

🙏 Acknowledgments #

Inspired by browser automation tools like "Action Replay" Chrome extension.

📚 Additional Resources #


Note: This package is designed for testing and automation purposes. Make sure to handle user data and privacy appropriately in your application.

2
likes
0
points
52
downloads

Publisher

unverified uploader

Weekly Downloads

Record and replay user interactions in Flutter apps. Automatically captures taps, text input, scrolls, navigation, and gestures. Perfect for automated testing, user flow analysis, and interaction replay. Features automatic persistence, session management with naming, and modern responsive UI.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

equatable, flutter, flutter_bloc, shared_preferences

More

Packages that depend on flutter_user_recorder