datasapien_sdk 0.42.0 copy "datasapien_sdk: ^0.42.0" to clipboard
datasapien_sdk: ^0.42.0 copied to clipboard

Flutter plugin wrapper for DataSapien iOS and Android SDKs

example/lib/main.dart

import 'dart:convert';

import 'package:datasapien_sdk/datasapien_sdk.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:share_plus/share_plus.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await dotenv.load(fileName: '.env');
  runApp(const MyApp());
}

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

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

class _MyAppState extends State<MyApp> {
  String _status = 'Not initialized';
  String? _resultPreview;
  String? _errorMessage;
  bool _isInitialized = false;
  bool _isLoading = false;
  SelectedService _selectedService = SelectedService.backup;

  // Backup state
  String? _backupJson;

  // Intelligence state
  double? _downloadProgress;
  String? _streamingText;

  // Input controllers (kept minimal, but cover all actions)
  final TextEditingController meDataNameController =
      TextEditingController(text: 'step_count');
  final TextEditingController meDataCategoryController =
      TextEditingController(text: 'health');
  final TextEditingController meDataValuesJsonController =
      TextEditingController(text: '1002');
  final TextEditingController meDataCollectNamesController =
      TextEditingController(text: 'step_count,height,weight');

  final TextEditingController audienceSegmentNameController =
      TextEditingController(text: 'ab_testing-a');

  final TextEditingController managedApiNameController =
      TextEditingController(text: 'youtube');

  final TextEditingController journeyNameController =
      TextEditingController(text: 'meal-planner');
  final TextEditingController journeyDataJsonController =
      TextEditingController(text: '{"key": "value"}');

  final TextEditingController intelligenceModelNameController =
      TextEditingController(text: 'SmolLM-135M.Q4_0');
  /// Key identifying the loaded model instance (used for load, unload, invoke, stop).
  final TextEditingController intelligenceModelKeyController =
      TextEditingController(text: 'default');
  final TextEditingController intelligencePromptController =
      TextEditingController(text: 'Hello, how are you?');
  final TextEditingController intelligenceRuleNameController =
      TextEditingController(text: 'sapien_dna');

  @override
  void dispose() {
    meDataNameController.dispose();
    meDataCategoryController.dispose();
    meDataValuesJsonController.dispose();
    meDataCollectNamesController.dispose();
    audienceSegmentNameController.dispose();
    managedApiNameController.dispose();
    journeyNameController.dispose();
    journeyDataJsonController.dispose();
    intelligenceModelNameController.dispose();
    intelligenceModelKeyController.dispose();
    intelligencePromptController.dispose();
    intelligenceRuleNameController.dispose();
    super.dispose();
  }

  void setRunning(String action) {
    setState(() {
      _status = 'Running $action...';
      _errorMessage = null;
    });
  }

  void setSuccess(String action, Object? result) {
    String? preview;
    if (result != null) {
      try {
        const encoder = JsonEncoder.withIndent('  ');
        preview = encoder.convert(result);
      } catch (_) {
        preview = result.toString();
      }
      if (preview.length > 800) {
        preview = '${preview.substring(0, 800)}...';
      }
    }
    setState(() {
      _status = '$action succeeded';
      _resultPreview = preview;
    });
  }

  void setError(String action, Object e, StackTrace stackTrace) {
    final errorMsg = '$e\n\nStack trace:\n$stackTrace';
    debugPrint('❌ $action Error: $e');
    debugPrint('Stack trace: $stackTrace');
    setState(() {
      _status = '$action failed';
      _errorMessage = errorMsg;
    });
  }

  bool ensureInitialized(String actionName) {
    if (_isInitialized) return true;
    const errorMsg = 'SDK must be initialized first';
    debugPrint('❌ $actionName Error: $errorMsg');
    setState(() {
      _errorMessage = errorMsg;
    });
    return false;
  }

  Future<void> _initializeSdk() async {
    if (_isLoading) return;
    
    setState(() {
      _isLoading = true;
      _errorMessage = null;
      _status = 'Initializing...';
    });

    try {
      String envRequired(String key) {
        final value = dotenv.env[key];
        if (value == null || value.trim().isEmpty) {
          throw Exception(
            'Missing $key in sdk/example/.env.\n'
            'Copy sdk/example/.env.example to sdk/example/.env and fill in values.',
          );
        }
        return value.trim();
      }

      final config = DataSapienConfig.builder()
        .setAuth(
          authUrl: envRequired('DATASAPIEN_AUTH_URL'),
          authClientId: envRequired('DATASAPIEN_CLIENT_ID'),
          authClientSecret: envRequired('DATASAPIEN_CLIENT_SECRET'),
          authScope: envRequired('DATASAPIEN_SCOPE'),
        )
        .setHostUrl(envRequired('DATASAPIEN_HOST_URL'))
        .setMediaUrl(envRequired('DATASAPIEN_MEDIA_URL'))
        .setMainColor('#F37102')
        .setDebug(true)
        .build();

      await DataSapien.initialize(config);
      setState(() {
        _status = 'Initialized';
      });

      setState(() {
        _status = 'Setting up...';
      });
      await DataSapien.setup();
      DataSapienDiagnostics.instance
        ..configure(
          const DataSapienDiagnosticsConfig(
            mode: DataSapienDiagnosticsMode.verboseSupport,
          ),
        )
        ..setEnabled(true)
        ..logUiEvent('SDK ready (exportable diagnostics enabled)');
      setState(() {
        _status = 'Ready';
        _isInitialized = true;
        _isLoading = false;
      });
    } catch (e, stackTrace) {
      setState(() {
        _status = 'Error';
        _errorMessage = '${e.toString()}\n\nStack trace:\n$stackTrace';
        _isLoading = false;
      });
    }
  }

