# Request Lifecycle - [Introduction](#introduction) - [Lifecycle Overview](#lifecycle-overview) - [Application Bootstrap](#application-bootstrap) - [Service Providers](#service-providers) - [Routing](#routing) - [Middleware](#middleware) - [Controller Dispatch](#controller-dispatch) - [View Rendering](#view-rendering) ## Introduction Understanding the Magic request lifecycle will help you build better applications. This document covers how a Magic application starts up, handles navigation requests, and renders views. ## Lifecycle Overview ``` ┌─────────────────────────────────────────────────────────┐ │ main.dart │ │ │ │ │ Magic.init() │ │ │ │ │ ┌────────────────┼────────────────┐ │ │ ▼ ▼ ▼ │ │ Load .env Register Configs Boot Providers │ │ │ │ │ │ │ runApp(MagicApplication) │ │ │ │ │ Route Matched (GoRouter) │ │ │ │ │ ┌─────────────┼─────────────┐ │ │ ▼ ▼ ▼ │ │ Run Middleware Resolve Layout Get Controller │ │ │ │ │ │ │ Controller.method() │ │ │ │ │ Render View │ └─────────────────────────────────────────────────────────┘ ``` ## Application Bootstrap The application starts in `main.dart`: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); // 1. Initialize Magic await Magic.init( envFileName: '.env', configFactories: [ () => appConfig, () => networkConfig, () => authConfig, ], ); // 2. Restore authentication (optional) await Auth.restore(); // 3. Run migrations (development) if (kDebugMode) { await Migrator().run([...migrations]); } // 4. Start the application runApp(MagicApplication( title: 'My App', initialRoute: Auth.check() ? '/dashboard' : '/login', )); } ``` ### Magic.init() Steps 1. **Load Environment** - Reads `.env` file into memory 2. **Merge Configurations** - Combines all config factories 3. **Register Service Providers** - Calls `register()` on each provider 4. **Boot Service Providers** - Calls `boot()` on each provider ## Service Providers Providers are registered in `config/app.dart`: ```dart 'providers': [ (app) => NetworkServiceProvider(app), (app) => AuthServiceProvider(app), (app) => DatabaseServiceProvider(app), (app) => CacheServiceProvider(app), (app) => LocalizationServiceProvider(app), (app) => EventServiceProvider(app), (app) => AppServiceProvider(app), ], ``` ### Provider Lifecycle 1. **register()** - Bind services to container (no dependencies) 2. **boot()** - Initialize services (can use other services) ```dart class AppServiceProvider extends ServiceProvider { @override void register() { // Runs first - just bind services app.bind('api', () => ApiService()); } @override Future boot() async { // Runs after ALL providers register // Safe to use other services here Gate.before((user, ability) { if ((user as User).isAdmin) return true; return null; }); } } ``` ## Routing Routes are defined in `lib/routes/`: ```dart // lib/routes/web.dart void registerRoutes() { // Guest routes MagicRoute.group( layout: (child) => GuestLayout(child: child), routes: () { MagicRoute.page('/login', () => AuthController.instance.login()); MagicRoute.page('/register', () => AuthController.instance.register()); }, ); // Authenticated routes MagicRoute.group( middleware: ['auth'], layout: (child) => AppLayout(child: child), routes: () { MagicRoute.page('/dashboard', () => DashboardView()); MagicRoute.page('/settings', () => SettingsView()); }, ); } ``` ### Route Resolution 1. URL is matched against defined routes 2. Layout is wrapped around the view 3. Middleware is executed in order 4. Controller/View is resolved ## Middleware Middleware intercepts navigation before the view renders: ```dart class EnsureAuthenticated extends MagicMiddleware { @override Future handle(void Function() next) async { if (Auth.check()) { next(); // Allow navigation } else { MagicRoute.to('/login'); // Redirect } } } ``` ### Middleware Execution Order 1. Global middleware (registered in provider) 2. Route group middleware 3. Route-specific middleware ## Controller Dispatch Controllers are resolved using the `findOrPut` pattern: ```dart // Route definition MagicRoute.page('/tasks', () => TaskController.instance.index()); // Controller class TaskController extends MagicController { static TaskController get instance => Magic.findOrPut(TaskController.new); Widget index() { if (isEmpty) _loadTasks(); return const TaskListView(); } } ``` ### Controller Lifecycle 1. **findOrPut** - Get existing or create new controller 2. **onInit()** - Called when controller is created 3. **Method execution** - Returns view widget 4. **State updates** - `notifyListeners()` triggers rebuilds 5. **onClose()** - Called when controller is disposed ## View Rendering Views render using controller state: ```dart class TaskListView extends MagicView { const TaskListView({super.key}); @override Widget build(BuildContext context) { final controller = TaskController.instance; return controller.renderState( (tasks) => _buildList(tasks), onLoading: CircularProgressIndicator(), onError: (msg) => ErrorWidget(message: msg), onEmpty: EmptyState(), ); } } ``` ### State Flow 1. Controller calls `setLoading()` → View shows loading 2. Controller calls `setSuccess(data)` → View renders data 3. Controller calls `setError(msg)` → View shows error 4. Controller calls `notifyListeners()` → View rebuilds > [!TIP] > Understanding this lifecycle helps you place logic in the right location: configuration in providers, authorization in middleware, business logic in controllers, and UI in views.