From 58bb484cd2561d276d75653fe8ab81f199ee7a91 Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 3 Feb 2026 19:26:29 +0800 Subject: [PATCH] refactor: remove clientType, translate example app, add response logging --- ...ter 埋点 SDK 设计方案(独立 Dart 实现).md | 10 +- example/lib/main.dart | 738 +----------------- example/lib/service/analytics_service.dart | 52 ++ example/lib/view/demo_app.dart | 21 + example/lib/view/demo_page.dart | 261 +++++++ example/lib/view/widgets.dart | 101 +++ example/lib/view_model/demo_view_model.dart | 376 +++++++++ example/test/widget_test.dart | 15 - lib/src/config/analytics_config.dart | 13 +- lib/src/network/api_client.dart | 3 +- test/analytics_config_extra_test.dart | 23 +- test/analytics_core_test.dart | 4 - test/api_client_test.dart | 1 - test/config_manager_test.dart | 1 - test/facade_test.dart | 2 - test/http_client_test.dart | 1 - test/validator_test.dart | 1 - test/yx_tracking_flutter_test.dart | 2 - 18 files changed, 837 insertions(+), 788 deletions(-) create mode 100644 example/lib/service/analytics_service.dart create mode 100644 example/lib/view/demo_app.dart create mode 100644 example/lib/view/demo_page.dart create mode 100644 example/lib/view/widgets.dart create mode 100644 example/lib/view_model/demo_view_model.dart delete mode 100644 example/test/widget_test.dart diff --git a/2.Flutter 埋点 SDK 设计方案(独立 Dart 实现).md b/2.Flutter 埋点 SDK 设计方案(独立 Dart 实现).md index 484d8a0..e23f145 100644 --- a/2.Flutter 埋点 SDK 设计方案(独立 Dart 实现).md +++ b/2.Flutter 埋点 SDK 设计方案(独立 Dart 实现).md @@ -96,13 +96,13 @@ class AnalyticsConfig { final int clientType; // 1=Android, 2=iOS, 3=Flutter final bool enableDebug; - final int batchSize; // 默认 20 - final int flushInterval; // 秒,默认 15 - final int maxCacheSize; // 默认 5000 + final int batchSize; // 默认 30 + final int flushInterval; // 秒,默认 30 + final int maxCacheSize; // 默认 10000 final int maxRetryCount; // 默认 3 - final Duration connectTimeout; // 默认 5s - final Duration readTimeout; // 默认 5s + final Duration connectTimeout; // 默认 10s + final Duration readTimeout; // 默认 10s const AnalyticsConfig({ required this.systemCode, diff --git a/example/lib/main.dart b/example/lib/main.dart index 038e984..a593889 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,14 +1,13 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:yx_tracking_flutter/yx_tracking_flutter.dart'; +import 'view/demo_app.dart'; + final AnalyticsConfig _defaultConfig = AnalyticsConfig( systemCode: 'SDK-TEST-FLUTTER', endpointBaseUrl: 'http://192.168.2.7:18828', - // clientType: 3, // Auto-detected enableDebug: true, - batchSize: 30, + batchSize: 50, flushInterval: 30, allowInsecureHttp: true, ); @@ -19,732 +18,5 @@ Future main() async { await Analytics.init(_defaultConfig); Analytics.bindLifecycleObserver(); - runApp(const DemoApp()); -} - -class DemoApp extends StatelessWidget { - const DemoApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: const DemoPage(), - theme: ThemeData(colorSchemeSeed: Colors.blue, useMaterial3: true), - ); - } -} - -class DemoPage extends StatefulWidget { - const DemoPage({super.key}); - - @override - State createState() => _DemoPageState(); -} - -class _DemoPageState extends State { - int _cacheCount = 0; - List _recent = const []; - final List _logs = []; - final Set _running = {}; - bool _verboseStressLogs = false; - bool _sdkDebugEnabled = true; - final TextEditingController _mockBaseUrlController = TextEditingController(); - final ScrollController _logScrollController = ScrollController(); - Timer? _pollTimer; - late AnalyticsConfig _activeConfig; - - @override - void initState() { - super.initState(); - _activeConfig = _defaultConfig; - _sdkDebugEnabled = _activeConfig.enableDebug; - _refreshCount(); - _pollTimer = Timer.periodic(const Duration(seconds: 2), (_) { - _refreshCount(); - }); - } - - @override - void dispose() { - _pollTimer?.cancel(); - _mockBaseUrlController.dispose(); - _logScrollController.dispose(); - super.dispose(); - } - - bool _isRunning(String key) => _running.contains(key); - - Future _runAction( - String key, - String label, - Future Function() action, { - bool suppressSdkLogs = false, - }) async { - if (_isRunning(key)) { - return; - } - setState(() { - _running.add(key); - }); - final start = DateTime.now(); - _addLog('▶ $label'); - final previousDebug = _sdkDebugEnabled; - if (suppressSdkLogs) { - _setSdkDebug(false, reason: '压力测试静默'); - } - try { - await action(); - final elapsed = DateTime.now().difference(start).inMilliseconds; - _addLog('✅ $label (${elapsed}ms)'); - } on Object catch (e) { - _addLog('❌ $label: $e'); - } finally { - if (suppressSdkLogs) { - _setSdkDebug(previousDebug, reason: '恢复日志'); - } - if (mounted) { - setState(() { - _running.remove(key); - }); - } - await _refreshCount(); - } - } - - void _addLog(String message) { - if (!mounted) { - return; - } - final now = DateTime.now(); - final ts = - '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}'; - setState(() { - _logs.add('[$ts] $message'); - if (_logs.length > 200) { - _logs.removeRange(0, _logs.length - 200); - } - }); - WidgetsBinding.instance.addPostFrameCallback((_) { - if (_logScrollController.hasClients) { - _logScrollController.jumpTo( - _logScrollController.position.maxScrollExtent, - ); - } - }); - } - - Future _refreshCount() async { - final results = await Future.wait(>[ - Analytics.cachedEventCount(), - Analytics.cachedRecentEvents(limit: 20), - ]); - final count = results[0] as int; - final recent = results[1] as List; - if (!mounted) { - return; - } - setState(() { - _cacheCount = count; - _recent = recent; - }); - } - - Map _requiredParams({ - required String page, - required String url, - required String buttonId, - Map? extra, - }) { - final params = { - 'Page': page, - 'Url': url, - 'ButtonId': buttonId, - }; - if (extra != null && extra.isNotEmpty) { - params.addAll(extra); - } - return params; - } - - AnalyticsConfig _buildConfig({ - String? endpointBaseUrl, - Duration? maxEventAge, - bool? allowInsecureHttp, - bool? useIsolateStorage, - Duration? connectTimeout, - Duration? readTimeout, - }) { - final base = _activeConfig; - return AnalyticsConfig( - systemCode: base.systemCode, - endpointBaseUrl: endpointBaseUrl ?? base.endpointBaseUrl, - clientType: base.clientType, - enableDebug: base.enableDebug, - batchSize: base.batchSize, - flushInterval: base.flushInterval, - maxCacheSize: base.maxCacheSize, - maxRetryCount: base.maxRetryCount, - connectTimeout: connectTimeout ?? base.connectTimeout, - readTimeout: readTimeout ?? base.readTimeout, - maxEventAge: maxEventAge ?? base.maxEventAge, - useIsolateStorage: useIsolateStorage ?? base.useIsolateStorage, - allowInsecureHttp: allowInsecureHttp ?? base.allowInsecureHttp, - enableMetrics: base.enableMetrics, - metricsReportInterval: base.metricsReportInterval, - blockOnValidationError: base.blockOnValidationError, - ); - } - - Future _reinitialize(AnalyticsConfig config, {String? reason}) async { - await Analytics.init(config); - if (!mounted) { - return; - } - setState(() { - _activeConfig = config; - _sdkDebugEnabled = config.enableDebug; - }); - if (reason != null && reason.isNotEmpty) { - _addLog('SDK 重新初始化: $reason'); - } - } - - void _setSdkDebug(bool enabled, {required String reason}) { - if (_sdkDebugEnabled == enabled) { - return; - } - Analytics.setDebug(enabled: enabled); - setState(() { - _sdkDebugEnabled = enabled; - }); - _addLog('SDK 日志${enabled ? '开启' : '关闭'}($reason)'); - } - - Future _trackDemoEvent() async { - await Analytics.track( - 'DEMO_BUTTON_CLICK', - eventParams: _requiredParams( - page: 'demo', - url: 'https://example.com/demo', - buttonId: 'demo_btn_01', - ), - customTags: TagTemplates.merge([ - TagTemplates.businessContext( - tenantId: 't1', - appVersion: '1.0.0', - channel: 'internal_test', - ), - TagTemplates.forScreen(screenName: 'DemoPage', featureModule: 'demo'), - ]), - ); - } - - Future _setUserInfo() async { - const userInfo = UserInfo( - userId: 10086, - userName: 'Test User', - account: 'test_account', - ); - await Analytics.setUser(userInfo); - _addLog('Set user: ${userInfo.toJson()}'); - } - - Future _flushNow() async { - await Analytics.flush(force: true); - } - - Future _refreshConfig() async { - await Analytics.refreshConfig(force: true); - } - - Future _runStressSequential() async { - for (var i = 0; i < 1000; i += 1) { - await Analytics.track( - 'STRESS_SEQ', - eventParams: _requiredParams( - page: 'stress_seq', - url: 'https://example.com/stress', - buttonId: 'stress_seq', - extra: {'index': i}, - ), - customTags: const {'feature': 'stress_seq'}, - ); - } - } - - Future _runStressConcurrent() async { - Future trackBatch(int batch, int count) async { - for (var i = 0; i < count; i += 1) { - await Analytics.track( - 'STRESS_CONCURRENT', - eventParams: _requiredParams( - page: 'stress_concurrent', - url: 'https://example.com/stress', - buttonId: 'stress_concurrent', - extra: {'batch': batch, 'index': i}, - ), - customTags: const {'feature': 'stress_concurrent'}, - ); - } - } - - final tasks = List>.generate( - 10, - (index) => trackBatch(index, 100), - ); - await Future.wait(tasks); - } - - Future _runContinuousTrackFlush() async { - final endAt = DateTime.now().add(const Duration(seconds: 30)); - final trackTimer = Timer.periodic(const Duration(milliseconds: 200), (_) { - unawaited( - Analytics.track( - 'CONTINUOUS_EVENT', - eventParams: _requiredParams( - page: 'continuous', - url: 'https://example.com/continuous', - buttonId: 'continuous', - extra: { - 'ts': DateTime.now().millisecondsSinceEpoch, - }, - ), - customTags: const {'feature': 'continuous'}, - ), - ); - }); - final flushTimer = Timer.periodic(const Duration(seconds: 1), (_) { - unawaited(Analytics.flush(force: true)); - }); - - while (DateTime.now().isBefore(endAt)) { - await Future.delayed(const Duration(milliseconds: 500)); - } - - trackTimer.cancel(); - flushTimer.cancel(); - } - - Future _trackInvalidEvent() async { - await Analytics.track( - 'DEMO_BUTTON_CLICK', - eventParams: _requiredParams( - page: 'demo_invalid', - url: 'https://example.com/invalid', - buttonId: 'demo_invalid', - ), - customTags: const {'tenantId': 't1'}, - ); - } - - Future _simulateTimeout() async { - final original = _activeConfig; - final tempConfig = _buildConfig( - endpointBaseUrl: 'http://10.255.255.1:18828', - allowInsecureHttp: true, - connectTimeout: const Duration(seconds: 1), - readTimeout: const Duration(seconds: 1), - ); - await _reinitialize(tempConfig, reason: '网络超时模拟'); - await Analytics.track( - 'EVENT_TIMEOUT_TEST', - eventParams: _requiredParams( - page: 'timeout', - url: 'https://example.com/timeout', - buttonId: 'timeout', - extra: const {'case': 'timeout'}, - ), - ); - await Analytics.flush(force: true); - await _reinitialize(original, reason: '恢复正常配置'); - } - - Future _simulateServerError() async { - final mock = _mockBaseUrlController.text.trim(); - if (mock.isEmpty) { - _addLog('⚠️ 请先填写 5xx/Mock BaseUrl'); - return; - } - final original = _activeConfig; - final tempConfig = _buildConfig( - endpointBaseUrl: mock, - allowInsecureHttp: mock.startsWith('http://'), - ); - await _reinitialize(tempConfig, reason: '5xx 模拟'); - await Analytics.track( - 'EVENT_5XX_TEST', - eventParams: _requiredParams( - page: 'server_5xx', - url: 'https://example.com/5xx', - buttonId: 'server_5xx', - extra: const {'case': 'server_5xx'}, - ), - ); - await Analytics.flush(force: true); - await _reinitialize(original, reason: '恢复正常配置'); - } - - Future _trackEmptyParams() async { - await Analytics.track('EMPTY_PARAMS_EVENT'); - } - - Future _trackLargePayload() async { - final payload = List.filled(100 * 1024, 'a').join(); - await Analytics.track( - 'LARGE_PAYLOAD_EVENT', - eventParams: _requiredParams( - page: 'large_payload', - url: 'https://example.com/large', - buttonId: 'large_payload', - extra: {'payload': payload, 'size': payload.length}, - ), - ); - } - - Future _trackSpecialChars() async { - await Analytics.track( - 'SPECIAL_空格_😀_!@#', - eventParams: _requiredParams( - page: 'special_chars', - url: 'https://example.com/special', - buttonId: 'special_chars', - extra: const {'feature': 'special_chars'}, - ), - ); - } - - Future _rapidInitDispose() async { - for (var i = 0; i < 5; i += 1) { - await Analytics.dispose(); - await Analytics.init(_activeConfig); - } - } - - Future _testExpiration() async { - final original = _activeConfig; - final tempConfig = _buildConfig(maxEventAge: const Duration(seconds: 1)); - await _reinitialize(tempConfig, reason: '过期清理测试'); - await Analytics.track( - 'EXPIRATION_EVENT', - eventParams: _requiredParams( - page: 'expiration', - url: 'https://example.com/expiration', - buttonId: 'expiration', - extra: const {'case': 'expiration'}, - ), - ); - await Future.delayed(const Duration(seconds: 2)); - await Analytics.flush(force: true); - await _reinitialize(original, reason: '恢复正常配置'); - } - - Widget _buildSection(String title, List children) { - return Card( - margin: const EdgeInsets.symmetric(vertical: 8), - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, style: Theme.of(context).textTheme.titleMedium), - const SizedBox(height: 12), - ...children, - ], - ), - ), - ); - } - - Widget _buildConfigSummary() { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Endpoint: ${_activeConfig.endpointBaseUrl}'), - Text('systemCode: ${_activeConfig.systemCode}'), - Text('allowInsecureHttp: ${_activeConfig.allowInsecureHttp}'), - Text('useIsolateStorage: ${_activeConfig.useIsolateStorage}'), - Text('maxEventAge: ${_activeConfig.maxEventAge.inSeconds}s'), - ], - ); - } - - Widget _buildRecentList() { - if (_recent.isEmpty) { - return const Text('暂无事件'); - } - return ListView.separated( - itemCount: _recent.length, - separatorBuilder: (_, __) => const Divider(height: 1), - itemBuilder: (context, index) { - final item = _recent[index]; - return ListTile( - dense: true, - title: Text(item.eventType), - subtitle: Text( - '${item.createTime.toIso8601String()} · retry=${item.retryCount}', - ), - ); - }, - ); - } - - Widget _buildLogList() { - if (_logs.isEmpty) { - return const Text('暂无日志'); - } - return ListView.builder( - controller: _logScrollController, - itemCount: _logs.length, - itemBuilder: (context, index) => Text(_logs[index]), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('YX Tracking Demo')), - body: ListView( - padding: const EdgeInsets.all(16), - children: [ - Text( - '本地缓存事件数:$_cacheCount', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 8), - _buildSection('配置摘要', [_buildConfigSummary()]), - _buildSection('基础操作', [ - Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ElevatedButton( - onPressed: _isRunning('track_demo') - ? null - : () => _runAction( - 'track_demo', - 'Track Demo Event', - _trackDemoEvent, - ), - child: const Text('Track Demo Event'), - ), - ElevatedButton( - onPressed: _isRunning('set_user') - ? null - : () => _runAction( - 'set_user', - 'Set User Info', - _setUserInfo, - ), - child: const Text('Set User Info'), - ), - ElevatedButton( - onPressed: _isRunning('flush') - ? null - : () => _runAction('flush', 'Flush Now', _flushNow), - child: Text( - _isRunning('flush') ? 'Flushing...' : 'Flush Now', - ), - ), - OutlinedButton( - onPressed: _isRunning('refresh_config') - ? null - : () => _runAction( - 'refresh_config', - 'Refresh Config', - _refreshConfig, - ), - child: Text( - _isRunning('refresh_config') - ? 'Refreshing...' - : 'Refresh Config', - ), - ), - ], - ), - const SizedBox(height: 8), - const Text( - '说明:已对接 SDK-TEST-FLUTTER 系统;若为 HTTP 联调,请保持 allowInsecureHttp=true。', - ), - ]), - _buildSection('压力测试', [ - SwitchListTile( - contentPadding: EdgeInsets.zero, - title: const Text('压力测试详细日志'), - subtitle: const Text('关闭后仅输出结果与异常'), - value: _verboseStressLogs, - onChanged: (value) { - setState(() { - _verboseStressLogs = value; - }); - }, - ), - Wrap( - spacing: 12, - runSpacing: 12, - children: [ - OutlinedButton( - onPressed: _isRunning('stress_seq') - ? null - : () => _runAction( - 'stress_seq', - 'Track 1000 (Sequential)', - _runStressSequential, - suppressSdkLogs: !_verboseStressLogs, - ), - child: Text( - _isRunning('stress_seq') - ? 'Testing...' - : 'Track 1000 (Sequential)', - ), - ), - OutlinedButton( - onPressed: _isRunning('stress_con') - ? null - : () => _runAction( - 'stress_con', - 'Track 1000 (Concurrent)', - _runStressConcurrent, - suppressSdkLogs: !_verboseStressLogs, - ), - child: Text( - _isRunning('stress_con') - ? 'Testing...' - : 'Track 1000 (Concurrent)', - ), - ), - OutlinedButton( - onPressed: _isRunning('continuous') - ? null - : () => _runAction( - 'continuous', - 'Continuous Track + Flush', - _runContinuousTrackFlush, - suppressSdkLogs: !_verboseStressLogs, - ), - child: Text( - _isRunning('continuous') - ? 'Running...' - : 'Continuous Track + Flush (30s)', - ), - ), - ], - ), - ]), - _buildSection('错误模拟', [ - TextField( - controller: _mockBaseUrlController, - decoration: const InputDecoration( - labelText: '5xx/Mock BaseUrl(可选)', - hintText: 'http://localhost:8080', - ), - ), - const SizedBox(height: 8), - Wrap( - spacing: 12, - runSpacing: 12, - children: [ - OutlinedButton( - onPressed: _isRunning('invalid_event') - ? null - : () => _runAction( - 'invalid_event', - 'Invalid Event (Missing Tag)', - _trackInvalidEvent, - ), - child: const Text('Invalid Event (Missing Tag)'), - ), - OutlinedButton( - onPressed: _isRunning('timeout') - ? null - : () => _runAction( - 'timeout', - 'Simulate Network Timeout', - _simulateTimeout, - ), - child: const Text('Simulate Network Timeout'), - ), - OutlinedButton( - onPressed: _isRunning('server_5xx') - ? null - : () => _runAction( - 'server_5xx', - 'Simulate Server 5xx', - _simulateServerError, - ), - child: const Text('Simulate Server 5xx'), - ), - ], - ), - ]), - _buildSection('边界用例', [ - Wrap( - spacing: 12, - runSpacing: 12, - children: [ - OutlinedButton( - onPressed: _isRunning('empty_params') - ? null - : () => _runAction( - 'empty_params', - 'Empty Params', - _trackEmptyParams, - ), - child: const Text('Empty Params'), - ), - OutlinedButton( - onPressed: _isRunning('large_payload') - ? null - : () => _runAction( - 'large_payload', - 'Large Payload (100KB)', - _trackLargePayload, - ), - child: const Text('Large Payload (100KB)'), - ), - OutlinedButton( - onPressed: _isRunning('special_chars') - ? null - : () => _runAction( - 'special_chars', - 'Special Characters', - _trackSpecialChars, - ), - child: const Text('Special Characters'), - ), - OutlinedButton( - onPressed: _isRunning('rapid_init') - ? null - : () => _runAction( - 'rapid_init', - 'Rapid Init/Dispose (x5)', - _rapidInitDispose, - ), - child: const Text('Rapid Init/Dispose (x5)'), - ), - OutlinedButton( - onPressed: _isRunning('expiration') - ? null - : () => _runAction( - 'expiration', - 'Test Expiration (1s)', - _testExpiration, - ), - child: const Text('Test Expiration (1s)'), - ), - ], - ), - ]), - _buildSection('状态与日志', [ - const Text('最近事件(最多 20 条)'), - const SizedBox(height: 8), - SizedBox(height: 220, child: _buildRecentList()), - const SizedBox(height: 12), - const Text('日志输出'), - const SizedBox(height: 8), - SizedBox(height: 160, child: _buildLogList()), - ]), - ], - ), - ); - } + runApp(DemoApp(initialConfig: _defaultConfig)); } diff --git a/example/lib/service/analytics_service.dart b/example/lib/service/analytics_service.dart new file mode 100644 index 0000000..493a797 --- /dev/null +++ b/example/lib/service/analytics_service.dart @@ -0,0 +1,52 @@ +import 'dart:async'; + +import 'package:yx_tracking_flutter/yx_tracking_flutter.dart'; + +class AnalyticsService { + const AnalyticsService(); + + Future init(AnalyticsConfig config) => Analytics.init(config); + + Future dispose() => Analytics.dispose(); + + Future flush({bool force = false}) => Analytics.flush(force: force); + + Future refreshConfig({bool force = true}) => + Analytics.refreshConfig(force: force); + + Future cachedEventCount() => Analytics.cachedEventCount(); + + Future> cachedRecentEvents({int limit = 20}) => + Analytics.cachedRecentEvents(limit: limit); + + void setDebug({required bool enabled}) => Analytics.setDebug(enabled: enabled); + + Map requiredParams({ + required String page, + required String url, + required String buttonId, + Map? extra, + }) { + final params = { + 'Page': page, + 'Url': url, + 'ButtonId': buttonId, + }; + if (extra != null && extra.isNotEmpty) { + params.addAll(extra); + } + return params; + } + + Future track( + String eventType, { + Map? eventParams, + Map? customTags, + }) { + return Analytics.track( + eventType, + eventParams: eventParams, + customTags: customTags, + ); + } +} diff --git a/example/lib/view/demo_app.dart b/example/lib/view/demo_app.dart new file mode 100644 index 0000000..dc4eb84 --- /dev/null +++ b/example/lib/view/demo_app.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:yx_tracking_flutter/yx_tracking_flutter.dart'; + +import 'demo_page.dart'; + +class DemoApp extends StatelessWidget { + const DemoApp({super.key, required this.initialConfig}); + + final AnalyticsConfig initialConfig; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: DemoPage(initialConfig: initialConfig), + theme: ThemeData( + colorSchemeSeed: Colors.blue, + useMaterial3: true, + ), + ); + } +} diff --git a/example/lib/view/demo_page.dart b/example/lib/view/demo_page.dart new file mode 100644 index 0000000..1336cf4 --- /dev/null +++ b/example/lib/view/demo_page.dart @@ -0,0 +1,261 @@ +import 'package:flutter/material.dart'; +import 'package:yx_tracking_flutter/yx_tracking_flutter.dart'; + +import '../service/analytics_service.dart'; +import '../view_model/demo_view_model.dart'; +import 'widgets.dart'; + +class DemoPage extends StatefulWidget { + const DemoPage({super.key, required this.initialConfig}); + + final AnalyticsConfig initialConfig; + + @override + State createState() => _DemoPageState(); +} + +class _DemoPageState extends State { + late DemoViewModel _viewModel; + final TextEditingController _mockBaseUrlController = TextEditingController(); + final ScrollController _logScrollController = ScrollController(); + int _lastLogCount = 0; + + @override + void initState() { + super.initState(); + _viewModel = DemoViewModel( + analytics: const AnalyticsService(), + initialConfig: widget.initialConfig, + )..init(); + _mockBaseUrlController.text = _viewModel.mockBaseUrl; + _viewModel.addListener(_handleViewModelChanged); + } + + @override + void dispose() { + _viewModel.removeListener(_handleViewModelChanged); + _viewModel.dispose(); + _mockBaseUrlController.dispose(); + _logScrollController.dispose(); + super.dispose(); + } + + void _handleViewModelChanged() { + if (!mounted) { + return; + } + if (_viewModel.logs.length != _lastLogCount) { + _lastLogCount = _viewModel.logs.length; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_logScrollController.hasClients) { + _logScrollController.jumpTo( + _logScrollController.position.maxScrollExtent, + ); + } + }); + } + setState(() {}); + } + + Widget _buildConfigSummary() { + final config = _viewModel.activeConfig; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Endpoint: ${config.endpointBaseUrl}'), + Text('systemCode: ${config.systemCode}'), + Text('allowInsecureHttp: ${config.allowInsecureHttp}'), + Text('useIsolateStorage: ${config.useIsolateStorage}'), + Text('maxEventAge: ${config.maxEventAge.inSeconds}s'), + ], + ); + } + + Widget _actionButton({ + required String key, + required String label, + required Future Function() action, + bool outlined = true, + bool suppressSdkLogs = false, + }) { + return ActionButton( + label: label, + outlined: outlined, + running: _viewModel.isRunning(key), + onPressed: _viewModel.isRunning(key) + ? null + : () => _viewModel.runAction( + key, + label, + action, + suppressSdkLogs: suppressSdkLogs, + ), + ); + } + + Widget _buildActionButtons(List buttons) { + return Wrap(spacing: 12, runSpacing: 12, children: buttons); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('YX Tracking Demo')), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + Text( + '本地缓存事件数:${_viewModel.cacheCount}', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + SectionCard(title: '配置摘要', children: [_buildConfigSummary()]), + SectionCard( + title: '基础操作', + children: [ + _buildActionButtons([ + _actionButton( + key: 'track_demo', + label: 'Track 演示事件', + action: _viewModel.trackDemoEvent, + outlined: false, + ), + _actionButton( + key: 'flush', + label: '立即上报', + action: _viewModel.flushNow, + outlined: false, + ), + _actionButton( + key: 'refresh_config', + label: '刷新配置', + action: _viewModel.refreshConfig, + ), + ]), + const SizedBox(height: 8), + const Text( + '说明:已对接 SDK-TEST-FLUTTER 系统;若为 HTTP 联调,请保持 allowInsecureHttp=true。', + ), + ], + ), + SectionCard( + title: '压力测试', + children: [ + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('压力测试详细日志'), + subtitle: const Text('关闭后仅输出结果与异常'), + value: _viewModel.verboseStressLogs, + onChanged: (value) => _viewModel.setVerboseStressLogs(value), + ), + _buildActionButtons([ + _actionButton( + key: 'stress_seq', + label: '串行 Track 1000', + action: _viewModel.runStressSequential, + suppressSdkLogs: !_viewModel.verboseStressLogs, + ), + _actionButton( + key: 'stress_con', + label: '并发 Track 1000', + action: _viewModel.runStressConcurrent, + suppressSdkLogs: !_viewModel.verboseStressLogs, + ), + _actionButton( + key: 'continuous', + label: '持续 Track + Flush (30秒)', + action: _viewModel.runContinuousTrackFlush, + suppressSdkLogs: !_viewModel.verboseStressLogs, + ), + ]), + ], + ), + SectionCard( + title: '错误模拟', + children: [ + TextField( + controller: _mockBaseUrlController, + decoration: const InputDecoration( + labelText: '5xx/Mock BaseUrl(可选)', + hintText: 'http://localhost:8080', + ), + onChanged: _viewModel.setMockBaseUrl, + ), + const SizedBox(height: 8), + _buildActionButtons([ + _actionButton( + key: 'invalid_event', + label: '非法事件 (缺少标签)', + action: _viewModel.trackInvalidEvent, + ), + _actionButton( + key: 'timeout', + label: '模拟网络超时', + action: _viewModel.simulateTimeout, + ), + _actionButton( + key: 'server_5xx', + label: '模拟服务器 5xx', + action: _viewModel.simulateServerError, + ), + ]), + ], + ), + SectionCard( + title: '边界用例', + children: [ + _buildActionButtons([ + _actionButton( + key: 'empty_params', + label: '空参数', + action: _viewModel.trackEmptyParams, + ), + _actionButton( + key: 'large_payload', + label: '大载荷 (100KB)', + action: _viewModel.trackLargePayload, + ), + _actionButton( + key: 'special_chars', + label: '特殊字符', + action: _viewModel.trackSpecialChars, + ), + _actionButton( + key: 'rapid_init', + label: '快速初始化/销毁 (x5)', + action: _viewModel.rapidInitDispose, + ), + _actionButton( + key: 'expiration', + label: '测试过期 (1秒)', + action: _viewModel.testExpiration, + ), + ]), + ], + ), + SectionCard( + title: '状态与日志', + children: [ + const Text('最近事件(最多 20 条)'), + const SizedBox(height: 8), + SizedBox( + height: 220, + child: RecentEventList(items: _viewModel.recent), + ), + const SizedBox(height: 12), + const Text('日志输出'), + const SizedBox(height: 8), + SizedBox( + height: 160, + child: LogList( + logs: _viewModel.logs, + controller: _logScrollController, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/example/lib/view/widgets.dart b/example/lib/view/widgets.dart new file mode 100644 index 0000000..4b23dc6 --- /dev/null +++ b/example/lib/view/widgets.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:yx_tracking_flutter/yx_tracking_flutter.dart'; + +class SectionCard extends StatelessWidget { + const SectionCard({ + super.key, + required this.title, + required this.children, + }); + + final String title; + final List children; + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(vertical: 8), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title, style: Theme.of(context).textTheme.titleMedium), + const SizedBox(height: 12), + ...children, + ], + ), + ), + ); + } +} + +class ActionButton extends StatelessWidget { + const ActionButton({ + super.key, + required this.label, + required this.onPressed, + this.outlined = true, + this.running = false, + }); + + final String label; + final VoidCallback? onPressed; + final bool outlined; + final bool running; + + @override + Widget build(BuildContext context) { + final child = Text(running ? '$label...' : label); + if (outlined) { + return OutlinedButton(onPressed: onPressed, child: child); + } + return ElevatedButton(onPressed: onPressed, child: child); + } +} + +class RecentEventList extends StatelessWidget { + const RecentEventList({super.key, required this.items}); + + final List items; + + @override + Widget build(BuildContext context) { + if (items.isEmpty) { + return const Text('暂无事件'); + } + return ListView.separated( + itemCount: items.length, + separatorBuilder: (_, __) => const Divider(height: 1), + itemBuilder: (context, index) { + final item = items[index]; + return ListTile( + dense: true, + title: Text(item.eventType), + subtitle: Text( + '${item.createTime.toIso8601String()} · retry=${item.retryCount}', + ), + ); + }, + ); + } +} + +class LogList extends StatelessWidget { + const LogList({super.key, required this.logs, required this.controller}); + + final List logs; + final ScrollController controller; + + @override + Widget build(BuildContext context) { + if (logs.isEmpty) { + return const Text('暂无日志'); + } + return ListView.builder( + controller: controller, + itemCount: logs.length, + itemBuilder: (context, index) => Text(logs[index]), + ); + } +} diff --git a/example/lib/view_model/demo_view_model.dart b/example/lib/view_model/demo_view_model.dart new file mode 100644 index 0000000..32e88e7 --- /dev/null +++ b/example/lib/view_model/demo_view_model.dart @@ -0,0 +1,376 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:yx_tracking_flutter/yx_tracking_flutter.dart'; + +import '../service/analytics_service.dart'; + +class DemoViewModel extends ChangeNotifier { + DemoViewModel({ + required AnalyticsService analytics, + required AnalyticsConfig initialConfig, + }) : _analytics = analytics, + _activeConfig = initialConfig, + _initialConfig = initialConfig { + _sdkDebugEnabled = initialConfig.enableDebug; + } + + final AnalyticsService _analytics; + final AnalyticsConfig _initialConfig; + AnalyticsConfig _activeConfig; + bool _verboseStressLogs = false; + bool _sdkDebugEnabled = true; + int _cacheCount = 0; + List _recent = const []; + final List _logs = []; + final Set _running = {}; + String _mockBaseUrl = ''; + Timer? _pollTimer; + + AnalyticsConfig get activeConfig => _activeConfig; + bool get verboseStressLogs => _verboseStressLogs; + bool get sdkDebugEnabled => _sdkDebugEnabled; + int get cacheCount => _cacheCount; + List get recent => _recent; + List get logs => List.unmodifiable(_logs); + String get mockBaseUrl => _mockBaseUrl; + + void init() { + _refreshCount(); + _pollTimer = Timer.periodic(const Duration(seconds: 2), (_) { + _refreshCount(); + }); + } + + @override + void dispose() { + _pollTimer?.cancel(); + super.dispose(); + } + + bool isRunning(String key) => _running.contains(key); + + void setVerboseStressLogs(bool value) { + if (_verboseStressLogs == value) { + return; + } + _verboseStressLogs = value; + notifyListeners(); + } + + void setMockBaseUrl(String value) { + if (_mockBaseUrl == value) { + return; + } + _mockBaseUrl = value; + } + + Future runAction( + String key, + String label, + Future Function() action, { + bool suppressSdkLogs = false, + }) async { + if (isRunning(key)) { + return; + } + _running.add(key); + notifyListeners(); + + final start = DateTime.now(); + _addLog('▶ $label'); + + final previousDebug = _sdkDebugEnabled; + if (suppressSdkLogs) { + _setSdkDebug(false, reason: '压力测试静默'); + } + + try { + await action(); + final elapsed = DateTime.now().difference(start).inMilliseconds; + _addLog('✅ $label (${elapsed}ms)'); + } on Object catch (e) { + _addLog('❌ $label: $e'); + } finally { + if (suppressSdkLogs) { + _setSdkDebug(previousDebug, reason: '恢复日志'); + } + _running.remove(key); + notifyListeners(); + await _refreshCount(); + } + } + + Future refreshCount() => _refreshCount(); + + AnalyticsConfig buildConfig({ + String? endpointBaseUrl, + Duration? maxEventAge, + bool? allowInsecureHttp, + bool? useIsolateStorage, + Duration? connectTimeout, + Duration? readTimeout, + }) { + final base = _activeConfig; + return AnalyticsConfig( + systemCode: base.systemCode, + endpointBaseUrl: endpointBaseUrl ?? base.endpointBaseUrl, + enableDebug: base.enableDebug, + batchSize: base.batchSize, + flushInterval: base.flushInterval, + maxCacheSize: base.maxCacheSize, + maxRetryCount: base.maxRetryCount, + connectTimeout: connectTimeout ?? base.connectTimeout, + readTimeout: readTimeout ?? base.readTimeout, + maxEventAge: maxEventAge ?? base.maxEventAge, + useIsolateStorage: useIsolateStorage ?? base.useIsolateStorage, + allowInsecureHttp: allowInsecureHttp ?? base.allowInsecureHttp, + enableMetrics: base.enableMetrics, + metricsReportInterval: base.metricsReportInterval, + blockOnValidationError: base.blockOnValidationError, + ); + } + + Future reinitialize(AnalyticsConfig config, {String? reason}) async { + await _analytics.init(config); + _activeConfig = config; + _sdkDebugEnabled = config.enableDebug; + if (reason != null && reason.isNotEmpty) { + _addLog('SDK 重新初始化: $reason'); + } + notifyListeners(); + } + + Future resetToInitial() => reinitialize(_initialConfig); + + void _setSdkDebug(bool enabled, {required String reason}) { + if (_sdkDebugEnabled == enabled) { + return; + } + _analytics.setDebug(enabled: enabled); + _sdkDebugEnabled = enabled; + _addLog('SDK 日志${enabled ? '开启' : '关闭'}($reason)'); + notifyListeners(); + } + + Future trackDemoEvent() async { + await _analytics.track( + 'DEMO_BUTTON_CLICK', + eventParams: _analytics.requiredParams( + page: 'demo', + url: 'https://example.com/demo', + buttonId: 'demo_btn_01', + ), + customTags: const {'tenantId': 't1', 'feature': 'demo'}, + ); + } + + Future flushNow() => _analytics.flush(force: true); + + Future refreshConfig() => _analytics.refreshConfig(force: true); + + Future runStressSequential() async { + for (var i = 0; i < 1000; i += 1) { + await _analytics.track( + 'STRESS_SEQ', + eventParams: _analytics.requiredParams( + page: 'stress_seq', + url: 'https://example.com/stress', + buttonId: 'stress_seq', + extra: {'index': i}, + ), + customTags: const {'feature': 'stress_seq'}, + ); + } + } + + Future runStressConcurrent() async { + Future trackBatch(int batch, int count) async { + for (var i = 0; i < count; i += 1) { + await _analytics.track( + 'STRESS_CONCURRENT', + eventParams: _analytics.requiredParams( + page: 'stress_concurrent', + url: 'https://example.com/stress', + buttonId: 'stress_concurrent', + extra: {'batch': batch, 'index': i}, + ), + customTags: const {'feature': 'stress_concurrent'}, + ); + } + } + + final tasks = List>.generate( + 10, + (index) => trackBatch(index, 100), + ); + await Future.wait(tasks); + } + + Future runContinuousTrackFlush() async { + final endAt = DateTime.now().add(const Duration(seconds: 30)); + final trackTimer = Timer.periodic(const Duration(milliseconds: 200), (_) { + unawaited( + _analytics.track( + 'CONTINUOUS_EVENT', + eventParams: _analytics.requiredParams( + page: 'continuous', + url: 'https://example.com/continuous', + buttonId: 'continuous', + extra: { + 'ts': DateTime.now().millisecondsSinceEpoch, + }, + ), + customTags: const {'feature': 'continuous'}, + ), + ); + }); + final flushTimer = Timer.periodic(const Duration(seconds: 1), (_) { + unawaited(_analytics.flush(force: true)); + }); + + while (DateTime.now().isBefore(endAt)) { + await Future.delayed(const Duration(milliseconds: 500)); + } + + trackTimer.cancel(); + flushTimer.cancel(); + } + + Future trackInvalidEvent() async { + await _analytics.track( + 'DEMO_BUTTON_CLICK', + eventParams: _analytics.requiredParams( + page: 'demo_invalid', + url: 'https://example.com/invalid', + buttonId: 'demo_invalid', + ), + customTags: const {'tenantId': 't1'}, + ); + } + + Future simulateTimeout() async { + final original = _activeConfig; + final tempConfig = buildConfig( + endpointBaseUrl: 'http://10.255.255.1:18828', + allowInsecureHttp: true, + connectTimeout: const Duration(seconds: 1), + readTimeout: const Duration(seconds: 1), + ); + await reinitialize(tempConfig, reason: '网络超时模拟'); + await _analytics.track( + 'EVENT_TIMEOUT_TEST', + eventParams: _analytics.requiredParams( + page: 'timeout', + url: 'https://example.com/timeout', + buttonId: 'timeout', + extra: const {'case': 'timeout'}, + ), + ); + await _analytics.flush(force: true); + await reinitialize(original, reason: '恢复正常配置'); + } + + Future simulateServerError() async { + final mock = _mockBaseUrl.trim(); + if (mock.isEmpty) { + _addLog('⚠️ 请先填写 5xx/Mock BaseUrl'); + notifyListeners(); + return; + } + final original = _activeConfig; + final tempConfig = buildConfig( + endpointBaseUrl: mock, + allowInsecureHttp: mock.startsWith('http://'), + ); + await reinitialize(tempConfig, reason: '5xx 模拟'); + await _analytics.track( + 'EVENT_5XX_TEST', + eventParams: _analytics.requiredParams( + page: 'server_5xx', + url: 'https://example.com/5xx', + buttonId: 'server_5xx', + extra: const {'case': 'server_5xx'}, + ), + ); + await _analytics.flush(force: true); + await reinitialize(original, reason: '恢复正常配置'); + } + + Future trackEmptyParams() async { + await _analytics.track('EMPTY_PARAMS_EVENT'); + } + + Future trackLargePayload() async { + final payload = List.filled(100 * 1024, 'a').join(); + await _analytics.track( + 'LARGE_PAYLOAD_EVENT', + eventParams: _analytics.requiredParams( + page: 'large_payload', + url: 'https://example.com/large', + buttonId: 'large_payload', + extra: {'payload': payload, 'size': payload.length}, + ), + ); + } + + Future trackSpecialChars() async { + await _analytics.track( + 'SPECIAL_空格_😀_!@#', + eventParams: _analytics.requiredParams( + page: 'special_chars', + url: 'https://example.com/special', + buttonId: 'special_chars', + extra: const {'feature': 'special_chars'}, + ), + ); + } + + Future rapidInitDispose() async { + for (var i = 0; i < 5; i += 1) { + await _analytics.dispose(); + await _analytics.init(_activeConfig); + } + } + + Future testExpiration() async { + final original = _activeConfig; + final tempConfig = buildConfig(maxEventAge: const Duration(seconds: 1)); + await reinitialize(tempConfig, reason: '过期清理测试'); + await _analytics.track( + 'EXPIRATION_EVENT', + eventParams: _analytics.requiredParams( + page: 'expiration', + url: 'https://example.com/expiration', + buttonId: 'expiration', + extra: const {'case': 'expiration'}, + ), + ); + await Future.delayed(const Duration(seconds: 2)); + await _analytics.flush(force: true); + await reinitialize(original, reason: '恢复正常配置'); + } + + void _addLog(String message) { + final now = DateTime.now(); + final ts = + '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}'; + _logs.add('[$ts] $message'); + if (_logs.length > 200) { + _logs.removeRange(0, _logs.length - 200); + } + notifyListeners(); + } + + Future _refreshCount() async { + final results = await Future.wait(>[ + _analytics.cachedEventCount(), + _analytics.cachedRecentEvents(limit: 20), + ]); + final count = results[0] as int; + final recent = results[1] as List; + _cacheCount = count; + _recent = recent; + notifyListeners(); + } +} diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index df8115b..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Demo 页面基础元素可渲染', (WidgetTester tester) async { - await tester.pumpWidget(const DemoApp()); - await tester.pump(); - - expect(find.text('YX Tracking Demo'), findsOneWidget); - expect(find.textContaining('本地缓存事件数'), findsOneWidget); - expect(find.text('Track Demo Event'), findsOneWidget); - expect(find.text('Flush Now'), findsOneWidget); - }); -} diff --git a/lib/src/config/analytics_config.dart b/lib/src/config/analytics_config.dart index 78a155a..1379d20 100644 --- a/lib/src/config/analytics_config.dart +++ b/lib/src/config/analytics_config.dart @@ -7,21 +7,20 @@ class AnalyticsConfig { AnalyticsConfig({ required this.systemCode, required this.endpointBaseUrl, - int? clientType, this.enableDebug = false, - this.batchSize = 20, - this.flushInterval = 15, - this.maxCacheSize = 5000, + this.batchSize = 30, + this.flushInterval = 30, + this.maxCacheSize = 10000, this.maxRetryCount = 3, - this.connectTimeout = const Duration(seconds: 5), - this.readTimeout = const Duration(seconds: 5), + this.connectTimeout = const Duration(seconds: 10), + this.readTimeout = const Duration(seconds: 10), this.maxEventAge = const Duration(days: 7), this.useIsolateStorage = true, this.allowInsecureHttp = false, this.enableMetrics = true, this.metricsReportInterval = const Duration(minutes: 10), this.blockOnValidationError = false, - }) : clientType = clientType ?? _detectClientType(); + }) : clientType = _detectClientType(); /// 自动探测客户端类型。 static int _detectClientType() { diff --git a/lib/src/network/api_client.dart b/lib/src/network/api_client.dart index 51bc718..8fe06ad 100644 --- a/lib/src/network/api_client.dart +++ b/lib/src/network/api_client.dart @@ -72,7 +72,8 @@ class ApiClient { } final size = events.length; - Logger.info('批量上报成功: size=$size, status=$statusCode'); + Logger.info( + '批量上报成功: size=$size, status=$statusCode, response=${response.data}'); } on DioException catch (e, st) { final apiException = _mapDioException(e); Logger.error('批量上报异常: ${apiException.message}', apiException, st); diff --git a/test/analytics_config_extra_test.dart b/test/analytics_config_extra_test.dart index fcd41c8..7e42c69 100644 --- a/test/analytics_config_extra_test.dart +++ b/test/analytics_config_extra_test.dart @@ -4,7 +4,6 @@ import 'package:yx_tracking_flutter/src/config/analytics_config.dart'; AnalyticsConfig _base({ String? systemCode, String? endpointBaseUrl, - int? clientType, int? batchSize, int? flushInterval, int? maxCacheSize, @@ -20,19 +19,17 @@ AnalyticsConfig _base({ return AnalyticsConfig( systemCode: systemCode ?? 'SYS', endpointBaseUrl: endpointBaseUrl ?? 'https://example.com', - clientType: clientType ?? 3, - batchSize: batchSize ?? 20, - flushInterval: flushInterval ?? 15, + batchSize: batchSize ?? 30, + flushInterval: flushInterval ?? 30, maxCacheSize: maxCacheSize ?? 100, maxRetryCount: maxRetryCount ?? 3, - connectTimeout: connectTimeout ?? const Duration(seconds: 1), - readTimeout: readTimeout ?? const Duration(seconds: 1), + connectTimeout: connectTimeout ?? const Duration(seconds: 10), + readTimeout: readTimeout ?? const Duration(seconds: 10), maxEventAge: maxEventAge ?? const Duration(days: 7), useIsolateStorage: useIsolateStorage ?? false, allowInsecureHttp: allowInsecureHttp ?? false, enableMetrics: enableMetrics ?? true, - metricsReportInterval: - metricsReportInterval ?? const Duration(minutes: 1), + metricsReportInterval: metricsReportInterval ?? const Duration(minutes: 1), ); } @@ -65,17 +62,13 @@ void main() { test('允许 http 时不抛错', () { expect( - () => - _base(endpointBaseUrl: 'http://example.com', allowInsecureHttp: true) - .validate(), + () => _base( + endpointBaseUrl: 'http://example.com', allowInsecureHttp: true) + .validate(), returnsNormally, ); }); - test('clientType 必须为正数', () { - expect(() => _base(clientType: 0).validate(), throwsArgumentError); - }); - test('batchSize 必须为正数', () { expect(() => _base(batchSize: 0).validate(), throwsArgumentError); }); diff --git a/test/analytics_core_test.dart b/test/analytics_core_test.dart index 0cc7a76..b902b6b 100644 --- a/test/analytics_core_test.dart +++ b/test/analytics_core_test.dart @@ -291,7 +291,6 @@ AnalyticsConfig _testConfig({ return AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, enableDebug: enableDebug, batchSize: batchSize, flushInterval: 3600, @@ -754,7 +753,6 @@ void main() { final config = AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, enableDebug: true, batchSize: 10, flushInterval: 3600, @@ -882,7 +880,6 @@ void main() { final config = AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, enableDebug: true, flushInterval: 3600, enableMetrics: false, @@ -1209,7 +1206,6 @@ void main() { final config = AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, flushInterval: 3600, metricsReportInterval: Duration(milliseconds: 10), ); diff --git a/test/api_client_test.dart b/test/api_client_test.dart index 30b0c83..a352dc7 100644 --- a/test/api_client_test.dart +++ b/test/api_client_test.dart @@ -10,7 +10,6 @@ AnalyticsConfig _config() { return AnalyticsConfig( systemCode: 'SYS', endpointBaseUrl: 'https://example.com', - clientType: 3, enableDebug: true, ); } diff --git a/test/config_manager_test.dart b/test/config_manager_test.dart index 9cbaf61..4385508 100644 --- a/test/config_manager_test.dart +++ b/test/config_manager_test.dart @@ -79,7 +79,6 @@ AnalyticsConfig _config() { return AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, enableMetrics: false, ); } diff --git a/test/facade_test.dart b/test/facade_test.dart index 30f2ef6..f1bcc62 100644 --- a/test/facade_test.dart +++ b/test/facade_test.dart @@ -190,8 +190,6 @@ AnalyticsConfig _config() { return AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, - enableDebug: true, metricsReportInterval: Duration(days: 1), ); } diff --git a/test/http_client_test.dart b/test/http_client_test.dart index 7b59bb4..66271d2 100644 --- a/test/http_client_test.dart +++ b/test/http_client_test.dart @@ -35,7 +35,6 @@ AnalyticsConfig _config(String baseUrl) { return AnalyticsConfig( systemCode: 'SYS', endpointBaseUrl: baseUrl, - clientType: 3, ); } diff --git a/test/validator_test.dart b/test/validator_test.dart index 19e12c1..0e34c28 100644 --- a/test/validator_test.dart +++ b/test/validator_test.dart @@ -41,7 +41,6 @@ AnalyticsConfig _config() { return AnalyticsConfig( systemCode: 'TEST_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, enableMetrics: false, ); } diff --git a/test/yx_tracking_flutter_test.dart b/test/yx_tracking_flutter_test.dart index ca82b1a..202aa1a 100644 --- a/test/yx_tracking_flutter_test.dart +++ b/test/yx_tracking_flutter_test.dart @@ -8,7 +8,6 @@ void main() { final config = AnalyticsConfig( systemCode: 'OA_APP', endpointBaseUrl: 'https://example.com', - clientType: 3, ); expect(config.validate, returnsNormally); @@ -18,7 +17,6 @@ void main() { final config = AnalyticsConfig( systemCode: 'OA_APP', endpointBaseUrl: 'http://example.com', - clientType: 3, ); expect(config.validate, throwsArgumentError);