datasapien_sdk 0.42.0
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),
],
);
}
}