survicate_sdk 8.1.1 copy "survicate_sdk: ^8.1.1" to clipboard
survicate_sdk: ^8.1.1 copied to clipboard

Official plugin for Survicate SDK integration with Flutter. Effortlessly collect user feedback and gain insights to improve your mobile app.

example/lib/main.dart

import 'dart:convert';

import 'package:flutter/material.dart' hide ThemeMode;
import 'package:survicate_sdk/survicate_sdk.dart';
import 'package:survicate_sdk/user_trait.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

const int _logLimit = 50;

class _MyAppState extends State<MyApp> {
  // Workspace
  final _workspaceKeyController = TextEditingController();
  bool _sdkInitialized = false;

  // Events
  final _eventNameController = TextEditingController();
  final _propKeyController = TextEditingController();
  final _propValueController = TextEditingController();
  final Map<String, String> _eventProps = {};

  // User traits
  final _traitKeyController = TextEditingController();
  final _traitValueController = TextEditingController();
  final List<_TraitEntry> _traits = [];

  // Screens
  final _screenNameController = TextEditingController();

  // Response attributes
  final _attrNameController = TextEditingController();
  final _attrValueController = TextEditingController();
  final _attrProviderController = TextEditingController();
  final List<_AttrEntry> _responseAttrs = [];

  // Locale
  final _localeController = TextEditingController();

  // Log
  final List<_LogEntry> _log = [];
  final Set<int> _expandedIds = {};
  bool _logVisible = true;
  int _nextId = 0;

  SurvicateEventListener? _listener;

  @override
  void dispose() {
    if (_listener != null) {
      SurvicateSdk.removeSurvicateEventListener(_listener!);
    }
    _workspaceKeyController.dispose();
    _eventNameController.dispose();
    _propKeyController.dispose();
    _propValueController.dispose();
    _traitKeyController.dispose();
    _traitValueController.dispose();
    _screenNameController.dispose();
    _attrNameController.dispose();
    _attrValueController.dispose();
    _attrProviderController.dispose();
    _localeController.dispose();
    super.dispose();
  }

  int _id() => ++_nextId;

  void _appendLog(String type, Object? payload) {
    setState(() {
      _log.insert(0, _LogEntry(_id(), DateTime.now(), type, payload));
      if (_log.length > _logLimit) {
        _log.removeRange(_logLimit, _log.length);
      }
    });
  }

  void _toggleExpanded(int id) {
    setState(() {
      if (_expandedIds.contains(id)) {
        _expandedIds.remove(id);
      } else {
        _expandedIds.add(id);
      }
    });
  }

  void _initializeSdk() {
    final key = _workspaceKeyController.text;
    SurvicateSdk.setWorkspaceKey(key);
    SurvicateSdk.initializeSdk();
    final listener = SurvicateEventListener(
      onSurveyDisplayed: (event) => _appendLog(
        'onSurveyDisplayed',
        {'surveyId': event.surveyId},
      ),
      onQuestionAnswered: (event) => _appendLog('onQuestionAnswered', {
        'surveyId': event.surveyId,
        'surveyName': event.surveyName,
        'visitorUuid': event.visitorUuid,
        'responseUuid': event.responseUuid,
        'questionId': event.questionId,
        'question': event.question,
        'answer': {
          'type': event.answer.type,
          'id': event.answer.id,
          'ids': event.answer.ids,
          'value': event.answer.value,
        },
        'panelAnswerUrl': event.panelAnswerUrl,
      }),
      onSurveyClosed: (event) => _appendLog(
        'onSurveyClosed',
        {'surveyId': event.surveyId},
      ),
      onSurveyCompleted: (event) => _appendLog(
        'onSurveyCompleted',
        {'surveyId': event.surveyId},
      ),
    );
    SurvicateSdk.addSurvicateEventListener(listener);
    setState(() {
      _listener = listener;
      _sdkInitialized = true;
    });
    _appendLog('→ initializeSdk', {'workspaceKey': key});
  }

  void _addEventProp() {
    final key = _propKeyController.text;
    if (key.isEmpty) return;
    setState(() {
      _eventProps[key] = _propValueController.text;
      _propKeyController.clear();
      _propValueController.clear();
    });
  }

  void _removeEventProp(String key) {
    setState(() => _eventProps.remove(key));
  }

