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.
This commit is contained in:
veto9292 2026-05-21 16:36:33 +03:30
parent 3152857060
commit 116c79e797
6 changed files with 27 additions and 24 deletions

View File

@ -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<void> 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",

View File

@ -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<bool> 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 {

View File

@ -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<OutboundInfo> build() async* {
Stream<OutboundInfo> 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<OutboundGroup>.empty()))
.map((event) => event.firstOrNull?.items.first ?? OutboundInfo());
}
ProxyRepository get _proxyRepo => ref.read(proxyRepositoryProvider);
final _urlTestThrottler = Throttler(const Duration(seconds: 1));
Future<void> urlTest(String? groupTag_) async {

View File

@ -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<OutboundGroup?> build() async* {
Stream<OutboundGroup?> 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(

View File

@ -21,7 +21,7 @@ part 'config_option_notifier.g.dart';
class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger {
@override
Future<bool> 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 {

View File

@ -10,16 +10,16 @@ part 'stats_notifier.g.dart';
@riverpod
class StatsNotifier extends _$StatsNotifier with AppLogger {
@override
Stream<SystemInfo> build() async* {
Stream<SystemInfo> 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());
}
}
}