Skip to main content

session_recorder_flutter 2.0.1 copy "session_recorder_flutter: ^2.0.1" to clipboard
session_recorder_flutter: ^2.0.1 copied to clipboard

A lightweight SDK for capturing user interactions and visible UI layout metadata during a Flutter application session.

example/lib/main.dart

import 'package:flutter/material.dart';

// ignore: depend_on_referenced_packages
import 'package:session_recorder_flutter/session_recorder_flutter.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  SessionRecorder.init(
    SessionRecorderConfig(
      endpoint: 'https://your-subdomain.ux-key.com/endpoint',

      /// Development options.
      debugLog: true,
      debugShowTree: false,
      debugSendSession: true,

      /// Optional custom logger.
      logger: (level, message, {error, stackTrace}) {
        if (level.name != 'error') return;

        debugPrint('Session Recorder error: $message');

        if (error != null) {
          debugPrint('$error');
        }

        if (stackTrace != null) {
          debugPrint('$stackTrace');
        }
      },

      /// For complex router-based navigation, you can optionally provide
      /// the application's current logical screen:
      ///
      /// screenNameProvider: () => navigationState.currentScreenName,
    ),
  );

  /// Create the observer once, after SessionRecorder.init().
  final sessionObserver = SessionNavigatorObserver();

  runApp(ExampleApp(sessionObserver: sessionObserver));
}

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

  final SessionNavigatorObserver sessionObserver;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Session Recorder Example',
      debugShowCheckedModeBanner: false,

      /// The observer is recommended for explicit navigation signals.
      ///
      /// If your application uses multiple `Navigator` instances, such as nested
      /// navigation or shell routes, use a separate `SessionNavigatorObserver()`
      /// for each Navigator.
      navigatorObservers: [sessionObserver],

      /// Stable route names improve automatic navigation context.
      initialRoute: 'home',
      onGenerateRoute: _onGenerateRoute,

      /// Required UI integration.
      builder: (context, child) =>
          SessionRecorderWidget(child: child ?? const SizedBox.shrink()),
    );
  }
}

Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
  switch (settings.name) {
    case 'home':
      return MaterialPageRoute<void>(
        settings: settings,
        builder: (_) => const HomePage(),
      );

    case 'details':
      return MaterialPageRoute<void>(
        settings: settings,
        builder: (_) => const DetailsPage(),
      );

    default:
      return null;
  }
}

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

  void _showMessage(BuildContext context, String message) {
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text(message)));
  }

  void _showExampleDialog(BuildContext context) {
    showDialog<void>(
      context: context,
      builder: (dialogContext) => AlertDialog(
        title: const Text('Example dialog'),
        content: const Text(
          'Dialogs and overlays do not require additional '
          'Session Recorder integration.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(dialogContext).pop(),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Session Recorder Example')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          const Text(
            'Try the interactions below',
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'This example demonstrates taps, gestures, scrolling, '
            'navigation and visible UI capture.',
          ),
          const SizedBox(height: 24),

          ExampleCard(
            title: 'Tap',
            description: 'A regular button interaction.',
            child: FilledButton(
              onPressed: () {
                _showMessage(context, 'Tap detected');
              },
              child: const Text('Tap me'),
            ),
          ),

          ExampleCard(
            title: 'Double tap',
            description: 'Tap the area twice quickly.',
            child: GestureDetector(
              onDoubleTap: () {
                _showMessage(context, 'Double tap detected');
              },
              child: const GestureArea(
                icon: Icons.touch_app,
                label: 'Double tap here',
              ),
            ),
          ),

          ExampleCard(
            title: 'Long press',
            description: 'Press and hold the area.',
            child: GestureDetector(
              onLongPress: () {
                _showMessage(context, 'Long press detected');
              },
              child: const GestureArea(
                icon: Icons.pan_tool_alt,
                label: 'Press and hold',
              ),
            ),
          ),

          const ExampleCard(
            title: 'Drag',
            description: 'Drag the circle horizontally.',
            child: DragExample(),
          ),

          const ExampleCard(
            title: 'Pinch',
            description: 'Use two fingers to resize the box.',
            child: PinchExample(),
          ),

          ExampleCard(
            title: 'Navigation',
            description: 'Open another named Flutter route.',
            child: FilledButton.tonal(
              onPressed: () {
                Navigator.of(context).pushNamed('details');
              },
              child: const Text('Open details'),
            ),
          ),

          ExampleCard(
            title: 'Dialog',
            description: 'Open a standard Flutter dialog.',
            child: OutlinedButton(
              onPressed: () {
                _showExampleDialog(context);
              },
              child: const Text('Show dialog'),
            ),
          ),

          const SizedBox(height: 16),

          const Text(
            'Scrollable content',
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 8),
          const Text(
            'Scroll the list to generate scroll activity and '
            'visible UI updates.',
          ),
          const SizedBox(height: 12),

          ...List.generate(
            20,
            (index) => ListTile(
              leading: CircleAvatar(child: Text('${index + 1}')),
              title: Text('Item ${index + 1}'),
              subtitle: const Text('Example scrollable item'),
              trailing: const Icon(Icons.chevron_right),
              onTap: () {
                _showMessage(context, 'Item ${index + 1} tapped');
              },
            ),
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Details')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Icon(Icons.layers_outlined, size: 72),
              const SizedBox(height: 24),
              const Text(
                'Named navigation route',
                style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 12),
              const Text(
                'This screen uses the stable logical route name '
                '"details".',
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 32),
              FilledButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: const Text('Go back'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class DragExample extends StatefulWidget {
  const DragExample({super.key});

  @override
  State<DragExample> createState() => _DragExampleState();
}

class _DragExampleState extends State<DragExample> {
  double _position = 0;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 90,
      child: LayoutBuilder(
        builder: (context, constraints) {
          const size = 56.0;
          final maxPosition = (constraints.maxWidth - size).clamp(
            0.0,
            double.infinity,
          );

          return Stack(
            children: [
              Positioned(
                left: _position.clamp(0.0, maxPosition),
                top: 17,
                child: GestureDetector(
                  onHorizontalDragUpdate: (details) {
                    setState(() {
                      _position = (_position + details.delta.dx).clamp(
                        0.0,
                        maxPosition,
                      );
                    });
                  },
                  child: const CircleAvatar(
                    radius: size / 2,
                    child: Icon(Icons.drag_indicator),
                  ),
                ),
              ),
            ],
          );
        },
      ),
    );
  }
}

class PinchExample extends StatefulWidget {
  const PinchExample({super.key});

  @override
  State<PinchExample> createState() => _PinchExampleState();
}

class _PinchExampleState extends State<PinchExample> {
  double _scale = 1;
  double _baseScale = 1;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 150,
      child: Center(
        child: GestureDetector(
          onScaleStart: (_) {
            _baseScale = _scale;
          },
          onScaleUpdate: (details) {
            setState(() {
              _scale = (_baseScale * details.scale).clamp(0.7, 1.8);
            });
          },
          child: Transform.scale(
            scale: _scale,
            child: Container(
              width: 90,
              height: 90,
              alignment: Alignment.center,
              decoration: BoxDecoration(
                border: Border.all(),
                borderRadius: BorderRadius.circular(16),
              ),
              child: const Icon(Icons.pinch, size: 36),
            ),
          ),
        ),
      ),
    );
  }
}

class GestureArea extends StatelessWidget {
  const GestureArea({super.key, required this.icon, required this.label});

  final IconData icon;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 80,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        border: Border.all(color: Theme.of(context).colorScheme.outline),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [Icon(icon), const SizedBox(width: 12), Text(label)],
      ),
    );
  }
}

class ExampleCard extends StatelessWidget {
  const ExampleCard({
    super.key,
    required this.title,
    required this.description,
    required this.child,
  });

  final String title;
  final String description;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.only(bottom: 16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text(
              title,
              style: Theme.of(
                context,
              ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 4),
            Text(description),
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }
}
3
likes
160
points
226
downloads

Documentation

API reference

Publisher

verified publisheropentech-ux.org

Weekly Downloads

A lightweight SDK for capturing user interactions and visible UI layout metadata during a Flutter application session.

Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

crypto, flutter, http, meta, uuid

More

Packages that depend on session_recorder_flutter