From 3eeaecdf69d8776037841923215f228f70958814 Mon Sep 17 00:00:00 2001 From: basemosama Date: Wed, 1 Apr 2026 22:10:33 +0200 Subject: [PATCH 1/6] feat: Update to v0.7.0 - **Nested JSON Key Extraction**: Added `dataKey` parameter to all network requests (`get`, `getList`, `post`, `postList`, etc.) to extract nested JSON values before parsing directly via dot-notation (e.g. `dataKey: 'data.users'`). --- .flutter-plugins-dependencies | 1 - CHANGELOG.md | 3 + .../Flutter/GeneratedPluginRegistrant.swift | 4 +- example/pubspec.lock | 96 +++++++++---------- lib/src/handler/api_handler.dart | 57 +++++++++-- lib/src/playx_network_client.dart | 20 ++++ pubspec.yaml | 12 +-- 7 files changed, 126 insertions(+), 67 deletions(-) delete mode 100644 .flutter-plugins-dependencies diff --git a/.flutter-plugins-dependencies b/.flutter-plugins-dependencies deleted file mode 100644 index c8c4e3f..0000000 --- a/.flutter-plugins-dependencies +++ /dev/null @@ -1 +0,0 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_secure_storage","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"flutter_secure_storage","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage-9.2.4/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_android-2.2.21/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_android-2.4.16/","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_secure_storage_macos","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.4/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.6/","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_secure_storage_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.4.1/","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false}],"windows":[{"name":"flutter_secure_storage_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/path_provider_windows-2.3.0/","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.4.1/","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false}],"web":[{"name":"flutter_secure_storage_web","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"/Users/basemosama/.pub-cache/hosted/pub.dev/shared_preferences_web-2.4.3/","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"flutter_secure_storage","dependencies":["flutter_secure_storage_linux","flutter_secure_storage_macos","flutter_secure_storage_web","flutter_secure_storage_windows"]},{"name":"flutter_secure_storage_linux","dependencies":[]},{"name":"flutter_secure_storage_macos","dependencies":[]},{"name":"flutter_secure_storage_web","dependencies":[]},{"name":"flutter_secure_storage_windows","dependencies":["path_provider"]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2025-11-18 01:19:13.073942","version":"3.35.7","swift_package_manager_enabled":{"ios":false,"macos":false}} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 85625d2..bb98a3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.7.0 +- **Nested JSON Key Extraction**: Added `dataKey` parameter to all network requests (`get`, `getList`, `post`, `postList`, etc.) to extract nested JSON values before parsing directly via dot-notation (e.g. `dataKey: 'data.users'`). + ## 0.6.0 - Updated dependencies to their latest versions. - PATCH Request Support: Added new methods (patch and patchList) to easily perform PATCH API requests. diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 37af1fe..66f641b 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,12 +5,12 @@ import FlutterMacOS import Foundation -import flutter_secure_storage_macos +import flutter_secure_storage_darwin import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/example/pubspec.lock b/example/pubspec.lock index 8d09c46..679e311 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.9.2" dio_smart_retry: dependency: transitive description: @@ -85,18 +85,18 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" equatable: dependency: transitive description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" fake_async: dependency: transitive description: @@ -154,50 +154,50 @@ packages: dependency: transitive description: name: flutter_secure_storage - sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40 url: "https://pub.dev" source: hosted - version: "9.2.4" - flutter_secure_storage_linux: + version: "10.0.0" + flutter_secure_storage_darwin: dependency: transitive description: - name: flutter_secure_storage_linux - sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 + name: flutter_secure_storage_darwin + sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3" url: "https://pub.dev" source: hosted - version: "1.2.2" - flutter_secure_storage_macos: + version: "0.2.0" + flutter_secure_storage_linux: dependency: transitive description: - name: flutter_secure_storage_macos - sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + name: flutter_secure_storage_linux + sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.0.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "2.1.0" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: get_it - sha256: "84792561b731b6463d053e9761a5236da967c369da10b134b8585a5e18429956" + sha256: "568d62f0e68666fb5d95519743b3c24a34c7f19d834b0658c46e26d778461f66" url: "https://pub.dev" source: hosted - version: "9.0.5" + version: "9.2.1" http: dependency: transitive description: @@ -232,14 +232,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - js: - dependency: transitive - description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" - source: hosted - version: "0.6.7" leak_tracker: dependency: transitive description: @@ -292,10 +284,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -372,10 +364,10 @@ packages: dependency: transitive description: name: playx_core - sha256: b49e07caeda91353f04f68d52c751fdec0513faf46646c74ef785ca258fd1683 + sha256: "1d92e7d33d1132b2fabd779149b9a95b2dbf4b8b0f8f6ea28cad76950ef12f94" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "1.0.0" playx_network: dependency: "direct main" description: @@ -395,26 +387,26 @@ packages: dependency: transitive description: name: sentry - sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9" + sha256: "288aee3d35f252ac0dc3a4b0accbbe7212fa2867604027f2cc5bc65334afd743" url: "https://pub.dev" source: hosted - version: "9.8.0" + version: "9.16.0" sentry_dio: dependency: transitive description: name: sentry_dio - sha256: bee438fd790c534da77f0a6c9cd04c54c818184b8a54bbe7d916489c8aad56a0 + sha256: "750afa9a14297908038b91b6322efb3f42d24aa79a21eb09053425e283d6c7ef" url: "https://pub.dev" source: hosted - version: "9.8.0" + version: "9.16.0" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: @@ -512,26 +504,26 @@ packages: dependency: transitive description: name: talker - sha256: "82de443cadfb6c41d457e7774c7890a91c73af3c2f17f3f7c01670bb58d5f5a1" + sha256: c364edc0fbd6c648e1a78e6edd89cccd64df2150ca96d899ecd486b76c185042 url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.16" talker_dio_logger: dependency: transitive description: name: talker_dio_logger - sha256: "5bbecc237f3d2c4af9348da5a0086321ed6dd6bf9857d723b1f54f61c810cff2" + sha256: "0c2b6e30c4e32b0d746cf2053874bf496043c62e67b9a0271e8c0b3eca65cdaa" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.16" talker_logger: dependency: transitive description: name: talker_logger - sha256: "8218836d871ea5ab1ec616cffe3cdae84e8fb44022d5cc04c95d7b220572b8fb" + sha256: cea1b8283a28c2118a0b197057fc5beb5b0672c75e40a48725e5e452c0278ff3 url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.1.16" term_glyph: dependency: transitive description: @@ -544,10 +536,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" typed_data: dependency: transitive description: @@ -613,5 +605,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.24.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/lib/src/handler/api_handler.dart b/lib/src/handler/api_handler.dart index f966ae6..c780e46 100644 --- a/lib/src/handler/api_handler.dart +++ b/lib/src/handler/api_handler.dart @@ -48,6 +48,7 @@ class ApiHandler { Future> handleNetworkResult({ required Response response, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -93,6 +94,9 @@ class ApiHandler { shouldShowApiError: shouldShowApiErrors)); } + final actualData = + dataKey != null ? _getJsonValueOrNull(data, dataKey) : data; + try { bool useIsolate = useIsolateForMappingJson(settings); bool useWorkManager = @@ -100,13 +104,12 @@ class ApiHandler { final result = useIsolate ? await MapUtils.mapAsyncInIsolate( - data: data, + data: actualData, mapper: fromJson, useWorkManager: useWorkManager, printError: false, ) - : await fromJson(data); - + : await fromJson(actualData); return NetworkResult.success(result); // ignore: avoid_catches_without_on_clauses } catch (e, s) { @@ -135,6 +138,7 @@ class ApiHandler { Future>> handleNetworkResultForList({ required Response response, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -180,20 +184,23 @@ class ApiHandler { shouldShowApiError: shouldShowApiErrors)); } + final actualData = + dataKey != null ? _getJsonValueOrNull(data, dataKey) : data; + try { - if (data is List) { + if (actualData is List) { bool useIsolate = useIsolateForMappingJson(settings); bool useWorkManager = useWorkManagerForMappingJsonInIsolate(settings); final result = useIsolate - ? await data.asyncMapInIsolate( + ? await actualData.asyncMapInIsolate( mapper: fromJson, useWorkManager: useWorkManager, printError: false, printEachItemError: false) : await Future.wait( - data.map((item) async => await fromJson(item))); + actualData.map((item) async => await fromJson(item))); if (result.isEmpty) { _printError( @@ -467,4 +474,42 @@ class ApiHandler { shouldShowApiError: shouldShowApiErrors), ); } + + /// Internal helper to safely get a value from a JSON map by key. + /// Returns null if the JSON is null, not a map, or if the key is missing. + /// Supports dot-notation for nested keys (e.g., 'data.user.name' or 'data.users.0.name'). + static dynamic _getJsonValueOrNull(dynamic json, String key) { + if (json == null) { + return null; + } + + if (json is Map && json.containsKey(key)) { + return json[key]; + } + + if (key.contains('.')) { + final keys = key.split('.'); + dynamic current = json; + + for (final k in keys) { + if (current is Map) { + if (!current.containsKey(k)) { + return null; + } + current = current[k]; + } else if (current is List) { + final index = int.tryParse(k); + if (index == null || index < 0 || index >= current.length) { + return null; + } + current = current[index]; + } else { + return null; + } + } + return current; + } + + return null; + } } diff --git a/lib/src/playx_network_client.dart b/lib/src/playx_network_client.dart index 6b5042e..54e2a1d 100644 --- a/lib/src/playx_network_client.dart +++ b/lib/src/playx_network_client.dart @@ -103,6 +103,7 @@ class PlayxNetworkClient { CancelToken? cancelToken, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -122,6 +123,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResult( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -153,6 +155,7 @@ class PlayxNetworkClient { CancelToken? cancelToken, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -172,6 +175,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResultForList( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -258,6 +262,7 @@ class PlayxNetworkClient { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -280,6 +285,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResult( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -313,6 +319,7 @@ class PlayxNetworkClient { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -335,6 +342,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResultForList( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -366,6 +374,7 @@ class PlayxNetworkClient { bool attachCustomQuery = true, CancelToken? cancelToken, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -386,6 +395,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResult( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -417,6 +427,7 @@ class PlayxNetworkClient { bool attachCustomQuery = true, CancelToken? cancelToken, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -437,6 +448,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResultForList( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -470,6 +482,7 @@ class PlayxNetworkClient { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -492,6 +505,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResult( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -525,6 +539,7 @@ class PlayxNetworkClient { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -547,6 +562,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResultForList( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -580,6 +596,7 @@ class PlayxNetworkClient { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -602,6 +619,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResult( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -635,6 +653,7 @@ class PlayxNetworkClient { ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, + String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -657,6 +676,7 @@ class PlayxNetworkClient { return _apiHandler.handleNetworkResultForList( response: res, fromJson: fromJson, + dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, diff --git a/pubspec.yaml b/pubspec.yaml index eb37033..4835d02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: playx_network description: playx_network is a Wrapper around Dio that can perform api request with better error handling and easily get the result of any api request. -version: 0.6.0 +version: 0.7.0 homepage: https://sourcya.io repository: https://github.com/playx-flutter/playx_network issue_tracker: https://github.com/playx-flutter/playx_network/issues @@ -18,12 +18,12 @@ topics: dependencies: flutter: sdk: flutter - dio: ^5.9.0 - sentry_dio: ^9.8.0 - talker_dio_logger: ^5.0.2 - playx_core: ^0.7.4 + dio: ^5.9.2 + sentry_dio: ^9.16.0 + talker_dio_logger: ^5.1.16 + playx_core: ^1.0.0 dio_smart_retry: ^7.0.1 - dio_web_adapter: ^2.1.1 + dio_web_adapter: ^2.1.2 dev_dependencies: flutter_test: From 5a9ac4f486b4380ccab433bed9d361e6970d514b Mon Sep 17 00:00:00 2001 From: basemosama Date: Thu, 2 Apr 2026 02:45:30 +0200 Subject: [PATCH 2/6] feat: Add smart request cancellation and mapping cancellation support - Introduces `PlayxCancelTokenManager` to manage and cancel requests using tags. - Adds `cancelTag` and `cancelOld` parameters to all network request methods in `PlayxNetworkClient`. - Implements mapping cancellation in `ApiHandler` to terminate JSON parsing in isolates if a request is canceled. - Updates documentation and examples in `README.md` to reflect new features. --- CHANGELOG.md | 2 + README.md | 47 +++++++++-- lib/playx_network.dart | 1 + lib/src/handler/api_handler.dart | 70 ++++++++++++++-- .../manager/playx_cancel_token_manager.dart | 45 +++++++++++ lib/src/playx_network_client.dart | 79 ++++++++++++++++--- pubspec.yaml | 2 +- 7 files changed, 222 insertions(+), 24 deletions(-) create mode 100644 lib/src/manager/playx_cancel_token_manager.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index bb98a3e..ff470c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 0.7.0 - **Nested JSON Key Extraction**: Added `dataKey` parameter to all network requests (`get`, `getList`, `post`, `postList`, etc.) to extract nested JSON values before parsing directly via dot-notation (e.g. `dataKey: 'data.users'`). +- **Smart Request Cancellation Manager**: Added `cancelTag` parameter to all requests to easily manage and cancel multiple requests at once. Added `cancelOld` option (defaults to `true`) to cancel previous requests with the same tag automatically. +- **Mapping Cancellation**: Better support for canceling requests during JSON mapping in isolates. Now, when a request is canceled, the mapping process is also canceled to save resources and improve performance. ## 0.6.0 - Updated dependencies to their latest versions. diff --git a/README.md b/README.md index 8c9ae36..355e01d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Wrapper around [`Dio`](https://pub.dev/packages/dio) that can perform API requests with better error handling and easily get the result of any API request. ## Features -- Perform GET, POST, PUT,, and DELETE HTTP methods for retrieving from and sending data to a server. +- Perform GET, POST, PUT, PATCH, and DELETE HTTP methods for retrieving from and sending data to a server. - Better Error handling for API and [`Dio`](https://pub.dev/packages/dio) Errors with the ability to customize these errors. - Each request is wrapped by `NetworkResult` which returns whether success or failure of the API request. - No need to use try catch anymore as every request is handled. @@ -14,13 +14,12 @@ Wrapper around [`Dio`](https://pub.dev/packages/dio) that can perform API reques In `pubspec.yaml` add these lines to `dependencies` -```yaml -playx_network: ^0.6.0 +playx_network: ^0.7.0 ``` ## Usage -We can use `PlayxNetworkClient` to perform GET, POST, PUT,, and DELETE HTTP methods for retrieving from and sending data to a server. +We can use `PlayxNetworkClient` to perform GET, POST, PUT, PATCH, and DELETE HTTP methods for retrieving from and sending data to a server. To use it we need to : @@ -33,7 +32,7 @@ To use it we need to : BaseOptions( baseUrl: _baseUrl, connectTimeout: const Duration(seconds: 20), - senTimeout: const Duration(seconds: 20), + sendTimeout: const Duration(seconds: 20), ), ), //If you want to attach a token to the client or add any custom headers to all requests. @@ -125,6 +124,44 @@ Here is all available methods of `PlayxNetworkClient` : | putList | sends a `PUT` request to the given url and returns `NetworkResult` of List of Type [T] model. | | delete | sends a `DELETE` request to the given url and returns `NetworkResult` of Type [T] model.| | deleteList | sends a `DELETE` request to the given url and returns `NetworkResult` of List of Type [T] model. | +| patch | sends a `PATCH` request to the given url and returns `NetworkResult` of Type [T] model.| +| patchList | sends a `PATCH` request to the given url and returns `NetworkResult` of List of Type [T] model. | + +## Nested JSON Key Extraction +You can extract nested JSON values directly from the response before parsing using dot-notation with the `dataKey` parameter. +This is useful when you want to target a specific field in a large JSON structure without mapping the outer layers. + +```dart +final result = await _client.get( + 'forecast', + query: { + 'latitude': '30.04', + 'longitude': '31.23', + 'current_weather': 'true', + }, + // Extract 'current_weather' directly from the response + dataKey: 'current_weather', + fromJson: CurrentWeather.fromJson, +); +``` + +## Smart Request Cancellation +Manage and cancel multiple requests easily using `cancelTag`. +By default, providing a `cancelTag` will automatically cancel any previous pending requests with the same tag (`cancelOld: true`). + +```dart +// Subsequent calls with the same tag will cancel previous ones automatically +networkClient.getList( + '/search?q=query', + cancelTag: 'search_request', + fromJson: Cat.fromJson, +); + +// You can also cancel requests manually +networkClient.cancelRequestsByTag('search_request'); +``` + +Additionally, if a request is canceled while JSON mapping is occurring in an isolate, the mapping process itself is also terminated to save resources. ## Error Message customization: diff --git a/lib/playx_network.dart b/lib/playx_network.dart index 8956fa5..3581f97 100644 --- a/lib/playx_network.dart +++ b/lib/playx_network.dart @@ -11,5 +11,6 @@ export 'package:playx_network/src/models/exceptions/network_exception.dart'; export 'package:playx_network/src/models/logger/logger_settings.dart'; export 'package:playx_network/src/models/network_result.dart'; export 'package:playx_network/src/models/settings/playx_network_client_settings.dart'; +export 'package:playx_network/src/manager/playx_cancel_token_manager.dart'; export 'package:playx_network/src/playx_network_client.dart'; export 'package:sentry_dio/sentry_dio.dart'; diff --git a/lib/src/handler/api_handler.dart b/lib/src/handler/api_handler.dart index c780e46..5e09363 100644 --- a/lib/src/handler/api_handler.dart +++ b/lib/src/handler/api_handler.dart @@ -49,6 +49,7 @@ class ApiHandler { required Response response, required JsonMapper fromJson, String? dataKey, + CancelToken? cancelToken, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -102,15 +103,39 @@ class ApiHandler { bool useWorkManager = useWorkManagerForMappingJsonInIsolate(settings); - final result = useIsolate - ? await MapUtils.mapAsyncInIsolate( + final future = Future.value(useIsolate + ? MapUtils.mapAsyncInIsolate( data: actualData, mapper: fromJson, useWorkManager: useWorkManager, printError: false, ) - : await fromJson(actualData); - return NetworkResult.success(result); + : fromJson(actualData)); + + bool isFinished = false; + final cancelable = Cancelable.fromFuture(future); + cancelToken?.whenCancel.then((_) { + if (!isFinished) { + cancelable.cancel(); + } + }); + + try { + final result = await cancelable; + isFinished = true; + if (cancelToken?.isCancelled ?? false) { + return NetworkResult.error(RequestCanceledException( + errorMessage: exceptionMessages.requestCancelled)); + } + return NetworkResult.success(result); + } on CanceledError { + isFinished = true; + return NetworkResult.error(RequestCanceledException( + errorMessage: exceptionMessages.requestCancelled)); + } catch (e) { + isFinished = true; + rethrow; + } // ignore: avoid_catches_without_on_clauses } catch (e, s) { return ApiHandler.unableToProcessException( @@ -139,6 +164,7 @@ class ApiHandler { required Response response, required JsonMapper fromJson, String? dataKey, + CancelToken? cancelToken, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -193,15 +219,40 @@ class ApiHandler { bool useWorkManager = useWorkManagerForMappingJsonInIsolate(settings); - final result = useIsolate - ? await actualData.asyncMapInIsolate( + final future = useIsolate + ? actualData.asyncMapInIsolate( mapper: fromJson, useWorkManager: useWorkManager, printError: false, printEachItemError: false) - : await Future.wait( + : Future.wait( actualData.map((item) async => await fromJson(item))); + bool isFinished = false; + final cancelable = Cancelable.fromFuture(future); + cancelToken?.whenCancel.then((_) { + if (!isFinished) { + cancelable.cancel(); + } + }); + + late final List result; + try { + result = await cancelable; + isFinished = true; + if (cancelToken?.isCancelled ?? false) { + return NetworkResult.error(RequestCanceledException( + errorMessage: exceptionMessages.requestCancelled)); + } + } on CanceledError { + isFinished = true; + return NetworkResult.error(RequestCanceledException( + errorMessage: exceptionMessages.requestCancelled)); + } catch (e) { + isFinished = true; + rethrow; + } + if (result.isEmpty) { _printError( header: 'Playx Network Error :', @@ -253,6 +304,7 @@ class ApiHandler { Future> handleNetworkResultForDownload({ required Response response, required bool shouldHandleUnauthorizedRequest, + CancelToken? cancelToken, ErrorMapper? errorMapper, PlayxNetworkClientSettings? settings, }) async { @@ -283,6 +335,10 @@ class ApiHandler { errorMessage: exceptionMessages.unexpectedError, )); } else { + if (cancelToken?.isCancelled ?? false) { + return NetworkResult.error(RequestCanceledException( + errorMessage: exceptionMessages.requestCancelled)); + } return NetworkResult.success(response); } } diff --git a/lib/src/manager/playx_cancel_token_manager.dart b/lib/src/manager/playx_cancel_token_manager.dart new file mode 100644 index 0000000..25fd504 --- /dev/null +++ b/lib/src/manager/playx_cancel_token_manager.dart @@ -0,0 +1,45 @@ +import 'package:dio/dio.dart'; + +/// A simple manager that caches and controls [CancelToken]s via string tags. +/// This allows grouped request cancellation (e.g., cancelling all API requests +/// initiated from a specific screen when it is disposed). +class PlayxCancelTokenManager { + final Map _tokens = {}; + + /// Retrieves an existing [CancelToken] for the given [tag], or creates a new one + /// if it doesn't exist or was already cancelled. + /// If [cancelOld] is true, it will cancel the previous request associated with the same tag before creating a new one. (defaults to true) + CancelToken getToken(String tag, {bool cancelOld = true}) { + var token = _tokens[tag]; + if (cancelOld && token != null && !token.isCancelled) { + token.cancel('New request started with same tag'); + token = null; + } + if (token == null || token.isCancelled) { + token = CancelToken(); + _tokens[tag] = token; + } + return token; + } + + /// Cancels all requests currently associated with the given [tag]. + /// The [tag] is then removed from the pool. + void cancelRequests(String tag, {dynamic reason}) { + final token = _tokens[tag]; + if (token != null && !token.isCancelled) { + token.cancel(reason); + } + _tokens.remove(tag); + } + + /// Cancels all pending requests managed by this manager. + /// Clears the token pool entirely. + void cancelAllRequests({dynamic reason}) { + for (final token in _tokens.values) { + if (!token.isCancelled) { + token.cancel(reason); + } + } + _tokens.clear(); + } +} diff --git a/lib/src/playx_network_client.dart b/lib/src/playx_network_client.dart index 54e2a1d..f88b7bc 100644 --- a/lib/src/playx_network_client.dart +++ b/lib/src/playx_network_client.dart @@ -5,6 +5,7 @@ import 'package:playx_core/playx_core.dart'; import 'dio/dio_client.dart'; import 'handler/api_handler.dart'; +import 'manager/playx_cancel_token_manager.dart'; import 'models/exceptions/message/exception_message.dart'; import 'models/network_result.dart'; import 'models/settings/playx_network_client_settings.dart'; @@ -23,6 +24,7 @@ typedef UnauthorizedRequestHandler = void Function(Response? response); class PlayxNetworkClient { late final DioClient _dioClient; late final ApiHandler _apiHandler; + late final PlayxCancelTokenManager _cancelTokenManager; ///Settings for the client. final PlayxNetworkClientSettings settings; @@ -42,6 +44,7 @@ class PlayxNetworkClient { ErrorMapper? errorMapper, this.settings = const PlayxNetworkClientSettings(), }) { + _cancelTokenManager = PlayxCancelTokenManager(); _dioClient = DioClient( dio: dio, customHeaders: customHeaders, @@ -72,6 +75,16 @@ class PlayxNetworkClient { instanceName: 'exception_messages'); } + /// Cancels all requests concurrently associated with the given [cancelTag]. + void cancelRequestsByTag(String cancelTag, {dynamic reason}) { + _cancelTokenManager.cancelRequests(cancelTag, reason: reason); + } + + /// Cancels all pending requests managed by this client. + void cancelAllRequests({dynamic reason}) { + _cancelTokenManager.cancelAllRequests(reason: reason); + } + static Dio createDefaultDioClient({ required String baseUrl, }) { @@ -101,6 +114,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, @@ -108,6 +123,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.get( path, @@ -116,12 +132,13 @@ class PlayxNetworkClient { options: options, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResult( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -153,6 +170,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, @@ -160,6 +179,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.get( path, @@ -168,12 +188,13 @@ class PlayxNetworkClient { options: options, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResultForList( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -202,6 +223,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onReceiveProgress, JsonMapper? fromJson, bool shouldHandleUnauthorizedRequest = true, @@ -211,6 +234,7 @@ class PlayxNetworkClient { PlayxNetworkClientSettings? settings, ErrorMapper? errorMapper, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.download( path, @@ -220,7 +244,7 @@ class PlayxNetworkClient { options: options, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onReceiveProgress: onReceiveProgress, data: data, deleteOnError: deleteOnError, @@ -229,6 +253,7 @@ class PlayxNetworkClient { ); return _apiHandler.handleNetworkResultForDownload( response: res, + cancelToken: resolvedCancelToken, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, errorMapper: errorMapper, settings: settings, @@ -259,6 +284,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, @@ -267,6 +294,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.post( path, @@ -277,13 +305,14 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResult( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -316,6 +345,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, @@ -324,6 +355,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.post( path, @@ -334,13 +366,14 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResultForList( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -373,12 +406,15 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, required JsonMapper fromJson, String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.delete( path, @@ -389,11 +425,12 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResult( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -426,12 +463,15 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, required JsonMapper fromJson, String? dataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.delete( path, @@ -442,11 +482,12 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResultForList( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -479,6 +520,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, @@ -487,6 +530,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.put( path, @@ -497,13 +541,14 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResult( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -536,6 +581,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, @@ -544,6 +591,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.put( path, @@ -554,13 +602,14 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResultForList( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -593,6 +642,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, @@ -601,6 +652,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.patch( path, @@ -611,13 +663,14 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResult( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, @@ -650,6 +703,8 @@ class PlayxNetworkClient { bool attachCustomHeaders = true, bool attachCustomQuery = true, CancelToken? cancelToken, + String? cancelTag, + bool cancelOld = true, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, required JsonMapper fromJson, @@ -658,6 +713,7 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { + final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; try { final res = await _dioClient.patch( path, @@ -668,13 +724,14 @@ class PlayxNetworkClient { contentType: contentType, attachCustomHeaders: attachCustomHeaders, attachCustomQuery: attachCustomQuery, - cancelToken: cancelToken, + cancelToken: resolvedCancelToken, onSendProgress: onSendProgress, onReceiveProgress: onReceiveProgress, logSettings: settings?.logSettings, ); return _apiHandler.handleNetworkResultForList( response: res, + cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, diff --git a/pubspec.yaml b/pubspec.yaml index 4835d02..3ef1573 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: playx_network description: playx_network is a Wrapper around Dio that can perform api request with better error handling and easily get the result of any api request. -version: 0.7.0 +version: 1.0.0 homepage: https://sourcya.io repository: https://github.com/playx-flutter/playx_network issue_tracker: https://github.com/playx-flutter/playx_network/issues From 76b4bb393512861251922c69d0c66711d414f62c Mon Sep 17 00:00:00 2001 From: basemosama Date: Thu, 2 Apr 2026 03:00:27 +0200 Subject: [PATCH 3/6] refactor: update example app --- example/.gitignore | 2 + example/README.md | 6 + example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 37 ++++++ example/ios/Runner.xcodeproj/project.pbxproj | 118 +++++++++++++++++- .../xcshareddata/xcschemes/Runner.xcscheme | 3 + .../contents.xcworkspacedata | 3 + example/lib/home.dart | 110 ++++++++++++++-- example/pubspec.lock | 2 +- 10 files changed, 268 insertions(+), 17 deletions(-) create mode 100644 example/ios/Podfile.lock diff --git a/example/.gitignore b/example/.gitignore index 24476c5..6c31954 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/example/README.md b/example/README.md index a11fee4..c156440 100644 --- a/example/README.md +++ b/example/README.md @@ -2,6 +2,12 @@ This an example that demonstrates how to use `playx_network` package to perform api requests. +It includes examples for: +- Basic GET and getList requests. +- **Nested JSON Extraction**: Using `dataKey` to parse specific fields from response. +- **Smart Request Cancellation**: Using `cancelTag` for managing concurrent requests. +- **Isolate Mapping Cancellation**: Automatic termination of JSON parsing when a request is canceled. + ## Getting Started A few resources to get you started if this is your first Flutter project: diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 7c56964..1dc6cf7 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 12.0 + 13.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index e549ee2..620e46e 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '12.0' +# platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock new file mode 100644 index 0000000..d3ab58f --- /dev/null +++ b/example/ios/Podfile.lock @@ -0,0 +1,37 @@ +PODS: + - Flutter (1.0.0) + - flutter_secure_storage_darwin (10.0.0): + - Flutter + - FlutterMacOS + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + +DEPENDENCIES: + - Flutter (from `Flutter`) + - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + flutter_secure_storage_darwin: + :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + +SPEC CHECKSUMS: + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 893f67d..ccde8fa 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,12 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 4BA4FBF82B4F86D633C065CD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46D00273F5AC05DF469FB977 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + D18E9BCFC4E693E90C7F8308 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9D2C1D41F63EB9930198991 /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,14 +42,21 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0E6BBC439AD66C45D2A299E7 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 127C86C68F7F85ABC43579B4 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 20D0EA0D6E9FBE2F875A3F04 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3BDCA8ADA86107A87A3B0EB9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 46D00273F5AC05DF469FB977 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 56B10B192590E821139DE09F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 96BE21EBC2514B10F86E61FE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -55,13 +64,23 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F9D2C1D41F63EB9930198991 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 7D4FC2DBF94CF31855D3E484 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D18E9BCFC4E693E90C7F8308 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4BA4FBF82B4F86D633C065CD /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -87,6 +106,20 @@ name = Flutter; sourceTree = ""; }; + 979AC6279394404F28F72EEA /* Pods */ = { + isa = PBXGroup; + children = ( + 127C86C68F7F85ABC43579B4 /* Pods-Runner.debug.xcconfig */, + 96BE21EBC2514B10F86E61FE /* Pods-Runner.release.xcconfig */, + 3BDCA8ADA86107A87A3B0EB9 /* Pods-Runner.profile.xcconfig */, + 56B10B192590E821139DE09F /* Pods-RunnerTests.debug.xcconfig */, + 20D0EA0D6E9FBE2F875A3F04 /* Pods-RunnerTests.release.xcconfig */, + 0E6BBC439AD66C45D2A299E7 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( @@ -94,6 +127,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 979AC6279394404F28F72EEA /* Pods */, + FF8B21DE5D2A2E24B7AA6A89 /* Frameworks */, ); sourceTree = ""; }; @@ -121,6 +156,15 @@ path = Runner; sourceTree = ""; }; + FF8B21DE5D2A2E24B7AA6A89 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 46D00273F5AC05DF469FB977 /* Pods_Runner.framework */, + F9D2C1D41F63EB9930198991 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -128,8 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + F76EFF623C9AAB46320365A8 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, + 7D4FC2DBF94CF31855D3E484 /* Frameworks */, ); buildRules = ( ); @@ -145,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 72B806D54838815F9281FB43 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 441DD13B527A4A3D0E61AB47 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -238,6 +286,45 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 441DD13B527A4A3D0E61AB47 /* [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; + }; + 72B806D54838815F9281FB43 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -253,6 +340,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + F76EFF623C9AAB46320365A8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -346,7 +455,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -378,6 +487,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 56B10B192590E821139DE09F /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -395,6 +505,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 20D0EA0D6E9FBE2F875A3F04 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -410,6 +521,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 0E6BBC439AD66C45D2A299E7 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -472,7 +584,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -523,7 +635,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 8e3ca5d..e3773d4 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/example/lib/home.dart b/example/lib/home.dart index f2a190f..54d87ab 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -115,18 +115,43 @@ class _MyHomePageState extends State { _weatherMsg, style: Theme.of(context).textTheme.headlineMedium, ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Wrap( + spacing: 8, + children: [ + ElevatedButton( + onPressed: getWeatherNestedFromApi, + child: const Text('Weather Nested'), + ), + ElevatedButton( + onPressed: getCatsWithCancellation, + child: const Text('Cats (CancelTag)'), + ), + ElevatedButton( + onPressed: cancelCatsRequest, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red.shade100, + ), + child: const Text('Cancel Cats'), + ), + ], + ), + ), Expanded( - child: ListView.builder( - itemCount: _cats.length, - itemBuilder: (context, index) { - return SizedBox( - height: 200, - child: Image.network( - _cats[index].url ?? '', - fit: BoxFit.cover, - ), - ); - })) + child: ListView.builder( + itemCount: _cats.length, + itemBuilder: (context, index) { + return SizedBox( + height: 200, + child: Image.network( + _cats[index].url ?? '', + fit: BoxFit.cover, + ), + ); + }, + ), + ), ], ), ), @@ -227,4 +252,67 @@ class _MyHomePageState extends State { return NetworkResult>.error(error.error); }); } + + /// Perform GET Request and extract nested data using [dataKey]. + /// In this example we extract [CurrentWeather] directly from the response. + Future getWeatherNestedFromApi() async { + setState(() { + _isLoading = true; + }); + final result = await _client.get( + _forecastEndpoint, + query: { + 'latitude': '30.04', + 'longitude': '31.23', + 'current_weather': 'true', + }, + // Extract 'current_weather' directly from the response. + dataKey: 'current_weather', + fromJson: CurrentWeather.fromJson, + ); + + result.when(success: (weather) { + setState(() { + _isLoading = false; + _weatherMsg = "Nested: ${weather.temperature ?? 0} C"; + }); + }, error: (error) { + _weatherMsg = "Error: ${error.message}"; + setState(() { + _isLoading = false; + }); + }); + } + + /// Demonstrates request cancellation using [cancelTag]. + Future getCatsWithCancellation() async { + setState(() { + _isLoading = true; + }); + + // This request will be cancelled if another one starts with the same tag + // because [cancelOld] is true by default. + final result = await _client.getList( + _catsUrl, + query: {'limit': '5'}, + cancelTag: 'cats_request', + fromJson: Cat.fromJson, + ); + + result.when(success: (cats) { + setState(() { + _isLoading = false; + _cats = cats; + }); + }, error: (error) { + debugPrint('Error: ${error.message}'); + setState(() { + _isLoading = false; + }); + }); + } + + void cancelCatsRequest() { + _client.cancelRequestsByTag('cats_request'); + } } diff --git a/example/pubspec.lock b/example/pubspec.lock index 679e311..9ca8561 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -374,7 +374,7 @@ packages: path: ".." relative: true source: path - version: "0.6.0" + version: "1.0.0" plugin_platform_interface: dependency: transitive description: From aa17825791992c9f57f9968f69b8565fd36f71f9 Mon Sep 17 00:00:00 2001 From: basemosama Date: Thu, 2 Apr 2026 03:43:17 +0200 Subject: [PATCH 4/6] refactor: update example app --- CHANGELOG.md | 5 ++- example/lib/home.dart | 71 ++++++++++++++++++++++++++++++- example/lib/model/Weight.dart | 18 ++++++++ lib/src/handler/api_handler.dart | 16 ++++++- lib/src/playx_network_client.dart | 11 +++++ 5 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 example/lib/model/Weight.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index ff470c7..709cf48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog ## 0.7.0 -- **Nested JSON Key Extraction**: Added `dataKey` parameter to all network requests (`get`, `getList`, `post`, `postList`, etc.) to extract nested JSON values before parsing directly via dot-notation (e.g. `dataKey: 'data.users'`). -- **Smart Request Cancellation Manager**: Added `cancelTag` parameter to all requests to easily manage and cancel multiple requests at once. Added `cancelOld` option (defaults to `true`) to cancel previous requests with the same tag automatically. +- **Nested JSON Key Extraction**: Added `dataKey` parameter to all network requests to extract nested JSON values before parsing. +- **Item-Level Nested Key Extraction**: Added `itemDataKey` to all list-returning requests (`getList`, `postList`, etc.) to unwrap each list item (e.g., `itemDataKey: 'attributes'`) before passing it to the mapper. +- **Smart Request Cancellation Manager**: Added `cancelTag` parameter to manage and cancel multiple requests at once with automatic `cancelOld` support. - **Mapping Cancellation**: Better support for canceling requests during JSON mapping in isolates. Now, when a request is canceled, the mapping process is also canceled to save resources and improve performance. ## 0.6.0 diff --git a/example/lib/home.dart b/example/lib/home.dart index 54d87ab..16c90cd 100644 --- a/example/lib/home.dart +++ b/example/lib/home.dart @@ -5,6 +5,7 @@ import 'package:playx_network_example/model/Weather.dart'; import 'package:playx_network_example/model/exception/custom_exception_message.dart'; import 'model/Cat.dart'; +import 'model/Weight.dart'; class MyHomePage extends StatefulWidget { const MyHomePage({ @@ -22,6 +23,8 @@ const String _forecastEndpoint = 'forecast'; //url to override the default base url with cats endpoint. const String _catsUrl = 'https://api.thecatapi.com/v1/images/search'; +//url to cat breeds endpoint. +const String _breedsUrl = 'https://api.thecatapi.com/v1/breeds'; class _MyHomePageState extends State { //Message for displaying current weather temperature from api. @@ -30,6 +33,9 @@ class _MyHomePageState extends State { //List of cats to be displayed from api. List _cats = []; + //List of weights to be displayed from api. + List _weights = []; + // determines whether the app is loading or not bool _isLoading = false; @@ -135,13 +141,37 @@ class _MyHomePageState extends State { ), child: const Text('Cancel Cats'), ), + ElevatedButton( + onPressed: getCatBreedsFromApi, + child: const Text('Cat Breeds'), + ), ], ), ), Expanded( child: ListView.builder( - itemCount: _cats.length, + itemCount: + _weights.isNotEmpty ? _weights.length : _cats.length, itemBuilder: (context, index) { + if (_weights.isNotEmpty) { + final weight = _weights[index]; + return Card( + margin: const EdgeInsets.all(8), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Weight (Metric): ${weight.metric} kg', + style: const TextStyle( + fontWeight: FontWeight.bold)), + Text( + 'Weight (Imperial): ${weight.imperial} lb'), + ], + ), + ), + ); + } return SizedBox( height: 200, child: Image.network( @@ -159,6 +189,7 @@ class _MyHomePageState extends State { onPressed: () { getWeatherFromApi(); getCatsFromApi(); + _weights = []; }, tooltip: 'Refresh', child: const Icon(Icons.refresh), @@ -226,6 +257,7 @@ class _MyHomePageState extends State { result.when(success: (cats) { setState(() { _isLoading = false; + _weights.clear(); _cats = cats; }); @@ -284,6 +316,42 @@ class _MyHomePageState extends State { }); } + /// Perform GET Request and extract nested item data using [itemDataKey]. + /// This example Uses [itemDataKey] to extract the 'image' object from each breed item + /// in the response list, which perfectly matches our [Cat] model. + Future getCatBreedsFromApi() async { + setState(() { + _isLoading = true; + }); + + final result = await _client.getList( + _breedsUrl, + query: { + 'limit': '10', + 'page': '0', + }, + fromJson: Weight.fromJson, + // Extract the 'weight' object from each breed item in the list. + itemDataKey: 'weight', + ); + + result.when( + success: (weights) { + setState(() { + _isLoading = false; + _weights = weights; + _cats = []; // Clear cats when showing weights + }); + }, + error: (error) { + setState(() { + _isLoading = false; + _weatherMsg = "Breeds Error: ${error.message}"; + }); + }, + ); + } + /// Demonstrates request cancellation using [cancelTag]. Future getCatsWithCancellation() async { setState(() { @@ -303,6 +371,7 @@ class _MyHomePageState extends State { setState(() { _isLoading = false; _cats = cats; + _weights.clear(); }); }, error: (error) { debugPrint('Error: ${error.message}'); diff --git a/example/lib/model/Weight.dart b/example/lib/model/Weight.dart new file mode 100644 index 0000000..45245b9 --- /dev/null +++ b/example/lib/model/Weight.dart @@ -0,0 +1,18 @@ +class Weight { + String? imperial; + String? metric; + + Weight({this.imperial, this.metric}); + + Weight.fromJson(dynamic json) { + imperial = json['imperial']; + metric = json['metric']; + } + + Map toJson() { + final map = {}; + map['imperial'] = imperial; + map['metric'] = metric; + return map; + } +} diff --git a/lib/src/handler/api_handler.dart b/lib/src/handler/api_handler.dart index 5e09363..a6d0dca 100644 --- a/lib/src/handler/api_handler.dart +++ b/lib/src/handler/api_handler.dart @@ -164,6 +164,7 @@ class ApiHandler { required Response response, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, CancelToken? cancelToken, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, @@ -219,14 +220,25 @@ class ApiHandler { bool useWorkManager = useWorkManagerForMappingJsonInIsolate(settings); + final List itemsToMap; + if (itemDataKey != null) { + itemsToMap = actualData + .map((item) => + ApiHandler._getJsonValueOrNull(item, itemDataKey) ?? + item) + .toList(); + } else { + itemsToMap = actualData; + } + final future = useIsolate - ? actualData.asyncMapInIsolate( + ? itemsToMap.asyncMapInIsolate( mapper: fromJson, useWorkManager: useWorkManager, printError: false, printEachItemError: false) : Future.wait( - actualData.map((item) async => await fromJson(item))); + itemsToMap.map((item) async => await fromJson(item))); bool isFinished = false; final cancelable = Cancelable.fromFuture(future); diff --git a/lib/src/playx_network_client.dart b/lib/src/playx_network_client.dart index f88b7bc..78ea0b5 100644 --- a/lib/src/playx_network_client.dart +++ b/lib/src/playx_network_client.dart @@ -175,6 +175,7 @@ class PlayxNetworkClient { ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -197,6 +198,7 @@ class PlayxNetworkClient { cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, + itemDataKey: itemDataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -351,6 +353,7 @@ class PlayxNetworkClient { ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -376,6 +379,7 @@ class PlayxNetworkClient { cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, + itemDataKey: itemDataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -467,6 +471,7 @@ class PlayxNetworkClient { bool cancelOld = true, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -490,6 +495,7 @@ class PlayxNetworkClient { cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, + itemDataKey: itemDataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -587,6 +593,7 @@ class PlayxNetworkClient { ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -612,6 +619,7 @@ class PlayxNetworkClient { cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, + itemDataKey: itemDataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, @@ -648,6 +656,7 @@ class PlayxNetworkClient { ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -709,6 +718,7 @@ class PlayxNetworkClient { ProgressCallback? onReceiveProgress, required JsonMapper fromJson, String? dataKey, + String? itemDataKey, ErrorMapper? errorMapper, bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, @@ -734,6 +744,7 @@ class PlayxNetworkClient { cancelToken: resolvedCancelToken, fromJson: fromJson, dataKey: dataKey, + itemDataKey: itemDataKey, shouldHandleUnauthorizedRequest: shouldHandleUnauthorizedRequest, settings: settings, errorMapper: errorMapper, From ae3074acb1677cc803f407f0c49403c22dbc4ff1 Mon Sep 17 00:00:00 2001 From: basemosama Date: Thu, 2 Apr 2026 04:17:49 +0200 Subject: [PATCH 5/6] feat: Enhance network logging with custom Talker support Introduces more granular control and customization for network logging in `PlayxNetworkLoggerSettings`. Key changes: - Added `enableColors` and `displayTimestamp` options to logger settings. - Added support for providing a custom `Talker` instance for logging. - Updated `buildTalkerDioLogger` to use `developer.log` by default, avoiding the 'flutter:' prefix in logs. - Added `talker` as a dependency in `pubspec.yaml`. - Refactored `PlayxNetworkClient` for better readability of `CancelToken` resolution. --- lib/src/models/logger/logger_settings.dart | 77 ++++++++++++++++------ lib/src/playx_network_client.dart | 44 +++++++++---- pubspec.yaml | 1 + 3 files changed, 90 insertions(+), 32 deletions(-) diff --git a/lib/src/models/logger/logger_settings.dart b/lib/src/models/logger/logger_settings.dart index fcd2550..9691aa4 100644 --- a/lib/src/models/logger/logger_settings.dart +++ b/lib/src/models/logger/logger_settings.dart @@ -1,6 +1,9 @@ +import 'dart:developer' as developer; + import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:playx_core/playx_core.dart'; +import 'package:talker/talker.dart'; import 'package:talker_dio_logger/talker_dio_logger.dart'; /// Logger settings used to customize what should be logged by the application when performing a request. @@ -41,6 +44,12 @@ class PlayxNetworkLoggerSettings extends Equatable { /// Print [request.extra] if true final bool printRequestExtra; + /// Whether to enable ANSI colors in logs. + final bool enableColors; + + /// Whether to display timestamp in logs. + final bool displayTimestamp; + /// For request filtering. /// You can add your custom logic to log only specific HTTP requests [RequestOptions]. final bool Function(RequestOptions requestOptions)? requestFilter; @@ -57,6 +66,10 @@ class PlayxNetworkLoggerSettings extends Equatable { /// Case insensitive final Set hiddenHeaders; + /// Custom [Talker] instance to be used for logging. + /// If null, a default instance will be used with [developer.log] to avoid 'flutter:' prefix. + final Talker? talker; + const PlayxNetworkLoggerSettings({ this.enabled = kDebugMode, this.printResponseData = true, @@ -70,32 +83,46 @@ class PlayxNetworkLoggerSettings extends Equatable { this.printRequestData = true, this.printRequestHeaders = true, this.printRequestExtra = false, + this.enableColors = false, + this.displayTimestamp = false, this.hiddenHeaders = const {}, this.requestFilter, this.responseFilter, this.errorFilter, + this.talker, }); TalkerDioLogger buildTalkerDioLogger() { return TalkerDioLogger( - settings: TalkerDioLoggerSettings( - enabled: enabled, - printResponseData: printResponseData, - printResponseHeaders: printResponseHeaders, - printResponseMessage: printResponseMessage, - printResponseRedirects: printResponseRedirects, - printResponseTime: printResponseTime, - printErrorData: printErrorData, - printErrorHeaders: printErrorHeaders, - printErrorMessage: printErrorMessage, - printRequestData: printRequestData, - printRequestHeaders: printRequestHeaders, - printRequestExtra: printRequestExtra, - requestFilter: requestFilter, - responseFilter: responseFilter, - errorFilter: errorFilter, - hiddenHeaders: hiddenHeaders, - )); + talker: talker ?? + Talker( + settings: TalkerSettings(), + logger: TalkerLogger( + settings: TalkerLoggerSettings( + enableColors: enableColors, + ), + output: (message) => developer.log(message), + ), + ), + settings: TalkerDioLoggerSettings( + enabled: enabled, + printResponseData: printResponseData, + printResponseHeaders: printResponseHeaders, + printResponseMessage: printResponseMessage, + printResponseRedirects: printResponseRedirects, + printResponseTime: printResponseTime, + printErrorData: printErrorData, + printErrorHeaders: printErrorHeaders, + printErrorMessage: printErrorMessage, + printRequestData: printRequestData, + printRequestHeaders: printRequestHeaders, + printRequestExtra: printRequestExtra, + requestFilter: requestFilter, + responseFilter: responseFilter, + errorFilter: errorFilter, + hiddenHeaders: hiddenHeaders, + ), + ); } PlayxNetworkLoggerSettings copyWith( @@ -114,7 +141,9 @@ class PlayxNetworkLoggerSettings extends Equatable { bool Function(RequestOptions requestOptions)? requestFilter, bool Function(Response response)? responseFilter, bool Function(DioException response)? errorFilter, - Set? hiddenHeaders}) { + Set? hiddenHeaders, + bool? enableColors, + Talker? talker}) { return PlayxNetworkLoggerSettings( enabled: enabled ?? this.enabled, printResponseData: printResponseData ?? this.printResponseData, @@ -129,10 +158,13 @@ class PlayxNetworkLoggerSettings extends Equatable { printRequestData: printRequestData ?? this.printRequestData, printRequestHeaders: printRequestHeaders ?? this.printRequestHeaders, printRequestExtra: printRequestExtra ?? this.printRequestExtra, + enableColors: enableColors ?? this.enableColors, + displayTimestamp: displayTimestamp ?? this.displayTimestamp, requestFilter: requestFilter ?? this.requestFilter, responseFilter: responseFilter ?? this.responseFilter, errorFilter: errorFilter ?? this.errorFilter, - hiddenHeaders: hiddenHeaders ?? this.hiddenHeaders); + hiddenHeaders: hiddenHeaders ?? this.hiddenHeaders, + talker: talker ?? this.talker); } @override @@ -152,7 +184,10 @@ class PlayxNetworkLoggerSettings extends Equatable { requestFilter, responseFilter, errorFilter, - hiddenHeaders + hiddenHeaders, + talker, + enableColors, + displayTimestamp, ]; @override diff --git a/lib/src/playx_network_client.dart b/lib/src/playx_network_client.dart index 78ea0b5..31eefb6 100644 --- a/lib/src/playx_network_client.dart +++ b/lib/src/playx_network_client.dart @@ -123,7 +123,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.get( path, @@ -180,7 +182,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.get( path, @@ -236,7 +240,9 @@ class PlayxNetworkClient { PlayxNetworkClientSettings? settings, ErrorMapper? errorMapper, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.download( path, @@ -296,7 +302,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.post( path, @@ -358,7 +366,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.post( path, @@ -418,7 +428,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.delete( path, @@ -476,7 +488,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.delete( path, @@ -536,7 +550,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.put( path, @@ -598,7 +614,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.put( path, @@ -661,7 +679,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.patch( path, @@ -723,7 +743,9 @@ class PlayxNetworkClient { bool shouldHandleUnauthorizedRequest = true, PlayxNetworkClientSettings? settings, }) async { - final resolvedCancelToken = cancelTag != null ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) : cancelToken; + final resolvedCancelToken = cancelTag != null + ? _cancelTokenManager.getToken(cancelTag, cancelOld: cancelOld) + : cancelToken; try { final res = await _dioClient.patch( path, diff --git a/pubspec.yaml b/pubspec.yaml index 3ef1573..caf01c7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: sdk: flutter dio: ^5.9.2 sentry_dio: ^9.16.0 + talker: ^5.1.16 talker_dio_logger: ^5.1.16 playx_core: ^1.0.0 dio_smart_retry: ^7.0.1 From bb7b8e96a5342b5de5c94d0c2e5a8da1a427259a Mon Sep 17 00:00:00 2001 From: basemosama Date: Thu, 2 Apr 2026 04:19:27 +0200 Subject: [PATCH 6/6] refactor: code cleanup --- CHANGELOG.md | 2 +- lib/src/models/logger/logger_settings.dart | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 709cf48..96339cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.7.0 +## 1.0.0 - **Nested JSON Key Extraction**: Added `dataKey` parameter to all network requests to extract nested JSON values before parsing. - **Item-Level Nested Key Extraction**: Added `itemDataKey` to all list-returning requests (`getList`, `postList`, etc.) to unwrap each list item (e.g., `itemDataKey: 'attributes'`) before passing it to the mapper. - **Smart Request Cancellation Manager**: Added `cancelTag` parameter to manage and cancel multiple requests at once with automatic `cancelOld` support. diff --git a/lib/src/models/logger/logger_settings.dart b/lib/src/models/logger/logger_settings.dart index 9691aa4..b3754e8 100644 --- a/lib/src/models/logger/logger_settings.dart +++ b/lib/src/models/logger/logger_settings.dart @@ -47,9 +47,6 @@ class PlayxNetworkLoggerSettings extends Equatable { /// Whether to enable ANSI colors in logs. final bool enableColors; - /// Whether to display timestamp in logs. - final bool displayTimestamp; - /// For request filtering. /// You can add your custom logic to log only specific HTTP requests [RequestOptions]. final bool Function(RequestOptions requestOptions)? requestFilter; @@ -84,7 +81,6 @@ class PlayxNetworkLoggerSettings extends Equatable { this.printRequestHeaders = true, this.printRequestExtra = false, this.enableColors = false, - this.displayTimestamp = false, this.hiddenHeaders = const {}, this.requestFilter, this.responseFilter, @@ -159,7 +155,6 @@ class PlayxNetworkLoggerSettings extends Equatable { printRequestHeaders: printRequestHeaders ?? this.printRequestHeaders, printRequestExtra: printRequestExtra ?? this.printRequestExtra, enableColors: enableColors ?? this.enableColors, - displayTimestamp: displayTimestamp ?? this.displayTimestamp, requestFilter: requestFilter ?? this.requestFilter, responseFilter: responseFilter ?? this.responseFilter, errorFilter: errorFilter ?? this.errorFilter, @@ -187,7 +182,6 @@ class PlayxNetworkLoggerSettings extends Equatable { hiddenHeaders, talker, enableColors, - displayTimestamp, ]; @override