From ddfc3a8a67e2646c295155177336f92c3e877875 Mon Sep 17 00:00:00 2001 From: koukibadr Date: Sun, 5 Jul 2026 09:21:38 +0300 Subject: [PATCH 1/2] feat: setup logging service --- example/ios/Runner.xcodeproj/project.pbxproj | 18 --------- example/lib/main.dart | 2 +- lib/flutter_skin.dart | 10 +++++ lib/services/fskin_logger.dart | 39 ++++++++++++++++++++ lib/services/fskin_subscriber.dart | 7 ++++ lib/services/skin_service.dart | 6 +++ 6 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 lib/services/fskin_logger.dart diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 338ea3f..d5b2739 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -201,7 +201,6 @@ 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - C28130B8ACDE2425E043E8EF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -348,23 +347,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - C28130B8ACDE2425E043E8EF /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/example/lib/main.dart b/example/lib/main.dart index 0d63e83..cbd5b7c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,7 +6,7 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); await FlutterSkin.init( apiKey: - "fsk_b4331e99326b000434d601a80284abf89b8425e5f246f3b76a924c9d04486012", + "fsk_9a8ea7f3a0deaf89470bdcfdd762b3d3c0fd63b086a08a24bcdd46c82755cd36", ); runApp(const MyApp()); } diff --git a/lib/flutter_skin.dart b/lib/flutter_skin.dart index 7d59f14..2a6ead2 100644 --- a/lib/flutter_skin.dart +++ b/lib/flutter_skin.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart' show ThemeData, ColorScheme; import 'package:flutter/widgets.dart'; import 'package:flutter_skin/models/project_config.dart'; import 'package:flutter_skin/remote/fskin_remote_config.dart'; +import 'package:flutter_skin/services/fskin_logger.dart'; import 'package:flutter_skin/services/fskin_subscriber.dart'; class FlutterSkin with WidgetsBindingObserver { @@ -10,12 +11,14 @@ class FlutterSkin with WidgetsBindingObserver { late String apiKey; static final FskinSubscriber _sse = FskinSubscriber(); static Stream get onSkinChanged => FskinRemoteConfig.onSkinChanged; + static final FskinLogger _logger = FskinLogger(); // Private constructor FlutterSkin._(); static Future init({required String apiKey}) async { if (apiKey.trim().isEmpty) { + _logger.logError('apiKey must not be empty'); throw ArgumentError.value(apiKey, 'apiKey', 'apiKey must not be empty'); } if (_instance == null) { @@ -40,6 +43,9 @@ class FlutterSkin with WidgetsBindingObserver { /// Start listening to the backend stream for skin and projects updates static void _startStream() { + _logger.logMessage( + 'Starting stream connection to listen for skin updates.', + ); _sse.listen( apiKey: _instance!.apiKey, onSkinUpdated: FskinRemoteConfig.singleton.fetchConfig, @@ -51,6 +57,7 @@ class FlutterSkin with WidgetsBindingObserver { // When the app is resumed, fetch the latest config and restart the stream. if (state == AppLifecycleState.resumed) { try { + _logger.logMessage('Fetching latest config and restarting stream.'); await FskinRemoteConfig.singleton.fetchConfig(); } finally { _startStream(); @@ -58,6 +65,7 @@ class FlutterSkin with WidgetsBindingObserver { // When the app is paused, dispose the stream to save resources. } else if (state == AppLifecycleState.paused) { + _logger.logMessage('Disposing stream to save resources.'); _sse.dispose(); } } @@ -70,9 +78,11 @@ class FlutterSkin with WidgetsBindingObserver { ThemeData remoteTheme = ThemeData(colorScheme: colors); if (colors == null) { if (fallbackTheme != null) { + _logger.logWarning('No active theme found. Returning fallback theme.'); return fallbackTheme; } } else { + _logger.logMessage('Active theme found. Returning remote theme.'); return remoteTheme; } return null; diff --git a/lib/services/fskin_logger.dart b/lib/services/fskin_logger.dart new file mode 100644 index 0000000..1787952 --- /dev/null +++ b/lib/services/fskin_logger.dart @@ -0,0 +1,39 @@ +import 'dart:developer'; + +class FskinLogger { + static final FskinLogger _instance = FskinLogger._(); + + FskinLogger._(); + + factory FskinLogger() { + return _instance; + } + + void logMessage(String message) { + log( + 'Fskin: $message', + level: 800, + time: DateTime.now(), + name: 'FskinLogger', + ); + } + + void logError(String error, {Object? errorObject}) { + log( + 'Fskin Error: $error', + level: 1000, + time: DateTime.now(), + name: 'FskinLogger', + error: errorObject, + ); + } + + void logWarning(String warning) { + log( + 'Fskin Warning: $warning', + level: 900, + time: DateTime.now(), + name: 'FskinLogger', + ); + } +} diff --git a/lib/services/fskin_subscriber.dart b/lib/services/fskin_subscriber.dart index 355068d..064608f 100644 --- a/lib/services/fskin_subscriber.dart +++ b/lib/services/fskin_subscriber.dart @@ -2,13 +2,16 @@ import 'dart:async'; import 'dart:convert'; import 'dart:ui'; import 'package:flutter_skin/constants/fskin_constants.dart'; +import 'package:flutter_skin/services/fskin_logger.dart'; import 'package:http/http.dart' as http; class FskinSubscriber { + StreamSubscription? _subscription; late http.Client _client; int _retryCount = 0; bool _disposed = false; + final FskinLogger _logger = FskinLogger(); void listen({required String apiKey, required VoidCallback onSkinUpdated}) { _disposed = false; @@ -35,18 +38,22 @@ class FskinSubscriber { (line) { if (line.startsWith('event: skin_updated') || line.startsWith('event: project_updated')) { + _logger.logMessage('Skin update event received.'); onSkinUpdated(); } }, onDone: () { + _logger.logMessage('Stream connection closed. Retrying...'); _retry(apiKey, baseUrl, onSkinUpdated); }, onError: (_) { + _logger.logError('Stream connection error. Retrying...'); _retry(apiKey, baseUrl, onSkinUpdated); }, ); }) .catchError((_) { + _logger.logError('Stream connection error. Retrying...'); _retry(apiKey, baseUrl, onSkinUpdated); }); } diff --git a/lib/services/skin_service.dart b/lib/services/skin_service.dart index 65e78d2..825e2ee 100644 --- a/lib/services/skin_service.dart +++ b/lib/services/skin_service.dart @@ -2,12 +2,15 @@ import 'dart:convert'; import 'package:flutter_skin/constants/fskin_constants.dart'; import 'package:flutter_skin/models/project_config.dart'; +import 'package:flutter_skin/services/fskin_logger.dart'; import 'package:http/http.dart' as http; /// Singleton service class responsible for fetching the skin configuration from the remote server. /// This class abstracts the network communication and provides a method to retrieve the ProjectConfig class SkinService { + static final SkinService _instance = SkinService._(); + final FskinLogger _logger = FskinLogger(); SkinService._(); @@ -16,6 +19,7 @@ class SkinService { } Future fetchData(String apiKey) async { + _logger.logMessage('Fetching skin configuration for the provided apiKey.'); var client = http.Client(); try { var response = await client @@ -27,6 +31,7 @@ class SkinService { .timeout(const Duration(seconds: 5)); if (response.statusCode != 200) { + _logger.logError('Error fetching skin configuration: ${response.statusCode}'); return null; } @@ -34,6 +39,7 @@ class SkinService { jsonDecode(utf8.decode(response.bodyBytes)) as Map; return ProjectConfig.fromMap(decodedResponse); } catch (e) { + _logger.logError('Error fetching skin configuration: $e'); return null; } finally { client.close(); From c92313ee53b3d3939cb43961270a00787f5a2216 Mon Sep 17 00:00:00 2001 From: koukibadr Date: Sun, 5 Jul 2026 09:28:44 +0300 Subject: [PATCH 2/2] chore: update error logging with stacktrace --- lib/services/fskin_logger.dart | 1 + lib/services/fskin_subscriber.dart | 18 +++++++++++++----- lib/services/skin_service.dart | 8 +++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/services/fskin_logger.dart b/lib/services/fskin_logger.dart index 1787952..636b077 100644 --- a/lib/services/fskin_logger.dart +++ b/lib/services/fskin_logger.dart @@ -25,6 +25,7 @@ class FskinLogger { time: DateTime.now(), name: 'FskinLogger', error: errorObject, + stackTrace: StackTrace.current, ); } diff --git a/lib/services/fskin_subscriber.dart b/lib/services/fskin_subscriber.dart index 064608f..23e256a 100644 --- a/lib/services/fskin_subscriber.dart +++ b/lib/services/fskin_subscriber.dart @@ -6,7 +6,6 @@ import 'package:flutter_skin/services/fskin_logger.dart'; import 'package:http/http.dart' as http; class FskinSubscriber { - StreamSubscription? _subscription; late http.Client _client; int _retryCount = 0; @@ -46,14 +45,20 @@ class FskinSubscriber { _logger.logMessage('Stream connection closed. Retrying...'); _retry(apiKey, baseUrl, onSkinUpdated); }, - onError: (_) { - _logger.logError('Stream connection error. Retrying...'); + onError: (e) { + _logger.logError( + 'Stream connection error. Retrying...', + errorObject: e, + ); _retry(apiKey, baseUrl, onSkinUpdated); }, ); }) - .catchError((_) { - _logger.logError('Stream connection error. Retrying...'); + .catchError((e) { + _logger.logError( + 'Stream connection error. Retrying...', + errorObject: e, + ); _retry(apiKey, baseUrl, onSkinUpdated); }); } @@ -69,6 +74,9 @@ class FskinSubscriber { } void dispose() { + _logger.logMessage( + 'Disposing FskinSubscriber and closing stream connection.', + ); _disposed = true; _subscription?.cancel(); _client.close(); diff --git a/lib/services/skin_service.dart b/lib/services/skin_service.dart index 825e2ee..9b1b601 100644 --- a/lib/services/skin_service.dart +++ b/lib/services/skin_service.dart @@ -8,7 +8,6 @@ import 'package:http/http.dart' as http; /// Singleton service class responsible for fetching the skin configuration from the remote server. /// This class abstracts the network communication and provides a method to retrieve the ProjectConfig class SkinService { - static final SkinService _instance = SkinService._(); final FskinLogger _logger = FskinLogger(); @@ -31,7 +30,10 @@ class SkinService { .timeout(const Duration(seconds: 5)); if (response.statusCode != 200) { - _logger.logError('Error fetching skin configuration: ${response.statusCode}'); + _logger.logError( + 'Error fetching skin configuration: ${response.statusCode}', + errorObject: response, + ); return null; } @@ -39,7 +41,7 @@ class SkinService { jsonDecode(utf8.decode(response.bodyBytes)) as Map; return ProjectConfig.fromMap(decodedResponse); } catch (e) { - _logger.logError('Error fetching skin configuration: $e'); + _logger.logError('Error fetching skin configuration: $e', errorObject: e); return null; } finally { client.close();