  // -------------------------
  // Actions (inlined from services/* to keep pub.dev example self-contained)
  // -------------------------

  Future<void> _createBackup() async {
    if (!ensureInitialized('Create Backup')) return;
    try {
      setRunning('Create Backup');
      final backupService = DataSapien.getBackupService();
      final json = await backupService.createBackup();
      setState(() {
        _backupJson = json;
      });
      setSuccess('Create Backup', json);
    } catch (e, stackTrace) {
      setError('Create Backup', e, stackTrace);
    }
  }

  Future<void> _restoreBackup() async {
    if (!ensureInitialized('Restore Backup')) return;
    if (_backupJson == null) {
      setError(
        'Restore Backup',
        StateError('No backup to restore. Create a backup first.'),
        StackTrace.current,
      );
      return;
    }
    try {
      setRunning('Restore Backup');
      final backupService = DataSapien.getBackupService();
      await backupService.restore(_backupJson!);
      setSuccess('Restore Backup', null);
    } catch (e, stackTrace) {
      setError('Restore Backup', e, stackTrace);
    }
  }

  List<ActionItem> _backupActions() => [
        ActionItem(
          label: 'Create Backup',
          description: 'Calls BackupService.createBackup()',
          onTap: _createBackup,
        ),
        ActionItem(
          label: 'Restore Backup',
          description: 'Calls BackupService.restore() with last backup JSON',
          onTap: _restoreBackup,
        ),
      ];

  Future<void> _runMeDataSyncDefinitions() async {
    if (!ensureInitialized('syncMeDataDefinitions')) return;
    setRunning('syncMeDataDefinitions');
    try {
      final service = DataSapien.getMeDataService();
      await service.syncMeDataDefinitions();
      setSuccess('syncMeDataDefinitions', null);
    } catch (e, stackTrace) {
      setError('syncMeDataDefinitions', e, stackTrace);
    }
  }

  Future<void> _runMeDataGetDefinitions() async {
    if (!ensureInitialized('getMeDataDefinitions')) return;
    setRunning('getMeDataDefinitions');
    try {
      final service = DataSapien.getMeDataService();
      final defs = await service.getMeDataDefinitions();
      setSuccess('getMeDataDefinitions', defs);
    } catch (e, stackTrace) {
      setError('getMeDataDefinitions', e, stackTrace);
    }
  }

  Future<void> _runMeDataGetDefinition() async {
    if (!ensureInitialized('getMeDataDefinition')) return;
    setRunning('getMeDataDefinition');
    try {
      final name = meDataNameController.text.trim();
      final service = DataSapien.getMeDataService();
      final def = await service.getMeDataDefinition(name);
      setSuccess('getMeDataDefinition', def);
    } catch (e, stackTrace) {
      setError('getMeDataDefinition', e, stackTrace);
    }
  }

  Future<void> _runMeDataGetCategories() async {
    if (!ensureInitialized('getMeDataCategories')) return;
    setRunning('getMeDataCategories');
    try {
      final service = DataSapien.getMeDataService();
      final categories = await service.getMeDataCategories();
      setSuccess('getMeDataCategories', categories);
    } catch (e, stackTrace) {
      setError('getMeDataCategories', e, stackTrace);
    }
  }

  Future<void> _runMeDataGetDefinitionsByCategory() async {
    if (!ensureInitialized('getMeDataDefinitionsByCategory')) return;
    setRunning('getMeDataDefinitionsByCategory');
    try {
      final name = meDataCategoryController.text.trim();
      final service = DataSapien.getMeDataService();
      final defs = await service.getMeDataDefinitionsByCategory(name);
      setSuccess('getMeDataDefinitionsByCategory', defs);
    } catch (e, stackTrace) {
      setError('getMeDataDefinitionsByCategory', e, stackTrace);
    }
  }

  Future<void> _runMeDataGetRecords() async {
    if (!ensureInitialized('getMeDataRecords')) return;
    setRunning('getMeDataRecords');
    try {
      final name = meDataNameController.text.trim();
      final service = DataSapien.getMeDataService();
      final records = await service.getMeDataRecords(name);
      setSuccess('getMeDataRecords', records);
    } catch (e, stackTrace) {
      setError('getMeDataRecords', e, stackTrace);
    }
  }

  Future<void> _runMeDataGetLastRecord() async {
    if (!ensureInitialized('getLastMeDataRecord')) return;
    setRunning('getLastMeDataRecord');
    try {
      final name = meDataNameController.text.trim();
      final service = DataSapien.getMeDataService();
      final record = await service.getLastMeDataRecord(name);
      setSuccess('getLastMeDataRecord', record);
    } catch (e, stackTrace) {
      setError('getLastMeDataRecord', e, stackTrace);
    }
  }

  Future<void> _runMeDataSaveRecord() async {
    if (!ensureInitialized('saveMeDataRecord')) return;
    setRunning('saveMeDataRecord');
    try {
      final name = meDataNameController.text.trim();
      final raw = meDataValuesJsonController.text.trim();
      dynamic values;
      if (raw.isEmpty) {
        values = null;
      } else {
        values = jsonDecode(raw);
      }
      final service = DataSapien.getMeDataService();
      await service.saveMeDataRecord(name, values);
      setSuccess('saveMeDataRecord', null);
    } catch (e, stackTrace) {
      setError('saveMeDataRecord', e, stackTrace);
    }
  }

