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

View File

@@ -149,11 +149,15 @@ final appStartupFlowProvider = Provider<void>((ref) {
Future.microtask(() async {
final online = ref.read(isOnlineProvider);
if (!online) return;
// Slightly increase jitter to reduce contention on startup
final jitter = Duration(
milliseconds: 50 + (DateTime.now().millisecond % 100),
milliseconds: 150 + (DateTime.now().millisecond % 200),
);
await Future.delayed(jitter);
_scheduleConversationWarmup(ref);
// Defer until after first frame to keep first paint smooth
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(jitter);
_scheduleConversationWarmup(ref);
});
});
// 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;
void _startConnectivityMonitoring() {
// Initial check after a slightly longer delay to avoid competing with
// first-frame work. Start periodic checks only after the first probe.
Timer(const Duration(milliseconds: 1200), () async {
await _checkConnectivity();
_connectivityTimer = Timer.periodic(_interval, (_) {
_checkConnectivity();
});
// Initial check after a brief delay to avoid showing offline during startup
Timer(const Duration(milliseconds: 800), () {
_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 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:share_handler/share_handler.dart' as sh;
@@ -72,45 +73,49 @@ final shareReceiverInitializerProvider = Provider<void>((ref) {
() => maybeProcessPending(),
);
// Hook into share_handler
final handler = sh.ShareHandler.instance;
// Hook into share_handler after a short defer to avoid startup contention
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 300));
// Handle initial share when app is cold-started via Share
Future.microtask(() async {
try {
final dynamic media = await handler.getInitialSharedMedia();
final payload = _toPayload(media);
if (payload.hasAnything) {
ref.read(pendingSharedPayloadProvider.notifier).set(payload);
maybeProcessPending();
final handler = sh.ShareHandler.instance;
// Handle initial share when app is cold-started via Share
Future.microtask(() async {
try {
final dynamic media = await handler.getInitialSharedMedia();
final payload = _toPayload(media);
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
final streamSub = handler.sharedMediaStream.listen((dynamic media) {
try {
final payload = _toPayload(media);
if (payload.hasAnything) {
ref.read(pendingSharedPayloadProvider.notifier).set(payload);
maybeProcessPending();
// Handle subsequent shares while app is alive
final streamSub = handler.sharedMediaStream.listen((dynamic media) {
try {
final payload = _toPayload(media);
if (payload.hasAnything) {
ref.read(pendingSharedPayloadProvider.notifier).set(payload);
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
ref.onDispose(() async {
await streamSub.cancel();
// Ensure cleanup
ref.onDispose(() async {
await streamSub.cancel();
});
});
});

View File

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