From 116c79e7979d4cbecd7c4eaaea401c81220afbd5 Mon Sep 17 00:00:00 2001 From: veto9292 Date: Thu, 21 May 2026 16:36:33 +0330 Subject: [PATCH] fix: temporarily patch Riverpod lazy build-phase collision on connection status changes Temporary hotfix to resolve fatal 'setState() called during build' errors caused by lazy evaluation of dirty provider dependency chains during high-frequency status transitions. - Establishes eager evaluation by listening to `activeProxyNotifierProvider` at the root container level during bootstrap to resolve dirty state flushes in microtasks. - Refactors `serviceRunningProvider` and all downstream dependent stream/future notifiers to execute synchronously to prevent build-phase timing hops. - Defers secondary state updates using `Future.microtask()`. Note: This is a temporary architectural patch to bypass Riverpod's lazy widget evaluation mechanics under high-frequency stream updates. A full core communication redesign is recommended for future maintenance to address this natively. --- lib/bootstrap.dart | 5 +++++ .../notifier/connection_notifier.dart | 8 +++----- .../proxy/active/active_proxy_notifier.dart | 17 +++++++++-------- .../overview/proxies_overview_notifier.dart | 11 +++++------ .../config_option/config_option_notifier.dart | 2 +- lib/features/stats/notifier/stats_notifier.dart | 8 ++++---- 6 files changed, 27 insertions(+), 24 deletions(-) diff --git a/lib/bootstrap.dart b/lib/bootstrap.dart index e46d3643..5a3a7f53 100644 --- a/lib/bootstrap.dart +++ b/lib/bootstrap.dart @@ -22,6 +22,7 @@ import 'package:hiddify/features/chain/notifier/chain_profile_notifier.dart'; import 'package:hiddify/features/log/data/log_data_providers.dart'; import 'package:hiddify/features/profile/data/profile_data_providers.dart'; import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart'; +import 'package:hiddify/features/proxy/active/active_proxy_notifier.dart'; import 'package:hiddify/features/system_tray/notifier/system_tray_notifier.dart'; import 'package:hiddify/features/window/notifier/window_notifier.dart'; import 'package:hiddify/hiddifycore/hiddify_core_service_provider.dart'; @@ -98,6 +99,10 @@ Future lazyBootstrap(WidgetsBinding widgetsBinding, Environment env) async ); await _init("hiddify-core", () => container.read(hiddifyCoreServiceProvider).init()); + // Eagerly listen to activeProxyNotifierProvider to force synchronous evaluation in microtasks, + // avoiding lazy build-phase flushes and sibling dependency collisions on the Home page. + container.listen(activeProxyNotifierProvider, (previous, next) {}); + if (!kIsWeb) { // await _safeInit( // "deep link service", diff --git a/lib/features/connection/notifier/connection_notifier.dart b/lib/features/connection/notifier/connection_notifier.dart index 833f2491..5953f8b9 100644 --- a/lib/features/connection/notifier/connection_notifier.dart +++ b/lib/features/connection/notifier/connection_notifier.dart @@ -57,7 +57,7 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger { yield* _connectionRepo.watchConnectionStatus().doOnData((event) { if (event case Disconnected(connectionFailure: final _?) when PlatformUtils.isDesktop) { - ref.read(Preferences.startedByUser.notifier).update(false); + Future.microtask(() => ref.read(Preferences.startedByUser.notifier).update(false)); } loggy.info("connection status: ${event.format()}"); }); @@ -170,11 +170,9 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger { } @Riverpod(keepAlive: true) -Future serviceRunning(Ref ref) async { +bool serviceRunning(Ref ref) { // ref.watch(coreRestartSignalProvider); - return await ref - .watch(connectionNotifierProvider.selectAsync((data) => data.isConnected)) - .onError((error, stackTrace) => false); + return ref.watch(connectionNotifierProvider).valueOrNull?.isConnected ?? false; } class SingleCall { diff --git a/lib/features/proxy/active/active_proxy_notifier.dart b/lib/features/proxy/active/active_proxy_notifier.dart index bc391a51..646d0446 100644 --- a/lib/features/proxy/active/active_proxy_notifier.dart +++ b/lib/features/proxy/active/active_proxy_notifier.dart @@ -6,10 +6,11 @@ import 'package:hiddify/core/preferences/general_preferences.dart'; import 'package:hiddify/core/utils/throttler.dart'; import 'package:hiddify/features/connection/notifier/connection_notifier.dart'; import 'package:hiddify/features/proxy/data/proxy_data_providers.dart'; +import 'package:hiddify/features/proxy/data/proxy_repository.dart'; import 'package:hiddify/features/proxy/model/ip_info_entity.dart' as oldipinfo; import 'package:hiddify/features/proxy/model/proxy_failure.dart'; import 'package:hiddify/hiddifycore/generated/v2/hcore/hcore.pb.dart'; -import 'package:hiddify/hiddifycore/init_signal.dart'; + import 'package:hiddify/utils/riverpod_utils.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -32,7 +33,7 @@ class IpInfoNotifier extends _$IpInfoNotifier with AppLogger { ref.listen(serviceRunningProvider, (_, next) => _idle = false); final autoCheck = ref.watch(Preferences.autoCheckIp); - final serviceRunning = await ref.watch(serviceRunningProvider.future); + final serviceRunning = ref.watch(serviceRunningProvider); // loggy.debug( // "idle? [$_idle], forced? [$_forceCheck], connected? [$serviceRunning]", // ); @@ -74,20 +75,20 @@ class IpInfoNotifier extends _$IpInfoNotifier with AppLogger { @Riverpod(keepAlive: true) class ActiveProxyNotifier extends _$ActiveProxyNotifier with AppLogger { @override - Stream build() async* { + Stream build() { // ref.disposeDelay(const Duration(seconds: 20)); - ref.watch(coreRestartSignalProvider); - final serviceRunning = await ref.watch(serviceRunningProvider.future); + final serviceRunning = ref.watch(serviceRunningProvider); if (!serviceRunning) { - throw const ServiceNotRunning(); + return Stream.error(const ServiceNotRunning()); } - final proxyprovider = ref.watch(proxyRepositoryProvider); - yield* proxyprovider + return _proxyRepo .watchActiveProxies() .map((event) => event.getOrElse((l) => List.empty())) .map((event) => event.firstOrNull?.items.first ?? OutboundInfo()); } + ProxyRepository get _proxyRepo => ref.read(proxyRepositoryProvider); + final _urlTestThrottler = Throttler(const Duration(seconds: 1)); Future urlTest(String? groupTag_) async { diff --git a/lib/features/proxy/overview/proxies_overview_notifier.dart b/lib/features/proxy/overview/proxies_overview_notifier.dart index 3d06080c..1d84b1d2 100644 --- a/lib/features/proxy/overview/proxies_overview_notifier.dart +++ b/lib/features/proxy/overview/proxies_overview_notifier.dart @@ -10,7 +10,7 @@ import 'package:hiddify/features/connection/notifier/connection_notifier.dart'; import 'package:hiddify/features/proxy/data/proxy_data_providers.dart'; import 'package:hiddify/features/proxy/model/proxy_failure.dart'; import 'package:hiddify/hiddifycore/generated/v2/hcore/hcore.pb.dart'; -import 'package:hiddify/hiddifycore/init_signal.dart'; + import 'package:hiddify/utils/riverpod_utils.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -57,12 +57,11 @@ class ProxiesSortNotifier extends _$ProxiesSortNotifier with AppLogger { @riverpod class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger { @override - Stream build() async* { + Stream build() { ref.disposeDelay(const Duration(seconds: 15)); - ref.watch(coreRestartSignalProvider); - final serviceRunning = await ref.watch(serviceRunningProvider.future); + final serviceRunning = ref.watch(serviceRunningProvider); if (!serviceRunning) { - throw const ServiceNotRunning(); + return Stream.error(const ServiceNotRunning()); } final sortBy = ref.watch(proxiesSortNotifierProvider); // yield* ref @@ -82,7 +81,7 @@ class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger { // ), // ) // .asyncMap((proxies) async => _sortOutbounds(proxies, sortBy)); - yield* ref + return ref .watch(proxyRepositoryProvider) .watchProxies() .map( diff --git a/lib/features/settings/notifier/config_option/config_option_notifier.dart b/lib/features/settings/notifier/config_option/config_option_notifier.dart index 0d3bc65e..c797728e 100644 --- a/lib/features/settings/notifier/config_option/config_option_notifier.dart +++ b/lib/features/settings/notifier/config_option/config_option_notifier.dart @@ -21,7 +21,7 @@ part 'config_option_notifier.g.dart'; class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger { @override Future build() async { - final serviceRunning = await ref.watch(serviceRunningProvider.future); + final serviceRunning = ref.watch(serviceRunningProvider); final serviceSingboxOptions = ref.read(connectionRepositoryProvider).configOptionsSnapshot; ref.listen(ConfigOptions.singboxConfigOptions, (previous, next) async { diff --git a/lib/features/stats/notifier/stats_notifier.dart b/lib/features/stats/notifier/stats_notifier.dart index c5ee49d4..358b1dfa 100644 --- a/lib/features/stats/notifier/stats_notifier.dart +++ b/lib/features/stats/notifier/stats_notifier.dart @@ -10,16 +10,16 @@ part 'stats_notifier.g.dart'; @riverpod class StatsNotifier extends _$StatsNotifier with AppLogger { @override - Stream build() async* { + Stream build() { ref.disposeDelay(const Duration(seconds: 10)); - final serviceRunning = await ref.watch(serviceRunningProvider.future); + final serviceRunning = ref.watch(serviceRunningProvider); if (serviceRunning) { - yield* ref + return ref .watch(statsRepositoryProvider) .watchStats() .map((event) => event.getOrElse((_) => SystemInfo.create())); } else { - yield* Stream.value(SystemInfo.create()); + return Stream.value(SystemInfo.create()); } } }