  Future<void> _runMeDataSaveLocationRecord() async {
    if (!ensureInitialized('saveLocationMeDataRecord')) return;
    setRunning('saveLocationMeDataRecord');
    try {
      final name = meDataNameController.text.trim();
      final raw = meDataValuesJsonController.text.trim();
      Location? location;

      if (raw.isNotEmpty) {
        final decoded = jsonDecode(raw);
        location = Location.tryParse(decoded);
        if (location == null) {
          throw const FormatException(
            'Expected JSON object with numeric lat/lng, e.g. {"lat":41.0,"lng":29.0}',
          );
        }
      }

      final service = DataSapien.getMeDataService();
      await service.saveLocationMeDataRecord(name, location);
      setSuccess('saveLocationMeDataRecord', location?.toJson());
    } catch (e, stackTrace) {
      setError('saveLocationMeDataRecord', e, stackTrace);
    }
  }

  Future<void> _runMeDataCollect() async {
    if (!ensureInitialized('collectMeData')) return;
    setRunning('collectMeData');
    try {
      final raw = meDataCollectNamesController.text;
      final names = raw
          .split(',')
          .map((s) => s.trim())
          .where((s) => s.isNotEmpty)
          .toList();
      final service = DataSapien.getMeDataService();
      final collected = await service.collectMeData(names);
      setSuccess('collectMeData', collected);
    } catch (e, stackTrace) {
      setError('collectMeData', e, stackTrace);
    }
  }

  List<ActionItem> _meDataActions() => [
        ActionItem(
          label: 'syncMeDataDefinitions',
          description: 'Synchronize MeData definitions from Orchestrator',
          onTap: _runMeDataSyncDefinitions,
        ),
        ActionItem(
          label: 'getMeDataDefinitions',
          description: 'Fetch all MeData definitions',
          onTap: _runMeDataGetDefinitions,
        ),
        ActionItem(
          label: 'getMeDataDefinition',
          description: 'Fetch a single MeData definition by name',
          onTap: _runMeDataGetDefinition,
        ),
        ActionItem(
          label: 'getMeDataCategories',
          description: 'Fetch MeData categories',
          onTap: _runMeDataGetCategories,
        ),
        ActionItem(
          label: 'getMeDataDefinitionsByCategory',
          description: 'Fetch definitions by category name',
          onTap: _runMeDataGetDefinitionsByCategory,
        ),
        ActionItem(
          label: 'getMeDataRecords',
          description: 'Fetch all records for a MeData definition',
          onTap: _runMeDataGetRecords,
        ),
        ActionItem(
          label: 'getLastMeDataRecord',
          description: 'Fetch last record for a MeData definition',
          onTap: _runMeDataGetLastRecord,
        ),
        ActionItem(
          label: 'saveMeDataRecord',
          description: 'Save a new MeData record (name + values)',
          onTap: _runMeDataSaveRecord,
        ),
        ActionItem(
          label: 'saveLocationMeDataRecord',
          description: 'Save LOCATION MeData (JSON: {"lat":..,"lng":..})',
          onTap: _runMeDataSaveLocationRecord,
        ),
        ActionItem(
          label: 'collectMeData',
          description: 'Collect MeData for a list of definition names',
          onTap: _runMeDataCollect,
        ),
      ];

  Future<void> _runAudienceSyncSegmentDefinitions() async {
    if (!ensureInitialized('syncSegmentDefinitions')) return;
    setRunning('syncSegmentDefinitions');
    try {
      final service = DataSapien.getAudienceService();
      await service.syncSegmentDefinitions();
      setSuccess('syncSegmentDefinitions', null);
    } catch (e, stackTrace) {
      setError('syncSegmentDefinitions', e, stackTrace);
    }
  }

  Future<void> _runAudienceGetSegmentDefinitions() async {
    if (!ensureInitialized('getSegmentDefinitions')) return;
    setRunning('getSegmentDefinitions');
    try {
      final service = DataSapien.getAudienceService();
      final defs = await service.getSegmentDefinitions();
      setSuccess('getSegmentDefinitions', defs.map((e) => e.toJson()).toList());
    } catch (e, stackTrace) {
      setError('getSegmentDefinitions', e, stackTrace);
    }
  }

  Future<void> _runAudienceGetSegmentDefinition() async {
    if (!ensureInitialized('getSegmentDefinition')) return;
    setRunning('getSegmentDefinition');
    try {
      final name = audienceSegmentNameController.text.trim();
      final service = DataSapien.getAudienceService();
      final def = await service.getSegmentDefinition(name);
      setSuccess('getSegmentDefinition', def?.toJson());
    } catch (e, stackTrace) {
      setError('getSegmentDefinition', e, stackTrace);
    }
  }

  Future<void> _runAudienceSyncSegmentSubscriptions() async {
    if (!ensureInitialized('syncSegmentSubscriptions')) return;
    setRunning('syncSegmentSubscriptions');
    try {
      final service = DataSapien.getAudienceService();
      await service.syncSegmentSubscriptions();
      setSuccess('syncSegmentSubscriptions', null);
    } catch (e, stackTrace) {
      setError('syncSegmentSubscriptions', e, stackTrace);
    }
  }

  Future<void> _runAudienceSyncAudiences() async {
    if (!ensureInitialized('syncAudiences')) return;
    setRunning('syncAudiences');
    try {
      final service = DataSapien.getAudienceService();
      await service.syncAudiences();
      setSuccess('syncAudiences', null);
    } catch (e, stackTrace) {
      setError('syncAudiences', e, stackTrace);
    }
  }

