refactor: optimize startup
This commit is contained in:
@@ -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 {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 150));
|
||||||
|
// ignore: discarded_futures
|
||||||
s.connect();
|
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
|
||||||
|
final navState = ref.read(authNavigationStateProvider);
|
||||||
|
if (navState != AuthNavigationState.authenticated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// Schedule background loading without blocking startup frame
|
||||||
Future.microtask(() async {
|
Future.microtask(() async {
|
||||||
// Wait a bit to ensure auth is complete
|
// Small delay to allow initial build/layout to settle
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
await Future.delayed(const Duration(milliseconds: 250));
|
||||||
|
|
||||||
DebugLogger.log('bg-start', scope: 'models/background');
|
DebugLogger.log('bg-start', scope: 'models/background');
|
||||||
|
|
||||||
// Load default model in 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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -149,12 +149,16 @@ 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),
|
||||||
);
|
);
|
||||||
|
// Defer until after first frame to keep first paint smooth
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
await Future.delayed(jitter);
|
await Future.delayed(jitter);
|
||||||
_scheduleConversationWarmup(ref);
|
_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
|
||||||
// match theme after the first frame. Avoids flicker at startup.
|
// match theme after the first frame. Avoids flicker at startup.
|
||||||
|
|||||||
@@ -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 {
|
|
||||||
await _checkConnectivity();
|
|
||||||
_connectivityTimer = Timer.periodic(_interval, (_) {
|
|
||||||
_checkConnectivity();
|
_checkConnectivity();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check periodically; interval adapts to recent failures
|
||||||
|
_connectivityTimer = Timer.periodic(_interval, (_) {
|
||||||
|
_checkConnectivity();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +73,10 @@ final shareReceiverInitializerProvider = Provider<void>((ref) {
|
|||||||
() => maybeProcessPending(),
|
() => maybeProcessPending(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hook into share_handler
|
// Hook into share_handler after a short defer to avoid startup contention
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
final handler = sh.ShareHandler.instance;
|
final handler = sh.ShareHandler.instance;
|
||||||
|
|
||||||
// Handle initial share when app is cold-started via Share
|
// Handle initial share when app is cold-started via Share
|
||||||
@@ -113,6 +117,7 @@ final shareReceiverInitializerProvider = Provider<void>((ref) {
|
|||||||
await streamSub.cancel();
|
await streamSub.cancel();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
SharedPayload _toPayload(dynamic media) {
|
SharedPayload _toPayload(dynamic media) {
|
||||||
if (media == null) return const SharedPayload();
|
if (media == null) return const SharedPayload();
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
Reference in New Issue
Block a user