  void _invokeEvent() {
    final name = _eventNameController.text;
    final props = _eventProps.isEmpty ? null : Map<String, String>.of(_eventProps);
    if (props != null) {
      SurvicateSdk.invokeEvent(name, eventProperties: props);
    } else {
      SurvicateSdk.invokeEvent(name);
    }
    _appendLog('→ invokeEvent', {'eventName': name, 'properties': props});
  }

  void _addTrait() {
    final key = _traitKeyController.text;
    if (key.isEmpty) return;
    final value = _traitValueController.text;
    final trait = UserTrait.string(key, value);
    SurvicateSdk.setUserTrait(trait);
    _appendLog('→ setUserTrait', {'key': trait.key, 'value': trait.value});
    setState(() {
      _traits.add(_TraitEntry(_id(), trait.key, trait.value));
      _traitKeyController.clear();
      _traitValueController.clear();
    });
  }

  void _addResponseAttr() {
    final name = _attrNameController.text;
    if (name.isEmpty) return;
    final value = _attrValueController.text;
    final provider = _attrProviderController.text;
    final attr = ResponseAttribute.string(
      name,
      value,
      provider: provider.isEmpty ? null : provider,
    );
    SurvicateSdk.setResponseAttribute(attr);
    _appendLog('→ setResponseAttribute', {
      'name': attr.name,
      'value': attr.value,
      'provider': attr.provider,
    });
    setState(() {
      _responseAttrs.add(_AttrEntry(_id(), attr.name, attr.value, attr.provider));
      _attrNameController.clear();
      _attrValueController.clear();
      _attrProviderController.clear();
    });
  }

  void _enterScreen() {
    final name = _screenNameController.text;
    SurvicateSdk.enterScreen(name);
    _appendLog('→ enterScreen', {'screenName': name});
  }

  void _leaveScreen() {
    final name = _screenNameController.text;
    SurvicateSdk.leaveScreen(name);
    _appendLog('→ leaveScreen', {'screenName': name});
  }

  void _applyTheme(ThemeMode mode) {
    SurvicateSdk.setThemeMode(mode);
    _appendLog('→ setThemeMode', {'themeMode': mode.name});
  }

  void _applyLocale() {
    final tag = _localeController.text;
    SurvicateSdk.setLocale(tag);
    _appendLog('→ setLocale', {'languageTag': tag});
  }