  List<ActionItem> _audienceActions() => [
        ActionItem(
          label: 'syncSegmentDefinitions',
          description: 'Synchronize segment definitions from Orchestrator',
          onTap: _runAudienceSyncSegmentDefinitions,
        ),
        ActionItem(
          label: 'getSegmentDefinitions',
          description: 'Fetch all segment definitions',
          onTap: _runAudienceGetSegmentDefinitions,
        ),
        ActionItem(
          label: 'getSegmentDefinition',
          description: 'Fetch a single segment definition by name',
          onTap: _runAudienceGetSegmentDefinition,
        ),
        ActionItem(
          label: 'syncSegmentSubscriptions',
          description: 'Synchronize segment subscriptions with backend',
          onTap: _runAudienceSyncSegmentSubscriptions,
        ),
        ActionItem(
          label: 'syncAudiences',
          description: 'Synchronize audiences from Orchestrator',
          onTap: _runAudienceSyncAudiences,
        ),
      ];

  Future<void> _runManagedApiSync() async {
    if (!ensureInitialized('syncManagedAPIs')) return;
    setRunning('syncManagedAPIs');
    try {
      final service = DataSapien.getManagedAPIService();
      await service.syncManagedAPIs();
      setSuccess('syncManagedAPIs', null);
    } catch (e, stackTrace) {
      setError('syncManagedAPIs', e, stackTrace);
    }
  }

  Future<void> _runManagedApiGetList() async {
    if (!ensureInitialized('getManagedAPIs')) return;
    setRunning('getManagedAPIs');
    try {
      final service = DataSapien.getManagedAPIService();
      final apis = await service.getManagedAPIs();
      setSuccess('getManagedAPIs', apis.map((e) => e.toJson()).toList());
    } catch (e, stackTrace) {
      setError('getManagedAPIs', e, stackTrace);
    }
  }

  Future<void> _runManagedApiGetOne() async {
    if (!ensureInitialized('getManagedAPI')) return;
    setRunning('getManagedAPI');
    try {
      final name = managedApiNameController.text.trim();
      final service = DataSapien.getManagedAPIService();
      final api = await service.getManagedAPI(name);
      setSuccess('getManagedAPI', api?.toJson());
    } catch (e, stackTrace) {
      setError('getManagedAPI', e, stackTrace);
    }
  }

  List<ActionItem> _managedApiActions() => [
        ActionItem(
          label: 'syncManagedAPIs',
          description: 'Synchronize managed APIs from backend',
          onTap: _runManagedApiSync,
        ),
        ActionItem(
          label: 'getManagedAPIs',
          description: 'Fetch all available managed APIs',
          onTap: _runManagedApiGetList,
        ),
        ActionItem(
          label: 'getManagedAPI',
          description: 'Fetch a single managed API by name',
          onTap: _runManagedApiGetOne,
        ),
      ];

  Future<void> _runJourneySync() async {
    if (!ensureInitialized('syncJourneys')) return;
    setRunning('syncJourneys');
    try {
      final service = DataSapien.getJourneyService();
      await service.syncJourneys();
      setSuccess('syncJourneys', null);
    } catch (e, stackTrace) {
      setError('syncJourneys', e, stackTrace);
    }
  }

  Future<void> _runJourneyGetList() async {
    if (!ensureInitialized('getJourneys')) return;
    setRunning('getJourneys');
    try {
      final service = DataSapien.getJourneyService();
      final journeys = await service.getJourneys();
      setSuccess('getJourneys', journeys.map((e) => e.name).toList());
    } catch (e, stackTrace) {
      setError('getJourneys', e, stackTrace);
    }
  }

  Future<void> _runJourneyRun() async {
    if (!ensureInitialized('runJourney')) return;
    setRunning('runJourney');
    try {
      final name = journeyNameController.text.trim();
      final raw = journeyDataJsonController.text.trim();
      Map<String, dynamic>? data;
      if (raw.isNotEmpty) {
        data = jsonDecode(raw) as Map<String, dynamic>;
      }
      final service = DataSapien.getJourneyService();
      final result = await service.runJourney(name, data: data);
      setSuccess('runJourney', result);
    } catch (e, stackTrace) {
      setError('runJourney', e, stackTrace);
    }
  }

  Future<void> _runJourneyGetStatus() async {
    if (!ensureInitialized('getJourneyStatus')) return;
    setRunning('getJourneyStatus');
    try {
      final name = journeyNameController.text.trim();
      final service = DataSapien.getJourneyService();
      final status = await service.getJourneyStatus(name);
      setSuccess('getJourneyStatus', status.toString());
    } catch (e, stackTrace) {
      setError('getJourneyStatus', e, stackTrace);
    }
  }

  List<ActionItem> _journeyActions() => [
        ActionItem(
          label: 'syncJourneys',
          description: 'Synchronize journeys from Orchestrator',
          onTap: _runJourneySync,
        ),
        ActionItem(
          label: 'getJourneys',
          description: 'Fetch all available journeys',
          onTap: _runJourneyGetList,
        ),
        ActionItem(
          label: 'runJourney',
          description: 'Run a specific journey by name',
          onTap: _runJourneyRun,
        ),
        ActionItem(
          label: 'getJourneyStatus',
          description: 'Get current status of a journey',
          onTap: _runJourneyGetStatus,
        ),
      ];

