From c47097e0522b2fb639b1e9e2df3cc2968cfef3de Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Tue, 9 Dec 2025 18:33:48 -0500 Subject: [PATCH 1/2] Caching settings --- packages/core/lib/analytics.dart | 33 ++++- packages/core/lib/state.dart | 40 +++++- packages/core/test/analytics_test.dart | 120 ++++++++++++++++ packages/core/test/cached_settings_test.dart | 140 +++++++++++++++++++ packages/core/test/mocks/mock_store.dart | 45 ++++++ packages/core/test/mocks/mocks.dart | 13 +- 6 files changed, 379 insertions(+), 12 deletions(-) create mode 100644 packages/core/test/cached_settings_test.dart create mode 100644 packages/core/test/mocks/mock_store.dart diff --git a/packages/core/lib/analytics.dart b/packages/core/lib/analytics.dart index 1508d12..a68d1f1 100644 --- a/packages/core/lib/analytics.dart +++ b/packages/core/lib/analytics.dart @@ -353,18 +353,37 @@ class Analytics with ClientMethods { } Future _fetchSettings() async { + // Try to fetch settings from network final settings = await httpClient.settingsFor(state.configuration.state.writeKey); - if (settings == null) { - log("""Could not receive settings from Segment. ${state.configuration.state.defaultIntegrationSettings != null ? 'Will use the default settings.' : 'Device mode destinations will be ignored unless you specify default settings in the client config.'}""", - kind: LogFilterKind.warning); - state.integrations.state = - state.configuration.state.defaultIntegrationSettings ?? {}; - } else { + if (settings != null) { + // Priority 1: Newly fetched settings from network final integrations = settings.integrations; - log("Received settings from Segment succesfully."); + log("Received settings from Segment successfully."); state.integrations.state = integrations; + return; + } + + // Network fetch failed, check for cached settings + final cachedSettings = await state.integrations.loadCachedSettings(); + + if (cachedSettings.isNotEmpty) { + // Priority 2: Use cached settings if available + log("Could not receive settings from Segment. Using cached settings.", + kind: LogFilterKind.warning); + // No need to set state.integrations.state as loadCachedSettings already updated it + } else if (state.configuration.state.defaultIntegrationSettings != null) { + // Priority 3: Fall back to default settings if no cache + log("Could not receive settings from Segment. Using default settings.", + kind: LogFilterKind.warning); + state.integrations.state = + state.configuration.state.defaultIntegrationSettings!; + } else { + // Priority 4: Last resort - empty map + log("Could not receive settings from Segment. Device mode destinations will not be used unless you specify default settings in the client config.", + kind: LogFilterKind.warning); + state.integrations.state = {}; } } diff --git a/packages/core/lib/state.dart b/packages/core/lib/state.dart index e8f8cb2..fb4390b 100644 --- a/packages/core/lib/state.dart +++ b/packages/core/lib/state.dart @@ -33,18 +33,19 @@ class StateManager { deepLinkData.init(errorHandler, storageJson); userInfo.init(errorHandler, storageJson); context.init(errorHandler, storageJson); + integrations.init(errorHandler, storageJson); } StateManager(Store store, System system, Configuration configuration) : system = SystemState(system), configuration = ConfigurationState(configuration), - integrations = IntegrationsState({}), + integrations = IntegrationsState(store), filters = FiltersState(store), deepLinkData = DeepLinkDataState(store), userInfo = UserInfoState(store), context = ContextState(store, configuration) { _ready = Future.wait( - [filters.ready, deepLinkData.ready, userInfo.ready, context.ready]) + [filters.ready, deepLinkData.ready, userInfo.ready, context.ready, integrations.ready]) .then((_) => _isReady = true); } } @@ -500,19 +501,50 @@ class TransformerConfigMap { } class IntegrationsState extends StateNotifier> { - IntegrationsState(super.integrations); + final Store _store; + final String _key = "integrations"; + Map? _cachedState; + + IntegrationsState(this._store) : super({}); @override Map get state => super.state; @override - set state(Map state) => super.state = state; + set state(Map newState) { + super.state = newState; + // Persist to store when state is updated + _store.setPersisted(_key, newState); + } void addIntegration(String key, Map settings) { final integrations = state; integrations[key] = settings; state = integrations; } + + // Load cached settings from store + Future> loadCachedSettings() async { + if (_cachedState != null) { + return _cachedState!; + } + + final cachedSettings = await _store.getPersisted(_key); + if (cachedSettings != null) { + _cachedState = cachedSettings; + // Update in-memory state with cached settings + super.state = cachedSettings; + return cachedSettings; + } + + return {}; + } + + // For compatibility with the ready mechanism + Future get ready => Future.value(); + void init(ErrorHandler errorHandler, bool storageJson) { + loadCachedSettings(); + } } class ConfigurationState extends StateNotifier { diff --git a/packages/core/test/analytics_test.dart b/packages/core/test/analytics_test.dart index 8d1e42b..cc27818 100644 --- a/packages/core/test/analytics_test.dart +++ b/packages/core/test/analytics_test.dart @@ -14,6 +14,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'mocks/mocks.dart'; import 'mocks/mocks.mocks.dart'; +import 'mocks/mock_store.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -137,6 +138,125 @@ void main() { ); expect(analytics, isA()); }); + + group("Integration Settings Persistence", () { + test("settings loading and fallback behavior", () async { + // Setup + final testSettings = {"test_integration": {"setting1": "value1"}}; + final defaultSettings = {"default_integration": {"enabled": true}}; + AnalyticsPlatform.instance = MockPlatform(); + LogFactory.logger = Mocks.logTarget(); + SharedPreferences.setMockInitialValues({}); + + // Test Case 1: First initialization with successful network fetch + // --------------------------------------------------------------- + // Create an in-memory store for testing + final mockStore1 = InMemoryStore(storageJson: true); + + final httpClient1 = Mocks.httpClient(); + when(httpClient1.settingsFor(any)) + .thenAnswer((_) => Future.value(SegmentAPISettings(testSettings))); + + // Initialize analytics + final analytics1 = Analytics( + Configuration("test_key"), + mockStore1, + httpClient: (_) => httpClient1, + ); + await analytics1.init(); + + // Verify network settings are used + final stateSettings1 = await analytics1.state.integrations.state; + expect(stateSettings1, equals(testSettings), + reason: "Should use settings from network"); + + // Test Case 2: Network failure with default settings + // -------------------------------------------------- + // Create an in-memory store for testing + final mockStore2 = InMemoryStore(storageJson: true); + + final failingHttpClient = Mocks.httpClient(); + when(failingHttpClient.settingsFor(any)) + .thenAnswer((_) => Future.value(null)); + + // Initialize analytics with default settings + final analytics2 = Analytics( + Configuration("test_key", + defaultIntegrationSettings: defaultSettings + ), + mockStore2, + httpClient: (_) => failingHttpClient, + ); + await analytics2.init(); + + // Verify default settings are used + final stateSettings2 = await analytics2.state.integrations.state; + expect(stateSettings2, equals(defaultSettings), + reason: "Should fall back to default settings when network fails"); + + // Test Case 3: Loading cached settings and refreshing from network + // --------------------------------------------------------------- + // Create an in-memory store with cached settings + final cachedSettings = {"cached_integration": {"setting1": "cached"}}; + final mockStore3 = InMemoryStore(storageJson: true); + + // Seed the store with cached settings + await mockStore3.setPersisted("integrations", cachedSettings); + + // Create HTTP client that returns new settings + final newNetworkSettings = {"network_integration": {"setting1": "new"}}; + final httpClient3 = Mocks.httpClient(); + when(httpClient3.settingsFor(any)) + .thenAnswer((_) => Future.value(SegmentAPISettings(newNetworkSettings))); + + // Initialize analytics + final analytics3 = Analytics( + Configuration("test_key"), + mockStore3, + httpClient: (_) => httpClient3, + ); + + // Before initialization, state would be empty + expect(analytics3.state.integrations.hasListeners, isFalse); + + // Initialize analytics + await analytics3.init(); + + // After initialization with cached settings + network fetch, we should have the network settings + final stateSettings3 = await analytics3.state.integrations.state; + expect(stateSettings3, equals(newNetworkSettings), + reason: "Should update cached settings with network settings"); + + // Test Case 4: Network failure with cached settings + // ------------------------------------------------ + // Create an in-memory store with cached settings + final cachedSettings2 = {"cached_integration2": {"setting1": "cached2"}}; + final mockStore4 = InMemoryStore(storageJson: true); + + // Seed the store with cached settings + await mockStore4.setPersisted("integrations", cachedSettings2); + + // Create failing HTTP client + final httpClient4 = Mocks.httpClient(); + when(httpClient4.settingsFor(any)) + .thenAnswer((_) => Future.value(null)); + + // Initialize analytics with default settings + final analytics4 = Analytics( + Configuration("test_key", + defaultIntegrationSettings: defaultSettings + ), + mockStore4, + httpClient: (_) => httpClient4, + ); + await analytics4.init(); + + // After initialization with network failure, we should have cached settings + final stateSettings4 = await analytics4.state.integrations.state; + expect(stateSettings4, equals(cachedSettings2), + reason: "Should use cached settings when network fails, even with default settings"); + }); + }); }); } diff --git a/packages/core/test/cached_settings_test.dart b/packages/core/test/cached_settings_test.dart new file mode 100644 index 0000000..cd0892a --- /dev/null +++ b/packages/core/test/cached_settings_test.dart @@ -0,0 +1,140 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:segment_analytics/analytics.dart'; +import 'package:segment_analytics/state.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter/widgets.dart'; +import 'package:segment_analytics/analytics_platform_interface.dart'; +import 'package:segment_analytics/logger.dart'; + +import 'mocks/mocks.dart'; +import 'mocks/mocks.mocks.dart'; +import 'mocks/mock_store.dart'; + +// Test that verifies the priority order of settings: +// 1. Network settings +// 2. Cached settings +// 3. Default settings +// 4. Empty map +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + group("Integration Settings Priority Test", () { + test("Settings priority order: network > cache > default > empty", () async { + // Setup + final networkSettings = {"network_integration": {"setting1": "network_value"}}; + final cachedSettings = {"cached_integration": {"setting1": "cached_value"}}; + final defaultSettings = {"default_integration": {"setting1": "default_value"}}; + + AnalyticsPlatform.instance = MockPlatform(); + LogFactory.logger = Mocks.logTarget(); + SharedPreferences.setMockInitialValues({}); + + // Test Case 1: First Initialization with Network Success + // --------------------------------------------------------------- + // Create an in-memory store with no cached settings + final mockStore1 = InMemoryStore(storageJson: true); + + // Note: settings will be stored directly in the InMemoryStore + + // Create HTTP client that returns network settings + final httpClient1 = Mocks.httpClient(); + when(httpClient1.settingsFor(any)) + .thenAnswer((_) => Future.value(SegmentAPISettings(networkSettings))); + + // Initialize analytics + final analytics1 = Analytics( + Configuration("test_key", + defaultIntegrationSettings: defaultSettings + ), + mockStore1, + httpClient: (_) => httpClient1, + ); + await analytics1.init(); + + // Verify network settings are used in this session + final stateSettings1 = analytics1.state.integrations.state; + expect(stateSettings1, equals(networkSettings), + reason: "Should use settings from network on first run"); + + // Verify settings were persisted to storage + final storedSettings = await mockStore1.getPersisted("integrations"); + expect(storedSettings, equals(networkSettings), + reason: "Network settings should be stored to cache"); + + // Test Case 2: Network Failure with Cached Settings + // --------------------------------------------------------------- + // Create an in-memory store with cached settings + final mockStore2 = InMemoryStore(storageJson: true); + + // Seed the store with cached settings + await mockStore2.setPersisted("integrations", networkSettings); + + // Create HTTP client that fails + final failingHttpClient = Mocks.httpClient(); + when(failingHttpClient.settingsFor(any)) + .thenAnswer((_) => Future.value(null)); + + // Initialize analytics + final analytics2 = Analytics( + Configuration("test_key", + defaultIntegrationSettings: defaultSettings + ), + mockStore2, + httpClient: (_) => failingHttpClient, + ); + await analytics2.init(); + + // Verify cached settings are used when network fails + final stateSettings2 = analytics2.state.integrations.state; + expect(stateSettings2, equals(networkSettings), + reason: "Should use cached settings when network fails"); + + // Test Case 3: No Network, No Cache - Uses Default Settings + // --------------------------------------------------------------- + final mockStore3 = InMemoryStore(storageJson: true); + + // No need to add any cached settings as the store starts empty + + final failingHttpClient2 = Mocks.httpClient(); + when(failingHttpClient2.settingsFor(any)) + .thenAnswer((_) => Future.value(null)); + + final analytics3 = Analytics( + Configuration("test_key", + defaultIntegrationSettings: defaultSettings + ), + mockStore3, + httpClient: (_) => failingHttpClient2, + ); + await analytics3.init(); + + // Verify default settings are used when no network and no cache + final stateSettings3 = analytics3.state.integrations.state; + expect(stateSettings3, equals(defaultSettings), + reason: "Should use default settings when network fails and no cache"); + + // Test Case 4: No Network, No Cache, No Default - Uses Empty Map + // --------------------------------------------------------------- + final mockStore4 = InMemoryStore(storageJson: true); + + // No need to add any cached settings as the store starts empty + + final failingHttpClient3 = Mocks.httpClient(); + when(failingHttpClient3.settingsFor(any)) + .thenAnswer((_) => Future.value(null)); + + final analytics4 = Analytics( + Configuration("test_key"), // No default settings + mockStore4, + httpClient: (_) => failingHttpClient3, + ); + await analytics4.init(); + + // Verify empty map is used as last resort + final stateSettings4 = analytics4.state.integrations.state; + expect(stateSettings4, equals({}), + reason: "Should use empty map when all else fails"); + }); + }); +} \ No newline at end of file diff --git a/packages/core/test/mocks/mock_store.dart b/packages/core/test/mocks/mock_store.dart new file mode 100644 index 0000000..6fcd377 --- /dev/null +++ b/packages/core/test/mocks/mock_store.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:segment_analytics/utils/store/store.dart'; + +/// An in-memory implementation of Store that doesn't access the file system +/// This is used for testing to avoid platform-specific file operations +class InMemoryStore implements Store { + final Map _storage = {}; + final bool storageJson; + + InMemoryStore({this.storageJson = false}); + + @override + Future?> getPersisted(String key) async { + return _storage[key]; + } + + @override + Future setPersisted(String key, Map value) async { + _storage[key] = value; + } + + @override + Future removePersisted(String key) async { + _storage.remove(key); + } + + @override + Future deletePersisted(String key) async { + _storage.remove(key); + } + + @override + Future dispose() async { + _storage.clear(); + } + + @override + Future purge() async { + _storage.clear(); + } + + @override + Future get ready => Future.value(); +} \ No newline at end of file diff --git a/packages/core/test/mocks/mocks.dart b/packages/core/test/mocks/mocks.dart index dff3751..1e63dff 100644 --- a/packages/core/test/mocks/mocks.dart +++ b/packages/core/test/mocks/mocks.dart @@ -11,6 +11,8 @@ import 'package:segment_analytics/utils/store/store.dart'; import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; +import 'mock_store.dart'; + @GenerateNiceMocks([ MockSpec(), MockSpec(), @@ -35,6 +37,12 @@ class MockPlatform extends AnalyticsPlatform { os: NativeContextOS(), screen: NativeContextScreen())); } + + @override + Stream> get linkStream { + // Return an empty stream for testing + return Stream>.empty(); + } } class Mocks { @@ -43,6 +51,9 @@ class Mocks { static MockStreamSubscription streamSubscription() => MockStreamSubscription(); static MockHTTPClient httpClient() => MockHTTPClient(); - static MockStore store() => MockStore(); + static Store store() => InMemoryStore(storageJson: true); + + // If tests need a mockito mock for specific verification + static MockStore mockStore() => MockStore(); static MockFlushPolicy flushPolicy() => MockFlushPolicy(); } From df25c916f74869a3d2752f32e23a265bccdd8504 Mon Sep 17 00:00:00 2001 From: Michael Grosse Huelsewiesche Date: Wed, 10 Dec 2025 14:38:25 -0500 Subject: [PATCH 2/2] Updates to get example running on emulator --- example/.metadata | 30 ++++ example/android/app/build.gradle | 24 +-- example/android/app/build.gradle.kts | 44 +++++ example/android/build.gradle | 16 +- example/android/build.gradle.kts | 21 +++ example/android/settings.gradle | 30 +++- example/pubspec.lock | 234 +++++++++++++++------------ 7 files changed, 255 insertions(+), 144 deletions(-) create mode 100644 example/.metadata create mode 100644 example/android/app/build.gradle.kts create mode 100644 example/android/build.gradle.kts diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 0000000..01965b7 --- /dev/null +++ b/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + - platform: android + create_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + base_revision: c23637390482d4cf9598c3ce3f2be31aa7332daf + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 2fc1239..f464d7d 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,13 +22,6 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -// START: FlutterFire Configuration -// apply plugin: 'com.google.gms.google-services' -// END: FlutterFire Configuration -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { namespace 'com.segment.analytics_example' compileSdkVersion flutter.compileSdkVersion @@ -51,7 +45,7 @@ android { applicationId "com.segment.analytics_example" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 19 + minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -74,6 +68,4 @@ flutter { source '../..' } -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} +// No additional dependencies needed diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 0000000..417d983 --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.segment.analytics_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.segment.analytics_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/example/android/build.gradle b/example/android/build.gradle index d160a0d..2101581 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,18 +1,4 @@ -buildscript { - ext.kotlin_version = '1.7.10' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.1.1' - // START: FlutterFire Configuration - // classpath 'com.google.gms:google-services:4.3.10' - // END: FlutterFire Configuration - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} +// No buildscript block is needed when using plugins DSL allprojects { repositories { diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..75bcbad 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "8.1.1" apply false + id "org.jetbrains.kotlin.android" version "1.9.22" apply false +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +include ':app' diff --git a/example/pubspec.lock b/example/pubspec.lock index 7c1563e..3161eaa 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,82 +5,90 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.19.1" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.7" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" ffi: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" file: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -90,10 +98,10 @@ packages: dependency: transitive description: name: flutter_fgbg - sha256: "08c4d2fd229e3df26083d5aecc3dea9ff4f2d188f8cd57aaf2b3f047bd08a047" + sha256: eb6da9b2047372566a6e17b505975fe5bace94af01f6fc825c4b6f81baa6c447 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.7.1" flutter_lints: dependency: "direct dev" description: @@ -116,34 +124,50 @@ packages: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.6.0" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" - js: + version: "4.1.2" + json_annotation: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "0.6.7" - json_annotation: + version: "4.9.0" + leak_tracker: dependency: transitive description: - name: json_annotation - sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.dev" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "4.8.1" + version: "3.0.1" lints: dependency: transitive description: @@ -156,66 +180,66 @@ packages: dependency: transitive description: name: logger - sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" + sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3 url: "https://pub.dev" source: hosted - version: "2.0.2+1" + version: "2.6.2" matcher: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.1" path_provider: dependency: transitive description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.19" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.2" path_provider_linux: dependency: transitive description: @@ -236,18 +260,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" platform: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -260,151 +284,143 @@ packages: dependency: "direct main" description: name: segment_analytics - sha256: fa6e2df7c5703d0d4830c0629ff6af7e2d1a7fd88c051636c5b82f3a8fc88f55 + sha256: "31e2221c6c8d16eb526c5189e773771165739e720d414931f2e462834f12fbf3" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.11" shared_preferences: dependency: transitive description: name: shared_preferences - sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.13" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.3" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 - url: "https://pub.dev" - source: hosted - version: "1.9.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" state_notifier: dependency: transitive description: name: state_notifier - sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289" + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb url: "https://pub.dev" source: hosted - version: "0.7.2+1" + version: "1.0.0" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.7.4" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" uuid: dependency: transitive description: name: uuid - sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.5.2" vector_math: dependency: transitive description: @@ -413,22 +429,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - win32: + vm_service: dependency: transitive description: - name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" sdks: - dart: ">=3.0.0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0"