  void _resetAll() {
    SurvicateSdk.reset();
    setState(() {
      _localeController.clear();
      _screenNameController.clear();
      _eventNameController.clear();
      _propKeyController.clear();
      _propValueController.clear();
      _eventProps.clear();
      _traitKeyController.clear();
      _traitValueController.clear();
      _traits.clear();
      _attrNameController.clear();
      _attrValueController.clear();
      _attrProviderController.clear();
      _responseAttrs.clear();
      _expandedIds.clear();
      _log
        ..clear()
        ..add(_LogEntry(_id(), DateTime.now(), '→ reset', const {}));
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Survicate Flutter Example',
      home: Scaffold(
        backgroundColor: Colors.white,
        body: SafeArea(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const Text('Survicate Flutter Example', style: _Styles.title),

                _sectionTitle('Workspace key'),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _workspaceKeyController,
                        enabled: !_sdkInitialized,
                        decoration: _inputDecoration('workspaceKey'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                        onChanged: (_) => setState(() {}),
                      ),
                    ),
                    const SizedBox(width: 8),
                    _ActionButton(
                      title: 'Initialize SDK',
                      onPressed: _initializeSdk,
                      enabled: !_sdkInitialized &&
                          _workspaceKeyController.text.isNotEmpty,
                    ),
                  ],
                ),

                _sectionTitle('Events'),
                TextField(
                  controller: _eventNameController,
                  decoration: _inputDecoration('eventName'),
                  onChanged: (_) => setState(() {}),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _propKeyController,
                        decoration: _inputDecoration('key'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                        onChanged: (_) => setState(() {}),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: TextField(
                        controller: _propValueController,
                        decoration: _inputDecoration('value'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                      ),
                    ),
                    const SizedBox(width: 8),
                    _ActionButton(
                      title: 'Add',
                      onPressed: _addEventProp,
                      enabled: _sdkInitialized &&
                          _propKeyController.text.isNotEmpty,
                    ),
                  ],
                ),
                ..._eventProps.entries.map(
                  (e) => Padding(
                    padding: const EdgeInsets.symmetric(vertical: 4),
                    child: Row(
                      children: [
                        Expanded(child: Text('${e.key}: ${e.value}')),
                        _ActionButton(
                          title: 'Remove',
                          onPressed: () => _removeEventProp(e.key),
                          enabled: _sdkInitialized,
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 8),
                _ActionButton(
                  title: 'Invoke Event',
                  onPressed: _invokeEvent,
                  enabled: _sdkInitialized &&
                      _eventNameController.text.isNotEmpty,
                ),

                _sectionTitle('User Traits'),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _traitKeyController,
                        decoration: _inputDecoration('key'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                        onChanged: (_) => setState(() {}),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: TextField(
                        controller: _traitValueController,
                        decoration: _inputDecoration('value'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                      ),
                    ),
                    const SizedBox(width: 8),
                    _ActionButton(
                      title: 'Set',
                      onPressed: _addTrait,
                      enabled: _sdkInitialized &&
                          _traitKeyController.text.isNotEmpty,
                    ),
                  ],
                ),
                ..._traits.map(
                  (t) => Padding(
                    padding: const EdgeInsets.symmetric(vertical: 4),
                    child: Text('${t.key}: ${t.value}'),
                  ),
                ),

                _sectionTitle('Screens'),
                TextField(
                  controller: _screenNameController,
                  decoration: _inputDecoration('screenName'),
                  onChanged: (_) => setState(() {}),
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: _ActionButton(
                        title: 'Enter Screen',
                        onPressed: _enterScreen,
                        enabled: _sdkInitialized &&
                            _screenNameController.text.isNotEmpty,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _ActionButton(
                        title: 'Leave Screen',
                        onPressed: _leaveScreen,
                        enabled: _sdkInitialized &&
                            _screenNameController.text.isNotEmpty,
                      ),
                    ),
                  ],
                ),

                _sectionTitle('Response Attributes'),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _attrNameController,
                        decoration: _inputDecoration('name'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                        onChanged: (_) => setState(() {}),
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: TextField(
                        controller: _attrValueController,
                        decoration: _inputDecoration('value'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _attrProviderController,
                        decoration: _inputDecoration('provider'),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                      ),
                    ),
                    const SizedBox(width: 8),
                    _ActionButton(
                      title: 'Set',
                      onPressed: _addResponseAttr,
                      enabled: _sdkInitialized &&
                          _attrNameController.text.isNotEmpty,
                    ),
                  ],
                ),
                ..._responseAttrs.map(
                  (a) => Padding(
                    padding: const EdgeInsets.symmetric(vertical: 4),
                    child: Text(
                      '${a.name}: ${a.value}'
                      '${a.provider == null ? '' : ' (${a.provider})'}',
                    ),
                  ),
                ),

                _sectionTitle('Theme'),
                Row(
                  children: [
                    Expanded(
                      child: _ActionButton(
                        title: 'Light',
                        onPressed: () => _applyTheme(ThemeMode.light),
                        enabled: _sdkInitialized,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _ActionButton(
                        title: 'Dark',
                        onPressed: () => _applyTheme(ThemeMode.dark),
                        enabled: _sdkInitialized,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _ActionButton(
                        title: 'Auto',
                        onPressed: () => _applyTheme(ThemeMode.auto),
                        enabled: _sdkInitialized,
                      ),
                    ),
                  ],
                ),

                _sectionTitle('Locale'),
                Row(
                  children: [
                    Expanded(
                      child: TextField(
                        controller: _localeController,
                        decoration: _inputDecoration(
                          'languageTag (e.g. en-US)',
                        ),
                        autocorrect: false,
                        textCapitalization: TextCapitalization.none,
                        onChanged: (_) => setState(() {}),
                      ),
                    ),
                    const SizedBox(width: 8),
                    _ActionButton(
                      title: 'Apply',
                      onPressed: _applyLocale,
                      enabled: _sdkInitialized &&
                          _localeController.text.isNotEmpty,
                    ),
                  ],
                ),

                GestureDetector(
                  onTap: () => setState(() => _logVisible = !_logVisible),
                  child: Padding(
                    padding: const EdgeInsets.only(top: 20, bottom: 8),
                    child: Text(
                      '${_logVisible ? '▾' : '▸'} Event Log'
                      '${_log.isNotEmpty ? ' (${_log.length})' : ''}',
                      style: _Styles.sectionTitle,
                    ),
                  ),
                ),
                if (_logVisible) ...[
                  _ActionButton(
                    title: 'Clear log',
                    onPressed: () => setState(() {
                      _log.clear();
                      _expandedIds.clear();
                    }),
                    enabled: _sdkInitialized && _log.isNotEmpty,
                  ),
                  if (_log.isEmpty)
                    const Padding(
                      padding: EdgeInsets.symmetric(vertical: 8),
                      child: Text(
                        'No events yet.',
                        style: TextStyle(
                          fontStyle: FontStyle.italic,
                          color: Color(0xFF999999),
                        ),
                      ),
                    )
                  else
                    ..._log.map((entry) {
                      final expanded = _expandedIds.contains(entry.id);
                      return GestureDetector(
                        onTap: () => _toggleExpanded(entry.id),
                        behavior: HitTestBehavior.opaque,
                        child: Container(
                          padding: const EdgeInsets.symmetric(vertical: 6),
                          decoration: const BoxDecoration(
                            border: Border(
                              bottom: BorderSide(color: Color(0xFFEEEEEE)),
                            ),
                          ),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                '${expanded ? '▾' : '▸'} '
                                '${_formatTime(entry.ts)} · ${entry.type}',
                                style: _Styles.logHeader,
                              ),
                              if (expanded) ...[
                                const SizedBox(height: 4),
                                Text(
                                  _prettyJson(entry.payload),
                                  style: _Styles.logPayload,
                                ),
                              ],
                            ],
                          ),
                        ),
                      );
                    }),
                ],

                _sectionTitle('Reset'),
                _ActionButton(
                  title: 'Reset',
                  onPressed: _resetAll,
                  enabled: _sdkInitialized,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  // --- Builders / formatters ---

  Widget _sectionTitle(String text) => Padding(
        padding: const EdgeInsets.only(top: 20, bottom: 8),
        child: Text(text, style: _Styles.sectionTitle),
      );

  InputDecoration _inputDecoration(String hint) => InputDecoration(
        hintText: hint,
        hintStyle: const TextStyle(color: Color(0xFF999999)),
        isDense: true,
        contentPadding:
            const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
        border: const OutlineInputBorder(
          borderRadius: BorderRadius.all(Radius.circular(4)),
        ),
      );

  static String _formatTime(DateTime ts) {
    String two(int n) => n.toString().padLeft(2, '0');
    return '${two(ts.hour)}:${two(ts.minute)}:${two(ts.second)}';
  }

  static String _prettyJson(Object? payload) {
    try {
      return const JsonEncoder.withIndent('  ').convert(payload);
    } catch (_) {
      return payload.toString();
    }
  }
}

class _ActionButton extends StatelessWidget {
  final String title;
  final VoidCallback onPressed;
  final bool enabled;

  const _ActionButton({
    required this.title,
    required this.onPressed,
    this.enabled = true,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: enabled ? onPressed : null,
      child: Container(
        constraints: const BoxConstraints(minHeight: 36),
        padding:
            const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
        decoration: BoxDecoration(
          color: enabled ? const Color(0xFF0A84FF) : const Color(0xFFD0D0D0),
          borderRadius: BorderRadius.circular(4),
        ),
        alignment: Alignment.center,
        child: Text(
          title,
          textAlign: TextAlign.center,
          style: TextStyle(
            color: enabled ? Colors.white : const Color(0xFF888888),
            fontWeight: FontWeight.w600,
            fontSize: 13,
            letterSpacing: 0.3,
          ),
        ),
      ),
    );
  }
}

class _LogEntry {
  final int id;
  final DateTime ts;
  final String type;
  final Object? payload;

  _LogEntry(this.id, this.ts, this.type, this.payload);
}

class _TraitEntry {
  final int id;
  final String key;
  final String value;

  _TraitEntry(this.id, this.key, this.value);
}

class _AttrEntry {
  final int id;
  final String name;
  final String value;
  final String? provider;

  _AttrEntry(this.id, this.name, this.value, this.provider);
}

class _Styles {
  static const title = TextStyle(
    fontSize: 22,
    fontWeight: FontWeight.w600,
    color: Colors.black,
  );
  static const sectionTitle = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.w600,
    color: Colors.black,
  );
  static const logHeader = TextStyle(
    fontWeight: FontWeight.w600,
    fontSize: 12,
    color: Colors.black,
  );
  static const logPayload = TextStyle(
    fontFamily: 'monospace',
    fontSize: 11,
    color: Color(0xFF333333),
  );
}
5
likes
160
points
7.38k
downloads

Documentation

Documentation
API reference

Publisher

verified publishersurvicate.com

Weekly Downloads

Official plugin for Survicate SDK integration with Flutter. Effortlessly collect user feedback and gain insights to improve your mobile app.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on survicate_sdk

Packages that implement survicate_sdk