  Future<void> _runIntelligenceLoadModel() async {
    if (!ensureInitialized('loadModel')) return;
    setRunning('loadModel');
    try {
      final name = intelligenceModelNameController.text.trim();
      final key = intelligenceModelKeyController.text.trim();
      DataSapienDiagnostics.instance.logModelPickerSelected(displayName: name);
      final service = DataSapien.getIntelligenceService();
      await service.loadModel(name, key);
      DataSapienDiagnostics.instance.logUiEvent('$name loaded');
      setSuccess('loadModel', null);
    } catch (e, stackTrace) {
      setError('loadModel', e, stackTrace);
    }
  }

  Future<void> _exportDiagnostics() async {
    final text = DataSapienDiagnostics.instance.exportAsPlainText();
    await Share.share(text, subject: 'DataSapien diagnostics');
  }

  Future<void> _runIntelligenceUnloadModel() async {
    if (!ensureInitialized('unloadModel')) return;
    setRunning('unloadModel');
    try {
      final key = intelligenceModelKeyController.text.trim();
      final service = DataSapien.getIntelligenceService();
      await service.unloadModel(key: key.isEmpty ? null : key);
      setSuccess('unloadModel', null);
    } catch (e, stackTrace) {
      setError('unloadModel', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceDownloadModel() async {
    if (!ensureInitialized('downloadModelFiles')) return;
    setRunning('downloadModelFiles');

    setState(() {
      _downloadProgress = 0.0;
    });

    try {
      final name = intelligenceModelNameController.text.trim();
      final service = DataSapien.getIntelligenceService();

      await service.downloadModelFiles(
        name,
        onProgress: (progress) {
          setState(() {
            _downloadProgress = progress;
          });
        },
      );

      setSuccess('downloadModelFiles', 'Download complete');
    } catch (e, stackTrace) {
      setError('downloadModelFiles', e, stackTrace);
    } finally {
      setState(() {
        _downloadProgress = null;
      });
    }
  }

  Future<void> _runIntelligenceDeleteModel() async {
    if (!ensureInitialized('deleteModelFiles')) return;
    setRunning('deleteModelFiles');
    try {
      final name = intelligenceModelNameController.text.trim();
      final service = DataSapien.getIntelligenceService();
      await service.deleteModelFiles(name);
      setSuccess('deleteModelFiles', null);
    } catch (e, stackTrace) {
      setError('deleteModelFiles', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceIsModelDownloaded() async {
    if (!ensureInitialized('isModelFilesDownloaded')) return;
    setRunning('isModelFilesDownloaded');
    try {
      final name = intelligenceModelNameController.text.trim();
      final service = DataSapien.getIntelligenceService();
      final isDownloaded = await service.isModelFilesDownloaded(name);
      setSuccess('isModelFilesDownloaded', isDownloaded);
    } catch (e, stackTrace) {
      setError('isModelFilesDownloaded', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceGetDownloadedModels() async {
    if (!ensureInitialized('getDownloadedModelsList')) return;
    setRunning('getDownloadedModelsList');
    try {
      final service = DataSapien.getIntelligenceService();
      final models = await service.getDownloadedModelsList();
      setSuccess('getDownloadedModelsList', models);
    } catch (e, stackTrace) {
      setError('getDownloadedModelsList', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceGetLoadedModels() async {
    if (!ensureInitialized('getLoadedModels')) return;
    setRunning('getLoadedModels');
    try {
      final key = intelligenceModelKeyController.text.trim();
      final service = DataSapien.getIntelligenceService();
      final isLoaded = await service.isModelLoaded(key);
      setSuccess('getLoadedModels', {'key': key, 'isLoaded': isLoaded});
    } catch (e, stackTrace) {
      setError('getLoadedModels', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceStopModelInference() async {
    if (!ensureInitialized('stopModelInference')) return;
    setRunning('stopModelInference');
    try {
      final key = intelligenceModelKeyController.text.trim();
      final service = DataSapien.getIntelligenceService();
      await service.stopModelInference(key);
      setSuccess('stopModelInference', null);
    } catch (e, stackTrace) {
      setError('stopModelInference', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceInvokeModel() async {
    if (!ensureInitialized('invokeModel')) return;
    setRunning('invokeModel');

    setState(() {
      _streamingText = '';
    });

    try {
      final key = intelligenceModelKeyController.text.trim();
      final promptText = intelligencePromptController.text.trim();
      final service = DataSapien.getIntelligenceService();
      final prompts = [
        Prompt(role: PromptRole.user, content: promptText),
      ];

      final result = await service.invokeModel(
        key,
        prompts,
        onStream: (chunk) {
          setState(() {
            _streamingText = (_streamingText ?? '') + chunk;
          });
        },
      );

      setSuccess('invokeModel', result);
    } catch (e, stackTrace) {
      setError('invokeModel', e, stackTrace);
    } finally {
      setState(() {
        _streamingText = null;
      });
    }
  }

  Future<void> _runIntelligenceSyncRules() async {
    if (!ensureInitialized('syncRules')) return;
    setRunning('syncRules');
    try {
      final service = DataSapien.getIntelligenceService();
      await service.syncRules();
      setSuccess('syncRules', null);
    } catch (e, stackTrace) {
      setError('syncRules', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceGetRules() async {
    if (!ensureInitialized('getRules')) return;
    setRunning('getRules');
    try {
      final service = DataSapien.getIntelligenceService();
      final rules = await service.getRules();
      setSuccess('getRules', rules.map((r) => r.toJson()).toList());
    } catch (e, stackTrace) {
      setError('getRules', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceGetRule() async {
    if (!ensureInitialized('getRule')) return;
    setRunning('getRule');
    try {
      final name = intelligenceRuleNameController.text.trim();
      final service = DataSapien.getIntelligenceService();
      final rule = await service.getRule(name);
      setSuccess('getRule', rule?.toJson());
    } catch (e, stackTrace) {
      setError('getRule', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceRunRule() async {
    if (!ensureInitialized('runRule')) return;
    setRunning('runRule');
    try {
      final name = intelligenceRuleNameController.text.trim();
      final service = DataSapien.getIntelligenceService();
      final rule = await service.runRule(name);
      setSuccess('runRule', rule?.toJson());
    } catch (e, stackTrace) {
      setError('runRule', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceRunRules() async {
    if (!ensureInitialized('runRules')) return;
    setRunning('runRules');
    try {
      final service = DataSapien.getIntelligenceService();
      final rules = await service.runRules();
      setSuccess('runRules', rules.map((r) => r.toJson()).toList());
    } catch (e, stackTrace) {
      setError('runRules', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceSyncManagedAIModels() async {
    if (!ensureInitialized('syncManagedAIModels')) return;
    setRunning('syncManagedAIModels');
    try {
      final service = DataSapien.getIntelligenceService();
      await service.syncManagedAIModels();
      setSuccess('syncManagedAIModels', null);
    } catch (e, stackTrace) {
      setError('syncManagedAIModels', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceGetManagedAIModels() async {
    if (!ensureInitialized('getManagedAIModels')) return;
    setRunning('getManagedAIModels');
    try {
      final service = DataSapien.getIntelligenceService();
      final models = await service.getManagedAIModels();
      setSuccess('getManagedAIModels', models.map((m) => m.toJson()).toList());
    } catch (e, stackTrace) {
      setError('getManagedAIModels', e, stackTrace);
    }
  }

  Future<void> _runIntelligenceGetManagedAIModel() async {
    if (!ensureInitialized('getManagedAIModel')) return;
    setRunning('getManagedAIModel');
    try {
      final name = intelligenceModelNameController.text.trim();
      final service = DataSapien.getIntelligenceService();
      final model = await service.getManagedAIModel(name);
      setSuccess('getManagedAIModel', model?.toJson());
    } catch (e, stackTrace) {
      setError('getManagedAIModel', e, stackTrace);
    }
  }

  List<ActionItem> _intelligenceActions() => [
        ActionItem(
          label: 'syncManagedAIModels',
          description: 'Synchronize managed AI models from backend',
          onTap: _runIntelligenceSyncManagedAIModels,
        ),
        ActionItem(
          label: 'getManagedAIModels',
          description: 'Fetch all managed AI models',
          onTap: _runIntelligenceGetManagedAIModels,
        ),
        ActionItem(
          label: 'getManagedAIModel',
          description: 'Fetch a single managed AI model by name',
          onTap: _runIntelligenceGetManagedAIModel,
        ),
        ActionItem(
          label: 'loadModel',
          description: 'Load a model for inference',
          onTap: _runIntelligenceLoadModel,
        ),
        ActionItem(
          label: 'unloadModel',
          description:
              'Unload model for the instance key (empty key unloads all tracked keys)',
          onTap: _runIntelligenceUnloadModel,
        ),
        ActionItem(
          label: 'downloadModelFiles',
          description: 'Download model with progress (shows progress bar)',
          onTap: _runIntelligenceDownloadModel,
        ),
        ActionItem(
          label: 'deleteModelFiles',
          description: 'Delete downloaded model files',
          onTap: _runIntelligenceDeleteModel,
        ),
        ActionItem(
          label: 'isModelFilesDownloaded',
          description: 'Check if model files are downloaded',
          onTap: _runIntelligenceIsModelDownloaded,
        ),
        ActionItem(
          label: 'getDownloadedModelsList',
          description: 'Get list of downloaded models',
          onTap: _runIntelligenceGetDownloadedModels,
        ),
        ActionItem(
          label: 'getLoadedModels',
          description: 'Get keys of models currently loaded in memory',
          onTap: _runIntelligenceGetLoadedModels,
        ),
        ActionItem(
          label: 'stopModelInference',
          description: 'Stop inference for the model key in inputs',
          onTap: _runIntelligenceStopModelInference,
        ),
        ActionItem(
          label: 'invokeModel',
          description: 'Invoke model with streaming (shows text in real-time)',
          onTap: _runIntelligenceInvokeModel,
        ),
        ActionItem(
          label: 'syncRules',
          description: 'Synchronize rules from backend',
          onTap: _runIntelligenceSyncRules,
        ),
        ActionItem(
          label: 'getRules',
          description: 'Fetch all rules',
          onTap: _runIntelligenceGetRules,
        ),
        ActionItem(
          label: 'getRule',
          description: 'Fetch a single rule by name',
          onTap: _runIntelligenceGetRule,
        ),
        ActionItem(
          label: 'runRule',
          description: 'Run a specific rule',
          onTap: _runIntelligenceRunRule,
        ),
        ActionItem(
          label: 'runRules',
          description: 'Run all rules',
          onTap: _runIntelligenceRunRules,
        ),
      ];

  List<ActionItem> _currentActions() {
    switch (_selectedService) {
      case SelectedService.backup:
        return _backupActions();
      case SelectedService.meData:
        return _meDataActions();
      case SelectedService.audience:
        return _audienceActions();
      case SelectedService.managedApi:
        return _managedApiActions();
      case SelectedService.journey:
        return _journeyActions();
      case SelectedService.intelligence:
        return _intelligenceActions();
      case SelectedService.other:
        return const [];
    }
  }

  String _getServiceTitle() {
    switch (_selectedService) {
      case SelectedService.backup:
        return 'BackupService Actions';
      case SelectedService.meData:
        return 'MeDataService Actions';
      case SelectedService.audience:
        return 'AudienceService Actions';
      case SelectedService.managedApi:
        return 'ManagedAPIService Actions';
      case SelectedService.journey:
        return 'JourneyService Actions';
      case SelectedService.intelligence:
        return 'IntelligenceService Actions';
      case SelectedService.other:
        return 'Other Service Actions';
    }
  }

  Widget? _getServiceInputForm() {
    switch (_selectedService) {
      case SelectedService.meData:
        return MeDataInputForm(
          nameController: meDataNameController,
          categoryController: meDataCategoryController,
          valuesJsonController: meDataValuesJsonController,
          collectNamesController: meDataCollectNamesController,
        );
      case SelectedService.audience:
        return AudienceInputForm(
          segmentNameController: audienceSegmentNameController,
        );
      case SelectedService.managedApi:
        return ManagedApiInputForm(
          nameController: managedApiNameController,
        );
      case SelectedService.journey:
        return JourneyInputForm(
          nameController: journeyNameController,
          dataJsonController: journeyDataJsonController,
        );
      case SelectedService.intelligence:
        return IntelligenceInputForm(
          modelNameController: intelligenceModelNameController,
          modelKeyController: intelligenceModelKeyController,
          promptController: intelligencePromptController,
          ruleNameController: intelligenceRuleNameController,
        );
      default:
        return null;
    }
  }

  double? get downloadProgress => _downloadProgress;
  String? get streamingText => _streamingText;
  String? get backupJson => _backupJson;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('DataSapien SDK Example'),
          actions: [
            IconButton(
              tooltip: 'Export diagnostics log (.txt via share sheet)',
              icon: const Icon(Icons.description_outlined),
              onPressed: _exportDiagnostics,
            ),
          ],
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                // SDK Status Card
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const Text(
                          'SDK Status',
                          style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 8),
                        Text(_status),
                        const SizedBox(height: 12),
                        if (!_isInitialized)
                          ElevatedButton(
                            onPressed: _isLoading ? null : _initializeSdk,
                            child: Text(
                              _isLoading ? 'Initializing...' : 'Initialize SDK',
                            ),
                          ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 12),

                // Service Selector
                Row(
                  children: [
                    const Text(
                      'Selected service:',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                    const SizedBox(width: 8),
                    DropdownButton<SelectedService>(
                      value: _selectedService,
                      onChanged: (value) {
                        if (value == null) return;
                        setState(() {
                          _selectedService = value;
                        });
                      },
                      items: const [
                        DropdownMenuItem(
                          value: SelectedService.backup,
                          child: Text('BackupService'),
                        ),
                        DropdownMenuItem(
                          value: SelectedService.meData,
                          child: Text('MeDataService'),
                        ),
                        DropdownMenuItem(
                          value: SelectedService.audience,
                          child: Text('AudienceService'),
                        ),
                        DropdownMenuItem(
                          value: SelectedService.managedApi,
                          child: Text('ManagedAPIService'),
                        ),
                        DropdownMenuItem(
                          value: SelectedService.journey,
                          child: Text('JourneyService'),
                        ),
                        DropdownMenuItem(
                          value: SelectedService.intelligence,
                          child: Text('IntelligenceService'),
                        ),
                        DropdownMenuItem(
                          value: SelectedService.other,
                          child: Text('Other (placeholder)'),
                        ),
                      ],
                    ),
                  ],
                ),
                if (!_isInitialized)
                  const Padding(
                    padding: EdgeInsets.only(top: 4.0, bottom: 8.0),
                    child: Text(
                      'Initialize SDK first to enable service actions.',
                      style: TextStyle(fontSize: 12, color: Colors.black54),
                    ),
                  ),
                const SizedBox(height: 8),

                // Service Actions Section
                ActionSection(
                  title: _getServiceTitle(),
                  actions: _currentActions(),
                  isInitialized: _isInitialized,
                  extraControls: _getServiceInputForm(),
                ),

                // Download Progress Indicator
                if (downloadProgress != null) ...[ 
                  const SizedBox(height: 12),
                  Card(
                    color: Colors.blue.shade50,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            'Download Progress: ${(downloadProgress! * 100).toStringAsFixed(1)}%',
                            style: const TextStyle(
                              fontSize: 14,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(height: 8),
                          LinearProgressIndicator(value: downloadProgress),
                        ],
                      ),
                    ),
                  ),
                ],

                // Streaming Text Display
                if (streamingText != null) ...[
                  const SizedBox(height: 12),
                  Card(
                    color: Colors.green.shade50,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Streaming Response',
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(height: 8),
                          Text(
                            streamingText!,
                            style: const TextStyle(fontSize: 12),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],

                // Error Display
                if (_errorMessage != null) ...[
                  const SizedBox(height: 12),
                  Card(
                    color: Colors.red.shade50,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Error',
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                              color: Colors.red,
                            ),
                          ),
                          const SizedBox(height: 8),
                          Text(
                            _errorMessage!,
                            style: const TextStyle(
                              fontSize: 12,
                              color: Colors.red,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],

                // Result Preview
                if (_resultPreview != null) ...[
                  const SizedBox(height: 12),
                  Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Last result',
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(height: 8),
                          Text(
                            _resultPreview!,
                            style: const TextStyle(fontSize: 12),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],

                // Backup JSON Display
                if (backupJson != null) ...[
                  const SizedBox(height: 12),
                  Card(
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Backup JSON (first 200 chars)',
                            style: TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          const SizedBox(height: 8),
                          Text(
                            backupJson!.length > 200
                                ? '${backupJson!.substring(0, 200)}...'
                                : backupJson!,
                            style: const TextStyle(fontSize: 12),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ],
            ),
          ),
        ),
      ),
    );
  }
}

// -------------------------
// Minimal inlined types/widgets (so pub.dev single-file Example is complete)
// -------------------------

enum SelectedService {
  backup,
  meData,
  audience,
  managedApi,
  journey,
  intelligence,
  other,
}

class ActionItem {
  final String label;
  final String? description;
  final Future<void> Function() onTap;

  const ActionItem({
    required this.label,
    required this.onTap,
    this.description,
  });
}

class ActionSection extends StatelessWidget {
  final String title;
  final List<ActionItem> actions;
  final Widget? extraControls;
  final bool isInitialized;

  const ActionSection({
    super.key,
    required this.title,
    required this.actions,
    required this.isInitialized,
    this.extraControls,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 12),
            if (extraControls != null) ...[
              extraControls!,
              const SizedBox(height: 12),
            ],
            for (final action in actions) ...[
              ElevatedButton(
                onPressed: !isInitialized ? null : action.onTap,
                child: Text(action.label),
              ),
              if (action.description != null) ...[
                const SizedBox(height: 4),
                Text(
                  action.description!,
                  style: const TextStyle(fontSize: 12, color: Colors.black54),
                ),
              ],
              const SizedBox(height: 8),
            ],
          ],
        ),
      ),
    );
  }
}

class MeDataInputForm extends StatelessWidget {
  final TextEditingController nameController;
  final TextEditingController categoryController;
  final TextEditingController valuesJsonController;
  final TextEditingController collectNamesController;

  const MeDataInputForm({
    super.key,
    required this.nameController,
    required this.categoryController,
    required this.valuesJsonController,
    required this.collectNamesController,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'MeData inputs',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: nameController,
          decoration: const InputDecoration(
            labelText: 'MeData name',
            hintText: 'e.g. daily_steps',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: categoryController,
          decoration: const InputDecoration(
            labelText: 'Category name',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: valuesJsonController,
          decoration: const InputDecoration(
            labelText: 'Values (JSON)',
            hintText: r'[12345, \"2025-10-04\"]',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: collectNamesController,
          decoration: const InputDecoration(
            labelText: 'Collect names (comma separated)',
            hintText: 'daily_steps,height,weight',
          ),
        ),
      ],
    );
  }
}

class AudienceInputForm extends StatelessWidget {
  final TextEditingController segmentNameController;

  const AudienceInputForm({
    super.key,
    required this.segmentNameController,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Audience inputs',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: segmentNameController,
          decoration: const InputDecoration(
            labelText: 'Segment name',
            hintText: 'e.g. ab_testing-a',
          ),
        ),
      ],
    );
  }
}

class ManagedApiInputForm extends StatelessWidget {
  final TextEditingController nameController;

  const ManagedApiInputForm({
    super.key,
    required this.nameController,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'ManagedAPI inputs',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: nameController,
          decoration: const InputDecoration(
            labelText: 'ManagedAPI name',
            hintText: 'e.g. google_fit',
          ),
        ),
        const SizedBox(height: 8),
      ],
    );
  }
}

class JourneyInputForm extends StatelessWidget {
  final TextEditingController nameController;
  final TextEditingController dataJsonController;

  const JourneyInputForm({
    super.key,
    required this.nameController,
    required this.dataJsonController,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Journey inputs',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: nameController,
          decoration: const InputDecoration(
            labelText: 'Journey name',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: dataJsonController,
          decoration: const InputDecoration(
            labelText: 'Context Data (JSON)',
          ),
        ),
        const SizedBox(height: 8),
      ],
    );
  }
}

class IntelligenceInputForm extends StatelessWidget {
  final TextEditingController modelNameController;
  final TextEditingController modelKeyController;
  final TextEditingController promptController;
  final TextEditingController ruleNameController;

  const IntelligenceInputForm({
    super.key,
    required this.modelNameController,
    required this.modelKeyController,
    required this.promptController,
    required this.ruleNameController,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Intelligence inputs',
          style: TextStyle(fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: modelNameController,
          decoration: const InputDecoration(
            labelText: 'Model name (catalog)',
            hintText: 'e.g. test_model',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: modelKeyController,
          decoration: const InputDecoration(
            labelText: 'Model instance key',
            hintText: 'e.g. default',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: promptController,
          decoration: const InputDecoration(
            labelText: 'Prompt text',
            hintText: 'Hello, how are you?',
          ),
        ),
        const SizedBox(height: 8),
        TextField(
          controller: ruleNameController,
          decoration: const InputDecoration(
            labelText: 'Rule name',
            hintText: 'e.g. test_rule',
          ),
        ),
        const SizedBox(height: 8),
      ],
    );
  }
}
0
likes
145
points
340
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter plugin wrapper for DataSapien iOS and Android SDKs

Homepage

License

unknown (license)

Dependencies

flutter

More

Packages that depend on datasapien_sdk

Packages that implement datasapien_sdk