Skip to content

Commit 116c79e

Browse files
committed
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.
1 parent 3152857 commit 116c79e

6 files changed

Lines changed: 27 additions & 24 deletions

File tree

lib/bootstrap.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import 'package:hiddify/features/chain/notifier/chain_profile_notifier.dart';
2222
import 'package:hiddify/features/log/data/log_data_providers.dart';
2323
import 'package:hiddify/features/profile/data/profile_data_providers.dart';
2424
import 'package:hiddify/features/profile/notifier/active_profile_notifier.dart';
25+
import 'package:hiddify/features/proxy/active/active_proxy_notifier.dart';
2526
import 'package:hiddify/features/system_tray/notifier/system_tray_notifier.dart';
2627
import 'package:hiddify/features/window/notifier/window_notifier.dart';
2728
import 'package:hiddify/hiddifycore/hiddify_core_service_provider.dart';
@@ -98,6 +99,10 @@ Future<void> lazyBootstrap(WidgetsBinding widgetsBinding, Environment env) async
9899
);
99100
await _init("hiddify-core", () => container.read(hiddifyCoreServiceProvider).init());
100101

102+
// Eagerly listen to activeProxyNotifierProvider to force synchronous evaluation in microtasks,
103+
// avoiding lazy build-phase flushes and sibling dependency collisions on the Home page.
104+
container.listen(activeProxyNotifierProvider, (previous, next) {});
105+
101106
if (!kIsWeb) {
102107
// await _safeInit(
103108
// "deep link service",

lib/features/connection/notifier/connection_notifier.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
5757

5858
yield* _connectionRepo.watchConnectionStatus().doOnData((event) {
5959
if (event case Disconnected(connectionFailure: final _?) when PlatformUtils.isDesktop) {
60-
ref.read(Preferences.startedByUser.notifier).update(false);
60+
Future.microtask(() => ref.read(Preferences.startedByUser.notifier).update(false));
6161
}
6262
loggy.info("connection status: ${event.format()}");
6363
});
@@ -170,11 +170,9 @@ class ConnectionNotifier extends _$ConnectionNotifier with AppLogger {
170170
}
171171

172172
@Riverpod(keepAlive: true)
173-
Future<bool> serviceRunning(Ref ref) async {
173+
bool serviceRunning(Ref ref) {
174174
// ref.watch(coreRestartSignalProvider);
175-
return await ref
176-
.watch(connectionNotifierProvider.selectAsync((data) => data.isConnected))
177-
.onError((error, stackTrace) => false);
175+
return ref.watch(connectionNotifierProvider).valueOrNull?.isConnected ?? false;
178176
}
179177

180178
class SingleCall {

lib/features/proxy/active/active_proxy_notifier.dart

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ import 'package:hiddify/core/preferences/general_preferences.dart';
66
import 'package:hiddify/core/utils/throttler.dart';
77
import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
88
import 'package:hiddify/features/proxy/data/proxy_data_providers.dart';
9+
import 'package:hiddify/features/proxy/data/proxy_repository.dart';
910
import 'package:hiddify/features/proxy/model/ip_info_entity.dart' as oldipinfo;
1011
import 'package:hiddify/features/proxy/model/proxy_failure.dart';
1112
import 'package:hiddify/hiddifycore/generated/v2/hcore/hcore.pb.dart';
12-
import 'package:hiddify/hiddifycore/init_signal.dart';
13+
1314
import 'package:hiddify/utils/riverpod_utils.dart';
1415
import 'package:hiddify/utils/utils.dart';
1516
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -32,7 +33,7 @@ class IpInfoNotifier extends _$IpInfoNotifier with AppLogger {
3233
ref.listen(serviceRunningProvider, (_, next) => _idle = false);
3334

3435
final autoCheck = ref.watch(Preferences.autoCheckIp);
35-
final serviceRunning = await ref.watch(serviceRunningProvider.future);
36+
final serviceRunning = ref.watch(serviceRunningProvider);
3637
// loggy.debug(
3738
// "idle? [$_idle], forced? [$_forceCheck], connected? [$serviceRunning]",
3839
// );
@@ -74,20 +75,20 @@ class IpInfoNotifier extends _$IpInfoNotifier with AppLogger {
7475
@Riverpod(keepAlive: true)
7576
class ActiveProxyNotifier extends _$ActiveProxyNotifier with AppLogger {
7677
@override
77-
Stream<OutboundInfo> build() async* {
78+
Stream<OutboundInfo> build() {
7879
// ref.disposeDelay(const Duration(seconds: 20));
79-
ref.watch(coreRestartSignalProvider);
80-
final serviceRunning = await ref.watch(serviceRunningProvider.future);
80+
final serviceRunning = ref.watch(serviceRunningProvider);
8181
if (!serviceRunning) {
82-
throw const ServiceNotRunning();
82+
return Stream.error(const ServiceNotRunning());
8383
}
84-
final proxyprovider = ref.watch(proxyRepositoryProvider);
85-
yield* proxyprovider
84+
return _proxyRepo
8685
.watchActiveProxies()
8786
.map((event) => event.getOrElse((l) => List<OutboundGroup>.empty()))
8887
.map((event) => event.firstOrNull?.items.first ?? OutboundInfo());
8988
}
9089

90+
ProxyRepository get _proxyRepo => ref.read(proxyRepositoryProvider);
91+
9192
final _urlTestThrottler = Throttler(const Duration(seconds: 1));
9293

9394
Future<void> urlTest(String? groupTag_) async {

lib/features/proxy/overview/proxies_overview_notifier.dart

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'package:hiddify/features/connection/notifier/connection_notifier.dart';
1010
import 'package:hiddify/features/proxy/data/proxy_data_providers.dart';
1111
import 'package:hiddify/features/proxy/model/proxy_failure.dart';
1212
import 'package:hiddify/hiddifycore/generated/v2/hcore/hcore.pb.dart';
13-
import 'package:hiddify/hiddifycore/init_signal.dart';
13+
1414
import 'package:hiddify/utils/riverpod_utils.dart';
1515
import 'package:hiddify/utils/utils.dart';
1616
import 'package:riverpod_annotation/riverpod_annotation.dart';
@@ -57,12 +57,11 @@ class ProxiesSortNotifier extends _$ProxiesSortNotifier with AppLogger {
5757
@riverpod
5858
class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger {
5959
@override
60-
Stream<OutboundGroup?> build() async* {
60+
Stream<OutboundGroup?> build() {
6161
ref.disposeDelay(const Duration(seconds: 15));
62-
ref.watch(coreRestartSignalProvider);
63-
final serviceRunning = await ref.watch(serviceRunningProvider.future);
62+
final serviceRunning = ref.watch(serviceRunningProvider);
6463
if (!serviceRunning) {
65-
throw const ServiceNotRunning();
64+
return Stream.error(const ServiceNotRunning());
6665
}
6766
final sortBy = ref.watch(proxiesSortNotifierProvider);
6867
// yield* ref
@@ -82,7 +81,7 @@ class ProxiesOverviewNotifier extends _$ProxiesOverviewNotifier with AppLogger {
8281
// ),
8382
// )
8483
// .asyncMap((proxies) async => _sortOutbounds(proxies, sortBy));
85-
yield* ref
84+
return ref
8685
.watch(proxyRepositoryProvider)
8786
.watchProxies()
8887
.map(

lib/features/settings/notifier/config_option/config_option_notifier.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ part 'config_option_notifier.g.dart';
2121
class ConfigOptionNotifier extends _$ConfigOptionNotifier with AppLogger {
2222
@override
2323
Future<bool> build() async {
24-
final serviceRunning = await ref.watch(serviceRunningProvider.future);
24+
final serviceRunning = ref.watch(serviceRunningProvider);
2525
final serviceSingboxOptions = ref.read(connectionRepositoryProvider).configOptionsSnapshot;
2626

2727
ref.listen(ConfigOptions.singboxConfigOptions, (previous, next) async {

lib/features/stats/notifier/stats_notifier.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ part 'stats_notifier.g.dart';
1010
@riverpod
1111
class StatsNotifier extends _$StatsNotifier with AppLogger {
1212
@override
13-
Stream<SystemInfo> build() async* {
13+
Stream<SystemInfo> build() {
1414
ref.disposeDelay(const Duration(seconds: 10));
15-
final serviceRunning = await ref.watch(serviceRunningProvider.future);
15+
final serviceRunning = ref.watch(serviceRunningProvider);
1616
if (serviceRunning) {
17-
yield* ref
17+
return ref
1818
.watch(statsRepositoryProvider)
1919
.watchStats()
2020
.map((event) => event.getOrElse((_) => SystemInfo.create()));
2121
} else {
22-
yield* Stream.value(SystemInfo.create());
22+
return Stream.value(SystemInfo.create());
2323
}
2424
}
2525
}

0 commit comments

Comments
 (0)