refactor: optimize startup

This commit is contained in:
cogwheel0
2025-09-28 20:59:19 +05:30
parent ba1176a181
commit f08259be2b
5 changed files with 85 additions and 67 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@@ -207,9 +208,12 @@ final socketServiceProvider = Provider<SocketService?>((ref) {
authToken: token, authToken: token,
websocketOnly: transportMode == 'ws', websocketOnly: transportMode == 'ws',
); );
// best-effort connect; errors handled internally // best-effort connect, but defer to post-frame with a small delay
// ignore unawaited_futures WidgetsBinding.instance.addPostFrameCallback((_) async {
s.connect(); await Future.delayed(const Duration(milliseconds: 150));
// ignore: discarded_futures
s.connect();
});
// Keep socket token up-to-date without reconstructing the service // Keep socket token up-to-date without reconstructing the service
ref.listen<String?>(authTokenProvider3, (prev, next) { ref.listen<String?>(authTokenProvider3, (prev, next) {
s.updateAuthToken(next); s.updateAuthToken(next);
@@ -881,24 +885,28 @@ final backgroundModelLoadProvider = Provider<void>((ref) {
// Ensure API token updater is initialized // Ensure API token updater is initialized
ref.watch(apiTokenUpdaterProvider); ref.watch(apiTokenUpdaterProvider);
// Schedule background loading without blocking // Only run when authenticated, and defer until after first frame
Future.microtask(() async { final navState = ref.read(authNavigationStateProvider);
// Wait a bit to ensure auth is complete if (navState != AuthNavigationState.authenticated) {
await Future.delayed(const Duration(milliseconds: 200)); return;
}
DebugLogger.log('bg-start', scope: 'models/background'); WidgetsBinding.instance.addPostFrameCallback((_) {
// Schedule background loading without blocking startup frame
Future.microtask(() async {
// Small delay to allow initial build/layout to settle
await Future.delayed(const Duration(milliseconds: 250));
// Load default model in background DebugLogger.log('bg-start', scope: 'models/background');
try { try {
await ref.read(defaultModelProvider.future); await ref.read(defaultModelProvider.future);
DebugLogger.log('bg-complete', scope: 'models/background'); DebugLogger.log('bg-complete', scope: 'models/background');
} catch (e) { } catch (e) {
// Ignore errors in background loading DebugLogger.error('bg-failed', scope: 'models/background', error: e);
DebugLogger.error('bg-failed', scope: 'models/background', error: e); }
} });
}); });
// Return immediately, don't block the UI
return; return;
}); });

View File

@@ -149,11 +149,15 @@ final appStartupFlowProvider = Provider<void>((ref) {
Future.microtask(() async { Future.microtask(() async {
final online = ref.read(isOnlineProvider); final online = ref.read(isOnlineProvider);
if (!online) return; if (!online) return;
// Slightly increase jitter to reduce contention on startup
final jitter = Duration( final jitter = Duration(
milliseconds: 50 + (DateTime.now().millisecond % 100), milliseconds: 150 + (DateTime.now().millisecond % 200),
); );
await Future.delayed(jitter); // Defer until after first frame to keep first paint smooth
_scheduleConversationWarmup(ref); WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(jitter);
_scheduleConversationWarmup(ref);
});
}); });
// One-time, post-frame system UI polish: set status bar icon brightness to // One-time, post-frame system UI polish: set status bar icon brightness to

View File

@@ -33,13 +33,14 @@ class ConnectivityService {
bool get isCurrentlyConnected => _lastStatus == ConnectivityStatus.online; bool get isCurrentlyConnected => _lastStatus == ConnectivityStatus.online;
void _startConnectivityMonitoring() { void _startConnectivityMonitoring() {
// Initial check after a slightly longer delay to avoid competing with // Initial check after a brief delay to avoid showing offline during startup
// first-frame work. Start periodic checks only after the first probe. Timer(const Duration(milliseconds: 800), () {
Timer(const Duration(milliseconds: 1200), () async { _checkConnectivity();
await _checkConnectivity(); });
_connectivityTimer = Timer.periodic(_interval, (_) {
_checkConnectivity(); // Check periodically; interval adapts to recent failures
}); _connectivityTimer = Timer.periodic(_interval, (_) {
_checkConnectivity();
}); });
} }

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:share_handler/share_handler.dart' as sh; import 'package:share_handler/share_handler.dart' as sh;
@@ -72,45 +73,49 @@ final shareReceiverInitializerProvider = Provider<void>((ref) {
() => maybeProcessPending(), () => maybeProcessPending(),
); );
// Hook into share_handler // Hook into share_handler after a short defer to avoid startup contention
final handler = sh.ShareHandler.instance; WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 300));
// Handle initial share when app is cold-started via Share final handler = sh.ShareHandler.instance;
Future.microtask(() async {
try { // Handle initial share when app is cold-started via Share
final dynamic media = await handler.getInitialSharedMedia(); Future.microtask(() async {
final payload = _toPayload(media); try {
if (payload.hasAnything) { final dynamic media = await handler.getInitialSharedMedia();
ref.read(pendingSharedPayloadProvider.notifier).set(payload); final payload = _toPayload(media);
maybeProcessPending(); if (payload.hasAnything) {
ref.read(pendingSharedPayloadProvider.notifier).set(payload);
maybeProcessPending();
}
} catch (e) {
DebugLogger.log(
'ShareReceiver: failed to get initial shared media: $e',
scope: 'share',
);
} }
} catch (e) { });
DebugLogger.log(
'ShareReceiver: failed to get initial shared media: $e',
scope: 'share',
);
}
});
// Handle subsequent shares while app is alive // Handle subsequent shares while app is alive
final streamSub = handler.sharedMediaStream.listen((dynamic media) { final streamSub = handler.sharedMediaStream.listen((dynamic media) {
try { try {
final payload = _toPayload(media); final payload = _toPayload(media);
if (payload.hasAnything) { if (payload.hasAnything) {
ref.read(pendingSharedPayloadProvider.notifier).set(payload); ref.read(pendingSharedPayloadProvider.notifier).set(payload);
maybeProcessPending(); maybeProcessPending();
}
} catch (e) {
DebugLogger.log(
'ShareReceiver: failed to parse shared media: $e',
scope: 'share',
);
} }
} catch (e) { });
DebugLogger.log(
'ShareReceiver: failed to parse shared media: $e',
scope: 'share',
);
}
});
// Ensure cleanup // Ensure cleanup
ref.onDispose(() async { ref.onDispose(() async {
await streamSub.cancel(); await streamSub.cancel();
});
}); });
}); });

View File

@@ -55,12 +55,12 @@ void main() {
_startupTimeline!.start('app_startup'); _startupTimeline!.start('app_startup');
_startupTimeline!.instant('bindings_initialized'); _startupTimeline!.instant('bindings_initialized');
// Enable edge-to-edge globally (back-compat on pre-Android 15) // Defer edge-to-edge mode to post-frame to avoid impacting first paint
// Pairs with Activity's EdgeToEdge.enable and our SafeArea usage. WidgetsBinding.instance.addPostFrameCallback((_) {
// Do not block first frame on system UI mode; apply shortly after startup // ignore: discarded_futures
// ignore: discarded_futures SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); _startupTimeline?.instant('edge_to_edge_enabled');
_startupTimeline!.instant('edge_to_edge_enabled'); });
final sharedPrefs = await SharedPreferences.getInstance(); final sharedPrefs = await SharedPreferences.getInstance();
_startupTimeline!.instant('shared_prefs_ready'); _startupTimeline!.instant('shared_prefs_ready');