run static method
Inizializza e lancia l'app con la configurazione fornita.
Implementation
static Future<void> run(CLAppConfig config) async {
WidgetsFlutterBinding.ensureInitialized();
if (kDebugMode && !kIsWeb) {
HttpOverrides.global = _MyHttpOverrides();
}
await SharedManager.initPrefs();
ApiManager.configure(baseUrl: config.baseUrl, apiVersion: config.apiVersion);
// Core providers
final appState = AppState();
appState.showAiButton = config.showAiButton;
appState.aiButtonPosition = config.aiButtonPosition;
appState.aiButtonBuilder = config.aiButtonBuilder;
appState.profilePosition = config.profilePosition;
final authState = config.authState;
final navigationState = NavigationState();
final errorState = ErrorState();
final themeProvider = ThemeProvider();
final moduleThemeProvider = ModuleThemeProvider();
final notificationsPanelProvider = NotificationsPanelProvider();
// Callback app-specific di init
await config.onInit();
// Popola registry colori moduli (consumato da CLPageHeader e altri widget).
for (final route in config.shellRoutes) {
if (route is ModuleRoute && route.color != null) {
ModuleColorRegistry.register(route.path, route.color!);
}
}
// Router
final appModule = _CLAppModule(config);
// Single Listenable instance shared between Modular.configure and the
// cache-invalidation listener — using the same instance ensures that
// every state notify flushes the redirect dedup cache exactly once.
final refreshListenable = Listenable.merge([authState, navigationState, appState, errorState]);
refreshListenable.addListener(() {
_lastRedirectLocation = null;
_lastRedirectResult = null;
});
await Modular.configure(
appModule: appModule,
initialRoute: config.initialRoute,
debugLogDiagnostics: config.debugLogDiagnostics,
debugLogDiagnosticsGoRouter: false,
pageTransition: PageTransition.fade,
refreshListenable: refreshListenable,
redirect: (context, state) {
final currentLocation = state.matchedLocation;
// Dedup: identical location + unchanged state ⇒ reuse last result.
// Cache is invalidated on every notify of refreshListenable above.
if (currentLocation == _lastRedirectLocation) {
return _lastRedirectResult;
}
String? result;
try {
if (config.customRedirect != null) {
result = config.customRedirect!(context, state);
}
} catch (_) {
// Durante hot-restart, il context può essere deactivato
// e i Provider non sono ancora disponibili
}
_lastRedirectLocation = currentLocation;
_lastRedirectResult = result;
return result;
},
observers: [GoRouterBreadcrumbObserver()],
navigatorKey: AlertManager.navigatorKey,
);
/// Lazy AI assistant config: starts as `null` and is populated on the next
/// microtask via [Future.delayed(Duration.zero)]. The notifier is passed
/// to [_CLMainApp], which rebuilds via [ValueListenableBuilder] when the
/// value lands. This keeps app-startup off the critical path without
/// forcing the AI panel into the initial frame.
final aiConfigNotifier = ValueNotifier<AiAssistantConfig?>(null);
unawaited(Future.delayed(Duration.zero, () {
aiConfigNotifier.value = config.buildAiConfig();
}));
// Run
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider.value(value: appState),
ChangeNotifierProvider.value(value: navigationState),
ChangeNotifierProvider.value(value: authState),
ChangeNotifierProvider<CLAuthState>.value(value: authState),
ChangeNotifierProvider.value(value: errorState),
ChangeNotifierProvider.value(value: themeProvider),
ChangeNotifierProvider.value(value: moduleThemeProvider),
ChangeNotifierProvider.value(value: notificationsPanelProvider),
...config.extraProviders,
],
child: _CLMainApp(
aiConfigNotifier: aiConfigNotifier,
locale: config.locale,
supportedLocales: config.supportedLocales,
mobileBreakpoint: config.mobileBreakpoint,
),
),
);
}