From fee15cb8d291cfaba1d243d756f95f93b344ca47 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 6 May 2026 18:32:45 +0200 Subject: [PATCH] provider lifecycle hardenings --- .../domain/services/app_initialization.dart | 8 ++- .../domain/services/app_initialization.g.dart | 2 +- .../lib/features/addons/domain/providers.dart | 55 +++++++++++++--- .../features/addons/domain/providers.g.dart | 10 +-- .../providers/web_extensions_state.dart | 13 +++- .../providers/web_extensions_state.g.dart | 2 +- .../geckoview/domain/repositories/tab.dart | 56 +++++++++++++---- .../geckoview/domain/repositories/tab.g.dart | 2 +- .../domain/services/browser_addon.dart | 14 ++++- .../domain/services/browser_addon.g.dart | 4 +- .../repositories/preference_observer.dart | 9 ++- .../repositories/preference_observer.g.dart | 2 +- .../repositories/preference_settings.dart | 62 +++++++++++-------- .../repositories/preference_settings.g.dart | 6 +- .../widgets/pwa_install_button.dart | 2 + .../presentation/controllers/readerable.dart | 10 ++- .../controllers/readerable.g.dart | 2 +- .../tabs/domain/repositories/tab_search.dart | 8 ++- .../domain/repositories/tab_search.g.dart | 2 +- .../controllers/save_settings.dart | 27 +++++++- .../controllers/save_settings.g.dart | 6 +- .../features/small_web/domain/providers.dart | 14 ++--- .../small_web/domain/providers.g.dart | 4 +- .../controllers/fetch_articles.dart | 18 +++++- .../controllers/fetch_articles.g.dart | 2 +- 25 files changed, 246 insertions(+), 94 deletions(-) diff --git a/apps/weblibre/lib/domain/services/app_initialization.dart b/apps/weblibre/lib/domain/services/app_initialization.dart index 305c45a1..422da451 100644 --- a/apps/weblibre/lib/domain/services/app_initialization.dart +++ b/apps/weblibre/lib/domain/services/app_initialization.dart @@ -60,7 +60,7 @@ class AppInitializationService extends _$AppInitializationService { } Future initialize() async { - state = await Result.fromAsync(() async { + final result = await Result.fromAsync(() async { final errors = []; await ref.read(formatProvider.future); @@ -80,6 +80,12 @@ class AppInitializationService extends _$AppInitializationService { return (initialized: true, stage: null, errors: errors); }); + + if (!ref.mounted) { + return; + } + + state = result; } @override diff --git a/apps/weblibre/lib/domain/services/app_initialization.g.dart b/apps/weblibre/lib/domain/services/app_initialization.g.dart index 38cad1cf..0c417f2a 100644 --- a/apps/weblibre/lib/domain/services/app_initialization.g.dart +++ b/apps/weblibre/lib/domain/services/app_initialization.g.dart @@ -54,7 +54,7 @@ final class AppInitializationServiceProvider } String _$appInitializationServiceHash() => - r'c26f968d53b4ea18f7be890c9610c9bf9d300322'; + r'bf4c472d5b0b758d703b8ca40763cf2a59712a4c'; abstract class _$AppInitializationService extends diff --git a/apps/weblibre/lib/features/addons/domain/providers.dart b/apps/weblibre/lib/features/addons/domain/providers.dart index 6b9f473d..ac966aab 100644 --- a/apps/weblibre/lib/features/addons/domain/providers.dart +++ b/apps/weblibre/lib/features/addons/domain/providers.dart @@ -80,7 +80,14 @@ class AddonDetails extends _$AddonDetails { Future _run(Future Function() action) async { state = const AsyncLoading(); - state = await AsyncValue.guard(action); + + final result = await AsyncValue.guard(action); + + if (!ref.mounted) { + return; + } + + state = result; ref.invalidate(addonListProvider); } @@ -239,6 +246,10 @@ class AddonUpdateCheck extends _$AddonUpdateCheck { : AddonUpdateRunDone(attempt?.message); }); + if (!ref.mounted) { + return result.value ?? const AddonUpdateRunFailed(); + } + state = result; ref.invalidate(addonDetailsProvider(addonId)); ref.invalidate(lastAddonUpdateAttemptProvider(addonId)); @@ -261,26 +272,40 @@ class AddonList extends _$AddonList { } Future install(AddonInfo addon) async { - ref.read(addonBusyIdsProvider.notifier).add(addon.id); + final busyIds = ref.read(addonBusyIdsProvider.notifier); + busyIds.add(addon.id); + try { await _service.installAddon(Uri.parse(addon.downloadUrl)); + + if (!ref.mounted) { + return; + } + ref.invalidate(addonDetailsProvider(addon.id)); ref.invalidateSelf(); await future; } finally { - ref.read(addonBusyIdsProvider.notifier).remove(addon.id); + busyIds.remove(addon.id); } } Future uninstall(AddonInfo addon) async { - ref.read(addonBusyIdsProvider.notifier).add(addon.id); + final busyIds = ref.read(addonBusyIdsProvider.notifier); + busyIds.add(addon.id); + try { await _service.uninstallAddon(addon.id); + + if (!ref.mounted) { + return; + } + ref.invalidate(addonDetailsProvider(addon.id)); ref.invalidateSelf(); await future; } finally { - ref.read(addonBusyIdsProvider.notifier).remove(addon.id); + busyIds.remove(addon.id); } } @@ -292,8 +317,15 @@ class AddonList extends _$AddonList { @Riverpod() class AddonBusyIds extends _$AddonBusyIds { - void add(String id) => state = {...state, id}; - void remove(String id) => state = {...state}..remove(id); + void add(String id) { + if (!ref.mounted) return; + state = {...state, id}; + } + + void remove(String id) { + if (!ref.mounted) return; + state = {...state}..remove(id); + } @override Set build() => const {}; @@ -329,9 +361,16 @@ class PinnedAddonIds extends _$PinnedAddonIds { class BulkAddonUpdate extends _$BulkAddonUpdate { Future triggerAll() async { state = const AsyncLoading(); - state = await AsyncValue.guard(() async { + + final result = await AsyncValue.guard(() async { await ref.read(addonServiceProvider).triggerAllAddonUpdates(); }); + + if (!ref.mounted) { + return; + } + + state = result; } @override diff --git a/apps/weblibre/lib/features/addons/domain/providers.g.dart b/apps/weblibre/lib/features/addons/domain/providers.g.dart index d3913feb..96afc9e5 100644 --- a/apps/weblibre/lib/features/addons/domain/providers.g.dart +++ b/apps/weblibre/lib/features/addons/domain/providers.g.dart @@ -50,7 +50,7 @@ final class AddonDetailsProvider } } -String _$addonDetailsHash() => r'26b4a33e9d17aced1d3fb5c6ff28921f611ca5b0'; +String _$addonDetailsHash() => r'b8b7b0620d618488227fb6b8ba889279b933b707'; final class AddonDetailsFamily extends $Family with @@ -658,7 +658,7 @@ final class AddonUpdateCheckProvider } } -String _$addonUpdateCheckHash() => r'4ef375b5cd9b0eb89fbbdaff3f93a35482191af5'; +String _$addonUpdateCheckHash() => r'496cf9756101c9c36fa4b90f1fd92627aee92177'; final class AddonUpdateCheckFamily extends $Family with @@ -739,7 +739,7 @@ final class AddonListProvider AddonList create() => AddonList(); } -String _$addonListHash() => r'7625b69a433d073e186571453e39b557d8b48a5d'; +String _$addonListHash() => r'1e60b0b04702e6ed6143ccf19d9e4140d1a1deec'; abstract class _$AddonList extends $AsyncNotifier> { FutureOr> build(); @@ -791,7 +791,7 @@ final class AddonBusyIdsProvider } } -String _$addonBusyIdsHash() => r'6f9761320d42b2b5132936797617fdb13b718dd3'; +String _$addonBusyIdsHash() => r'aac8fd8784ba296129ffdb17c2d1355c1546140f'; abstract class _$AddonBusyIds extends $Notifier> { Set build(); @@ -895,7 +895,7 @@ final class BulkAddonUpdateProvider } } -String _$bulkAddonUpdateHash() => r'2605711734f9eb6af70e721a4337556a7ea85512'; +String _$bulkAddonUpdateHash() => r'ec3cdc136c0ad4a6687516bf14912fd5b96b11b4'; abstract class _$BulkAddonUpdate extends $Notifier> { AsyncValue build(); diff --git a/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.dart b/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.dart index 6032cb0f..c152c9e0 100644 --- a/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.dart +++ b/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.dart @@ -39,6 +39,10 @@ class WebExtensionsState extends _$WebExtensionsState { : _imageCache = LRUCache(50, onEvict: (image) => image.dispose()); void _onExtensionUpdate(ExtensionDataEvent event) { + if (!ref.mounted) { + return; + } + final ExtensionDataEvent(:extensionId, :data) = event; if (data != null) { @@ -81,6 +85,11 @@ class WebExtensionsState extends _$WebExtensionsState { final image = await tryDecodeImage(bytes); + if (!ref.mounted) { + image?.dispose(); + return; + } + if (image != null) { // set() will evict the old entry via onEvict callback, which handles disposal _imageCache.set(extensionId, image); @@ -185,13 +194,13 @@ class WebExtensionsState extends _$WebExtensionsState { }, ); - ref.onDispose(() async { + ref.onDispose(() { // Dispose all cached images _imageCache.clear(); // Cancel all stream subscriptions for (final sub in subscriptions) { - await sub.cancel(); + unawaited(sub.cancel()); } }); diff --git a/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.g.dart b/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.g.dart index 7c390817..d70a38fb 100644 --- a/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.g.dart +++ b/apps/weblibre/lib/features/geckoview/domain/providers/web_extensions_state.g.dart @@ -62,7 +62,7 @@ final class WebExtensionsStateProvider } String _$webExtensionsStateHash() => - r'13cbbea409b5b643eb6766689f6b23a4d1701204'; + r'e067323e9a0a466e46e0c4d529667950ee62d4ca'; final class WebExtensionsStateFamily extends $Family with diff --git a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart index ebbffcb1..3bc8a47e 100644 --- a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart +++ b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.dart @@ -784,8 +784,9 @@ class TabRepository extends _$TabRepository { final containerSiteAssignementSub = eventSerivce.siteAssignementEvent.listen( (event) async { - if (event.tabId != null) { - final tabState = ref.read(tabStatesProvider)[event.tabId]; + final tabId = event.tabId; + if (tabId != null) { + final tabState = ref.read(tabStatesProvider)[tabId]; if (tabState != null) { final uri = Uri.parse(event.url); final originUri = event.originUrl.mapNotNull(Uri.parse); @@ -793,52 +794,81 @@ class TabRepository extends _$TabRepository { final targetContainerId = await ref .read(containerRepositoryProvider.notifier) .siteAssignedContainerId(Uri.parse(uri.origin)); + + if (!ref.mounted) { + return; + } + final containerData = await targetContainerId.mapNotNull( (id) => ref .read(containerRepositoryProvider.notifier) .getContainerData(id), ); + if (!ref.mounted) { + return; + } + if (containerData != null) { + final currentTabState = ref.read(tabStatesProvider)[tabId]; + if (currentTabState == null) { + logger.w('Could not get tab for assignement ${event.url}'); + return; + } + final tabIsEmpty = - tabState.url == TabState.defaultUrl && - tabState.historyState.items.isEmpty; + currentTabState.url == TabState.defaultUrl && + currentTabState.historyState.items.isEmpty; if (event.blocked || tabIsEmpty) { await addTab( url: uri, - tabMode: tabState.tabMode, + tabMode: currentTabState.tabMode, containerSelection: TabContainerSelection.specific( containerData, ), - parentId: tabState.id, + parentId: currentTabState.id, selectTab: true, ); - if (tabState.historyState.items.isEmpty) { - await closeTab(tabState.id); + if (!ref.mounted) { + return; + } + + if (currentTabState.historyState.items.isEmpty) { + await closeTab(currentTabState.id); } } else { final tabContainerId = await ref .read(tabDataRepositoryProvider.notifier) - .getTabContainerId(tabState.id); + .getTabContainerId(currentTabState.id); + + if (!ref.mounted) { + return; + } + + final latestTabState = ref.read(tabStatesProvider)[tabId]; + if (latestTabState == null) { + logger.w('Could not get tab for assignement ${event.url}'); + return; + } if (targetContainerId != tabContainerId) { if (originUri == null) { await ref .read(tabDataRepositoryProvider.notifier) - .assignContainer(tabState.id, containerData); - } else if (tabState.url == originUri) { + .assignContainer(latestTabState.id, containerData); + } else if (latestTabState.url == originUri) { await ref .read(tabDataRepositoryProvider.notifier) .assignContainer( - tabState.id, + latestTabState.id, containerData, closeOldTab: false, ); } else { logger.w( - 'Could not match origin url for assignment ${tabState.url} to request ${event.originUrl}', + 'Could not match origin url for assignment ${latestTabState.url} to request ${event.originUrl}', ); } } diff --git a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart index 520f1fbb..c60e7a3d 100644 --- a/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart +++ b/apps/weblibre/lib/features/geckoview/domain/repositories/tab.g.dart @@ -41,7 +41,7 @@ final class TabRepositoryProvider } } -String _$tabRepositoryHash() => r'a68941d373f348201e3d8b1cab26343acca3852f'; +String _$tabRepositoryHash() => r'f458baaf9061143a668470dccd57f32664728951'; abstract class _$TabRepository extends $Notifier { void build(); diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.dart index 0bed280c..e481bd7b 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.dart @@ -42,20 +42,24 @@ class AllowUnsignedExtensions extends _$AllowUnsignedExtensions { await fixator.unregister(_signatureRequiredPref); await GeckoPrefService().applyPrefs({_signatureRequiredPref: true}); } + + if (!ref.mounted) { + return; + } + state = AsyncData(allow); } @override FutureOr build() async { + final fixator = ref.read(preferenceFixatorProvider.notifier); final prefs = await GeckoPrefService().getPrefs([_signatureRequiredPref]); final pref = prefs[_signatureRequiredPref]; final allowUnsigned = pref?.value == false; // Re-register with fixator to prevent Gecko from resetting if (allowUnsigned) { - await ref - .read(preferenceFixatorProvider.notifier) - .register(_signatureRequiredPref, false); + await fixator.register(_signatureRequiredPref, false); } return allowUnsigned; @@ -68,6 +72,10 @@ class AddonAutoUpdate extends _$AddonAutoUpdate { final service = ref.read(addonServiceProvider); await service.setAddonAutoUpdateEnabled(enabled: enabled); + if (!ref.mounted) { + return; + } + state = AsyncData(enabled); } diff --git a/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.g.dart b/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.g.dart index 69942bb7..aaf6ab36 100644 --- a/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/browser/domain/services/browser_addon.g.dart @@ -34,7 +34,7 @@ final class AllowUnsignedExtensionsProvider } String _$allowUnsignedExtensionsHash() => - r'3b67f51cabf8e2f37d992320c5b93bb61c6dbe1f'; + r'ba282219edc9149da886371d873d2a5ee0dfb6dd'; abstract class _$AllowUnsignedExtensions extends $AsyncNotifier { FutureOr build(); @@ -78,7 +78,7 @@ final class AddonAutoUpdateProvider AddonAutoUpdate create() => AddonAutoUpdate(); } -String _$addonAutoUpdateHash() => r'89791e8b771da715b068bbdbe5c3c24a3dad4194'; +String _$addonAutoUpdateHash() => r'0a2661316d1d1345e81df2552925eda3c2bf6ec1'; abstract class _$AddonAutoUpdate extends $AsyncNotifier { FutureOr build(); diff --git a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.dart b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.dart index e89835f8..264b9501 100644 --- a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.dart +++ b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.dart @@ -31,11 +31,16 @@ class PreferenceChangeListener extends _$PreferenceChangeListener { Stream build() async* { final events = ref.watch(eventServiceProvider); + ref.onDispose(() { + unawaited(GeckoPrefService().stopObserveChanges()); + }); + await GeckoPrefService().startObserveChanges(); - ref.onDispose(() async { + if (!ref.mounted) { await GeckoPrefService().stopObserveChanges(); - }); + return; + } yield* events.prefUpdateEvent; } diff --git a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.g.dart b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.g.dart index 91aeeea5..45e6d38f 100644 --- a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_observer.g.dart @@ -34,7 +34,7 @@ final class PreferenceChangeListenerProvider } String _$preferenceChangeListenerHash() => - r'de9759b32a6a6b9bab5656137a365da78633f866'; + r'baf2abe1948998a043a8473547a225582dbc8c7b'; abstract class _$PreferenceChangeListener extends $StreamNotifier { Stream build(); diff --git a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart index 46449a88..32bd8cae 100644 --- a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart +++ b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.dart @@ -243,8 +243,8 @@ class _PreferenceRepository extends _$PreferenceRepository { _prefSubject = BehaviorSubject>(); - ref.onDispose(() async { - await _prefSubject.close(); + ref.onDispose(() { + unawaited(_prefSubject.close()); }); ref.onAddListener(() { @@ -260,19 +260,21 @@ class _PreferenceRepository extends _$PreferenceRepository { @Riverpod() class UnifiedPreferenceSettingsRepository extends _$UnifiedPreferenceSettingsRepository { - Map? _statelessGroups; - Future apply() async { final preferenceRepository = ref.read( _preferenceRepositoryProvider(partition).notifier, ); - _statelessGroups = await ref.read( + final groups = await ref.read( _preferenceSettingGroupsProvider(partition).future, ); + if (!ref.mounted) { + return; + } + final prefs = { - for (final group in _statelessGroups!.values) + for (final group in groups.values) ...Map.fromEntries( group.settings.entries .where((e) => !e.value.requireUserOptIn) @@ -288,11 +290,15 @@ class UnifiedPreferenceSettingsRepository _preferenceRepositoryProvider(partition).notifier, ); - _statelessGroups = await ref.read( + final groups = await ref.read( _preferenceSettingGroupsProvider(partition).future, ); - final prefs = _statelessGroups!.values + if (!ref.mounted) { + return; + } + + final prefs = groups.values .map((group) => group.settings.keys) .flattened .toList(); @@ -304,14 +310,13 @@ class UnifiedPreferenceSettingsRepository Stream> build( PreferencePartition partition, ) async* { - _statelessGroups = await ref.watch( + final prefStream = ref.watch(_preferenceRepositoryProvider(partition)); + final groups = await ref.watch( _preferenceSettingGroupsProvider(partition).future, ); - final prefStream = ref.watch(_preferenceRepositoryProvider(partition)); - yield* prefStream.map( - (prefs) => _statelessGroups!.map( + (prefs) => groups.map( (groupName, group) => MapEntry( groupName, group.copyWith.settings( @@ -329,26 +334,28 @@ class UnifiedPreferenceSettingsRepository @Riverpod() class PreferenceSettingsGroupRepository extends _$PreferenceSettingsGroupRepository { - PreferenceSettingGroup? _statelessSettingGroup; - Future apply({List? filter}) async { final preferenceRepository = ref.read( _preferenceRepositoryProvider(partition).notifier, ); - _statelessSettingGroup ??= await ref.read( + final settingGroup = await ref.read( _preferenceSettingGroupProvider(partition, groupName).future, ); + if (!ref.mounted) { + return; + } + final prefs = Map.fromEntries( filter?.map( (e) => MapEntry( e, - _statelessSettingGroup!.settings[e]?.value ?? + settingGroup.settings[e]?.value ?? (throw Exception('Preference not part of group')), ), ) ?? - _statelessSettingGroup!.settings.entries + settingGroup.settings.entries .where((e) => !e.value.requireUserOptIn) .map((e) => MapEntry(e.key, e.value.value)), ); @@ -361,19 +368,21 @@ class PreferenceSettingsGroupRepository _preferenceRepositoryProvider(partition).notifier, ); - _statelessSettingGroup ??= await ref.read( + final settingGroup = await ref.read( _preferenceSettingGroupProvider(partition, groupName).future, ); + if (!ref.mounted) { + return; + } + if (filter != null && - filter.any( - (value) => !_statelessSettingGroup!.settings.containsKey(value), - )) { + filter.any((value) => !settingGroup.settings.containsKey(value))) { throw Exception('Preference not part of group'); } await preferenceRepository.resetPrefs( - filter ?? _statelessSettingGroup!.settings.keys.toList(), + filter ?? settingGroup.settings.keys.toList(), ); } @@ -382,15 +391,14 @@ class PreferenceSettingsGroupRepository PreferencePartition partition, String groupName, ) async* { - _statelessSettingGroup = await ref.watch( + final prefStream = ref.watch(_preferenceRepositoryProvider(partition)); + final settingGroup = await ref.watch( _preferenceSettingGroupProvider(partition, groupName).future, ); - final prefStream = ref.watch(_preferenceRepositoryProvider(partition)); - yield* prefStream.map( - (prefs) => _statelessSettingGroup!.copyWith.settings( - _statelessSettingGroup!.settings.map( + (prefs) => settingGroup.copyWith.settings( + settingGroup.settings.map( (key, value) => MapEntry(key, value.copyWith.current(prefs[key])), ), ), diff --git a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.g.dart b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.g.dart index 584a0019..abd590f3 100644 --- a/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/preferences/data/repositories/preference_settings.g.dart @@ -334,7 +334,7 @@ final class _PreferenceRepositoryProvider } String _$_preferenceRepositoryHash() => - r'c416d7684f1e8f24dd9e5fc98ecb6312c07d938f'; + r'ca5ccbe1c3f5f1576e3e7ba359c3cd1324281a97'; final class _PreferenceRepositoryFamily extends $Family with @@ -441,7 +441,7 @@ final class UnifiedPreferenceSettingsRepositoryProvider } String _$unifiedPreferenceSettingsRepositoryHash() => - r'03c8c2f247974ff32bba14b4b34857a0f14f6f37'; + r'c544b5815ec676ddb6cde1ffeb8c9ef4a7dc4469'; final class UnifiedPreferenceSettingsRepositoryFamily extends $Family with @@ -554,7 +554,7 @@ final class PreferenceSettingsGroupRepositoryProvider } String _$preferenceSettingsGroupRepositoryHash() => - r'b5c439643ec78094122c33983e58d0977641832a'; + r'c8335b39c4c9fa95c4d415231b103a2feff97d26'; final class PreferenceSettingsGroupRepositoryFamily extends $Family with diff --git a/apps/weblibre/lib/features/geckoview/features/pwa/presentation/widgets/pwa_install_button.dart b/apps/weblibre/lib/features/geckoview/features/pwa/presentation/widgets/pwa_install_button.dart index affd1cc6..7403b995 100644 --- a/apps/weblibre/lib/features/geckoview/features/pwa/presentation/widgets/pwa_install_button.dart +++ b/apps/weblibre/lib/features/geckoview/features/pwa/presentation/widgets/pwa_install_button.dart @@ -44,6 +44,7 @@ Future showPwaInstallDialog(BuildContext context, WidgetRef ref) async { url: url, ); + if (!context.mounted) return; if (config == null) return; final name = config.name; @@ -106,6 +107,7 @@ Future showShortcutInstallDialog( showAppOption: showAppOption, ); + if (!context.mounted) return; if (config == null) return; final name = config.name; diff --git a/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.dart b/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.dart index 8a560bba..d82e5105 100644 --- a/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.dart +++ b/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.dart @@ -34,7 +34,15 @@ class ReaderableScreenController extends _$ReaderableScreenController { final eventChange = ref.read(eventServiceProvider).readerableEvents.first; final toggle = _service.toggleReaderView(enable); - state = await AsyncValue.guard(() => Future.wait([toggle, eventChange])); + final result = await AsyncValue.guard( + () => Future.wait([toggle, eventChange]), + ); + + if (!ref.mounted) { + return; + } + + state = result; } @override diff --git a/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.g.dart b/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.g.dart index 4c974bac..c2cbd784 100644 --- a/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/readerview/presentation/controllers/readerable.g.dart @@ -35,7 +35,7 @@ final class ReaderableScreenControllerProvider } String _$readerableScreenControllerHash() => - r'62b2167ff8f9fe21c3cf3339e135a50aa684160c'; + r'46206cfb431c5d36dc6351a8fc52c3b98b86dd0f'; abstract class _$ReaderableScreenController extends $AsyncNotifier { FutureOr build(); diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart b/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart index 5cbcb286..102c29cb 100644 --- a/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart +++ b/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.dart @@ -38,7 +38,7 @@ class TabSearchRepository extends _$TabSearchRepository { String ellipsis = '…', }) async { if (input.isNotEmpty) { - state = await AsyncValue.guard(() async { + final result = await AsyncValue.guard(() async { return ( query: input, results: await ref @@ -55,6 +55,12 @@ class TabSearchRepository extends _$TabSearchRepository { .get(), ); }); + + if (!ref.mounted) { + return; + } + + state = result; } else { state = const AsyncValue.data(null); } diff --git a/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart b/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart index 1268fa35..818ecc89 100644 --- a/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart +++ b/apps/weblibre/lib/features/geckoview/features/tabs/domain/repositories/tab_search.g.dart @@ -55,7 +55,7 @@ final class TabSearchRepositoryProvider } String _$tabSearchRepositoryHash() => - r'473057d6a9f1e76d6b7fec13f710189d758bc924'; + r'a05922f7c487105f46abbd272fbc2ed72994c332'; final class TabSearchRepositoryFamily extends $Family with diff --git a/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.dart b/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.dart index 90f93d85..24f006d6 100644 --- a/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.dart +++ b/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.dart @@ -33,11 +33,18 @@ class SaveGeneralSettingsController extends _$SaveGeneralSettingsController { Future save(UpdateGeneralSettingsFunc updateSettings) async { state = const AsyncLoading(); - state = await AsyncValue.guard( + + final result = await AsyncValue.guard( () => ref .read(generalSettingsRepositoryProvider.notifier) .updateSettings(updateSettings), ); + + if (!ref.mounted) { + return; + } + + state = result; } } @@ -50,11 +57,18 @@ class SaveEngineSettingsController extends _$SaveEngineSettingsController { Future save(UpdateEngineSettingsFunc updateSettings) async { state = const AsyncLoading(); - state = await AsyncValue.guard( + + final result = await AsyncValue.guard( () => ref .read(engineSettingsRepositoryProvider.notifier) .updateSettings(updateSettings), ); + + if (!ref.mounted) { + return; + } + + state = result; } } @@ -67,10 +81,17 @@ class SaveTorSettingsController extends _$SaveTorSettingsController { Future save(UpdateTorSettingsFunc updateSettings) async { state = const AsyncLoading(); - state = await AsyncValue.guard( + + final result = await AsyncValue.guard( () => ref .read(torSettingsRepositoryProvider.notifier) .updateSettings(updateSettings), ); + + if (!ref.mounted) { + return; + } + + state = result; } } diff --git a/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.g.dart b/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.g.dart index ea218652..13c0c5dd 100644 --- a/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.g.dart +++ b/apps/weblibre/lib/features/settings/presentation/controllers/save_settings.g.dart @@ -35,7 +35,7 @@ final class SaveGeneralSettingsControllerProvider } String _$saveGeneralSettingsControllerHash() => - r'b75c9c52452a9cb687f69dad1523f1ed5205847e'; + r'9a6d663234c1a1750bf90bf29b4664e4d86861c0'; abstract class _$SaveGeneralSettingsController extends $AsyncNotifier { FutureOr build(); @@ -81,7 +81,7 @@ final class SaveEngineSettingsControllerProvider } String _$saveEngineSettingsControllerHash() => - r'd3d74a6aadc89532b1d81c424d92efbfa43002d5'; + r'fbe89c8f9772e50fc8ada3d41a233ba5650a7982'; abstract class _$SaveEngineSettingsController extends $AsyncNotifier { FutureOr build(); @@ -126,7 +126,7 @@ final class SaveTorSettingsControllerProvider } String _$saveTorSettingsControllerHash() => - r'd8aa04d7ae60c97bdad6c072f894189509dd35d3'; + r'bc8bac2f2d777025fa3e2c85badcbc095a9e3ad1'; abstract class _$SaveTorSettingsController extends $AsyncNotifier { FutureOr build(); diff --git a/apps/weblibre/lib/features/small_web/domain/providers.dart b/apps/weblibre/lib/features/small_web/domain/providers.dart index fa5b1c0e..c5c2a834 100644 --- a/apps/weblibre/lib/features/small_web/domain/providers.dart +++ b/apps/weblibre/lib/features/small_web/domain/providers.dart @@ -31,11 +31,9 @@ part 'providers.g.dart'; @Riverpod(keepAlive: true) Future kagiSourceService(Ref ref) async { + final db = ref.watch(smallWebDatabaseProvider); final categories = await ref.watch(kagiCategoriesProvider.future); - return KagiSourceService( - ref.watch(smallWebDatabaseProvider), - categories.remap, - ); + return KagiSourceService(db, categories.remap); } @Riverpod(keepAlive: true) @@ -45,13 +43,11 @@ WanderSourceService wanderSourceService(Ref ref) { @Riverpod(keepAlive: true) Future smallWebDiscoverService(Ref ref) async { + final db = ref.watch(smallWebDatabaseProvider); + final wanderService = ref.watch(wanderSourceServiceProvider); final kagiService = await ref.watch(kagiSourceServiceProvider.future); - return SmallWebDiscoverService( - ref.watch(smallWebDatabaseProvider), - kagiService, - ref.watch(wanderSourceServiceProvider), - ); + return SmallWebDiscoverService(db, kagiService, wanderService); } @Riverpod() diff --git a/apps/weblibre/lib/features/small_web/domain/providers.g.dart b/apps/weblibre/lib/features/small_web/domain/providers.g.dart index e301a5bb..5e15526e 100644 --- a/apps/weblibre/lib/features/small_web/domain/providers.g.dart +++ b/apps/weblibre/lib/features/small_web/domain/providers.g.dart @@ -48,7 +48,7 @@ final class KagiSourceServiceProvider } } -String _$kagiSourceServiceHash() => r'7f0cca556d22cc65356660f3e59c976a35d40d37'; +String _$kagiSourceServiceHash() => r'5e8aef50353b959d681675feba9eba75bb91bdff'; @ProviderFor(wanderSourceService) final wanderSourceServiceProvider = WanderSourceServiceProvider._(); @@ -138,7 +138,7 @@ final class SmallWebDiscoverServiceProvider } String _$smallWebDiscoverServiceHash() => - r'14fba9eb1c2aa51fa1fac2c2c764ea400603e8ca'; + r'eefefdc1b22c6f7e57e61a0c1dafa2b8080ab150'; @ProviderFor(smallWebRecentVisits) final smallWebRecentVisitsProvider = SmallWebRecentVisitsFamily._(); diff --git a/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.dart b/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.dart index da4bd67b..96a74174 100644 --- a/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.dart +++ b/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.dart @@ -28,7 +28,8 @@ part 'fetch_articles.g.dart'; class FetchArticlesController extends _$FetchArticlesController { Future fetchAllArticles() async { state = const AsyncLoading(); - state = await AsyncValue.guard(() async { + + final result = await AsyncValue.guard(() async { final feedRepository = ref.read(feedRepositoryProvider.notifier); final feeds = await feedRepository.getAllFeeds(); @@ -52,11 +53,18 @@ class FetchArticlesController extends _$FetchArticlesController { }).toList(), ); }); + + if (!ref.mounted) { + return; + } + + state = result; } Future fetchFeedArticles(Uri uri) async { state = const AsyncLoading(); - state = await AsyncValue.guard(() async { + + final result = await AsyncValue.guard(() async { final feedRepository = ref.read(feedRepositoryProvider.notifier); final result = await ref.read(feedReaderProvider.notifier).parseFeed(uri); @@ -64,6 +72,12 @@ class FetchArticlesController extends _$FetchArticlesController { await feedRepository.upsertArticles(result.articleData); await feedRepository.touchFeedFetched(uri); }); + + if (!ref.mounted) { + return; + } + + state = result; } @override diff --git a/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.g.dart b/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.g.dart index 63b5eea5..95c70b9d 100644 --- a/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.g.dart +++ b/apps/weblibre/lib/features/web_feed/presentation/controllers/fetch_articles.g.dart @@ -42,7 +42,7 @@ final class FetchArticlesControllerProvider } String _$fetchArticlesControllerHash() => - r'5dcb9de003bc911d365c6a8ef107bf1bf446755b'; + r'b8a1813f3497af731f20a48693b700eb3d9aeceb'; abstract class _$FetchArticlesController extends $Notifier> { AsyncValue build();