flutter_user_recorder 0.1.0
flutter_user_recorder: ^0.1.0 copied to clipboard
A powerful Flutter package for automatically recording and replaying user interactions on any screen or widget. Perfect for testing, automation, and user flow analysis.
flutter_recorder #
A powerful Flutter package for automatically recording and replaying user interactions on any screen or widget. Perfect for testing, automation, and user flow analysis.
✨ Features #
- 🎯 Easy Integration: Wrap your app with
RecorderLayerand start recording - 📝 Comprehensive Recording: Records taps, text input, scrolls, navigation, drag/swipe gestures
- 🔄 Accurate Replay: Replays interactions exactly as recorded, with timing preserved
- 💾 Session Management: Save and manage multiple recording sessions
- 📦 Export/Import: Export recordings as JSON for sharing or analysis
- 🎨 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: MVVM-ready, modular, and extensible
- 🔒 Null-Safe: Fully null-safety enabled
📦 Installation #
Add flutter_recorder to your pubspec.yaml:
dependencies:
flutter_recorder: ^0.1.0
Then run:
flutter pub get
🚀 Quick Start #
1. Wrap your app with RecorderLayer #
import 'package:flutter/material.dart';
import 'package:flutter_recorder/flutter_recorder.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return RecorderLayer(
recordNavigation: true,
recordGlobalScroll: true,
recordDrag: true,
child: MaterialApp(
title: 'My App',
home: HomePage(),
),
);
}
}
2. Use built-in recorder widgets #
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
// Record text input
RecorderTextField(
id: 'username_field',
decoration: InputDecoration(labelText: 'Username'),
),
// Record button taps
RecorderTap(
id: 'submit_button',
child: ElevatedButton(
onPressed: () {
print('Submitted!');
},
child: Text('Submit'),
),
),
// Record scrolls
RecorderScroll(
id: 'my_list',
child: ListView(
children: [/* ... */],
),
),
],
),
// Add floating control panel
floatingActionButton: RecorderFAB(),
);
}
}
3. Control recording programmatically #
// Get the controller
final controller = RecorderLayer.of(context);
// Start recording
controller.start();
// Stop recording
controller.stop();
// Get current events
final events = controller.events;
// Save session
await controller.save();
// Load session
await controller.load();
// Export as JSON
final json = controller.export();
📖 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)),
],
// ...
)
RecorderController #
Manages recording state and events. Provides methods to:
start()- Start recording (creates a new session)stop()- Stop recording (saves the current session)record()- Record an event (called automatically by widgets)export()- Export current session events as JSON stringexportAllSessions()- Export all sessions as JSON stringimport()- Import events from JSON stringimportSessions()- Import sessions from JSON stringsave()- Save all sessions to local storageload()- Load all sessions from local storageclear()- Clear current session eventsdeleteSession(String id)- Delete a specific sessionclearAllSessions()- Delete all sessionsgetSession(String id)- Get a specific session
Replayer #
Replays recorded events in order, respecting time delays.
final replayer = Replayer(
minDelayMs: 200, // Minimum delay between events
respectTiming: true, // Use original timing
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'),
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: [/* ... */],
),
)
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
}
Navigation with Data #
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
- Managing multiple sessions
- Speed control and looping
- Using the floating control panel
Run the example:
cd example
flutter run
🏗️ Architecture #
The package follows a clean, modular architecture:
- models.dart: Event and session data models with JSON serialization
- registry.dart: Global registry for RecorderTarget widgets
- recorder_controller.dart: Manages recording state, events, and sessions
- recorder_layer.dart: InheritedWidget providing controller and replayer context
- replayer.dart: Replays recorded events with timing, speed control, and looping
- widgets/: Built-in recorder widgets and control panels
📋 Requirements #
- Flutter >= 3.0.0
- Dart >= 3.0.0
📦 Dependencies #
shared_preferences: ^2.2.2- For persistent storage
🤝 Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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.