# Request Lifecycle

Understanding the Magic request lifecycle helps you know exactly where your service providers, middleware, and controllers run relative to each bootstrap step.

- [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)
- [Related Documentation](#related-documentation)

## 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()                           │
│                       │                                 │
│                  Env.load()                             │
│                       │                                 │
│               configFactories evaluated                 │
│                       │                                 │
│             URL strategy applied (web)                  │
│                       │                                 │
│      ┌────────────────┴────────────────┐                │
│      ▼                                 ▼                │
│  providers register() (sync)    (all providers)        │
│                       │                                 │
│      providers boot() (async, all registered)          │
│                       │                                 │
│              router pre-build                           │
│                       │                                 │
│                 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. Start the application
  runApp(MagicApplication(
    title: 'My App',
    initialRoute: Auth.check() ? '/dashboard' : '/login',
  ));
}
```

### Magic.init() Steps

1. **Load Environment** - Reads `.env` file into memory via `Env.load()`
2. **Evaluate Config Factories** - Runs all `configFactories` now that `Env` is populated
3. **Apply URL Strategy** - Configures path vs hash URL strategy for web (before any widget tree exists)
4. **Register Service Providers** - Calls `register()` on each provider synchronously
5. **Boot Service Providers** - Calls `boot()` on each provider asynchronously
6. **Pre-build Router** - Resolves `MagicRouter.instance.routerConfig` so GoRouter is ready before `runApp`

## Service Providers

Providers are registered in `config/app.dart`:

```dart
'providers': [
  (app) => NetworkServiceProvider(app),
  (app) => CacheServiceProvider(app),
  (app) => LocalizationServiceProvider(app),
  (app) => EventServiceProvider(app),
  (app) => AppServiceProvider(app),
  (app) => AuthServiceProvider(app),
],
```

`AppServiceProvider` comes before `AuthServiceProvider` so the session restore finds the user factory; see [Provider Loading Order](https://anilcancakir.com/magic/docs/getting-started/service-providers.md#registering-providers).

### Provider Lifecycle

1. **register()** - Bind services to container (no dependencies)
2. **boot()** - Initialise services (can use other services)

```dart
class AppServiceProvider extends ServiceProvider {
  @override
  void register() {
    // Runs first - just bind services
    app.bind('api', () => ApiService());
  }

  @override
  Future<void> 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;
    });
  }
}
```

### Registering a provider after boot

A provider registered once the app has already booted is booted on the spot,
rather than waiting for a boot phase that has been and gone. `register()`
returns a `Future` so a caller that needs the provider wired before its next
line can wait for it:

```dart
// A plugin installing itself lazily, after the app is running.
await Magic.app.register(NotificationServiceProvider(Magic.app));
Notify.startPolling(); // the provider has finished booting
```

Ignoring the future is fine when you do not need that guarantee. Before the boot
phase it completes immediately, since nothing asynchronous has happened yet.

Registering the same provider **instance** twice is a no-op the second time, so
its `register()` and `boot()` each run once. Two different instances of the same
provider class both run, which is deliberate: a provider parameterised per
plugin is a legitimate shape.

## Routing

Routes are defined in `lib/routes/`:

```dart
// lib/routes/web.dart
void registerRoutes() {
  // Guest routes
  MagicRoute.group(
    layoutId: 'guest',
    layout: (child) => GuestLayout(child: child),
    routes: () {
      MagicRoute.page('/login', () => AuthController.instance.login());
      MagicRoute.page('/register', () => AuthController.instance.register());
    },
  );

  // Authenticated routes
  MagicRoute.group(
    layoutId: 'app',
    middleware: ['auth'],
    layout: (child) => AppLayout(child: child),
    routes: () {
      MagicRoute.page('/dashboard', () => DashboardView());
      MagicRoute.page('/settings', () => SettingsView());
    },
  );
}
```

Each group with a `layout:` is its own go_router `ShellRoute`, so the guest and authenticated groups are separate shells. Groups that share a `layoutId` share one persistent shell, and shells are emitted in registration order. See [Layouts](https://anilcancakir.com/magic/docs/basics/routing.md#layouts-shell-routes).

### 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
  String? redirectTarget(String location) {
    if (!Auth.check() && location != '/login') return '/login';
    return null; // Allow navigation
  }
}
```

A redirect guard overrides `redirectTarget`, which runs in the router's `redirect` callback before the route builds, so the destination mounts once. `handle(next)` is for async, non-redirecting work; a `MagicRoute.to()` from inside it runs after the route has mounted and remounts the destination. See [Middleware](https://anilcancakir.com/magic/docs/basics/middleware.md).

### 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 with MagicStateMixin<List<Task>> {
  static TaskController get instance => Magic.findOrPut(TaskController.new);

  Widget index() {
    if (isEmpty) _loadTasks();
    return const TaskListView();
  }

  Future<void> _loadTasks() async {
    setLoading();
    try {
      setSuccess(await Task.all());
    } on Exception catch (e) {
      setError(e.toString()); // the view's onError branch renders it
    }
  }
}
```

`isEmpty`, `setLoading()`, `setSuccess()` and `setError()` come from `MagicStateMixin`, so a controller that tracks loading state mixes it in.

### Controller Lifecycle

For a `MagicController` behind a route:

1. **findOrPut** - Get the existing controller or create one; creating it does not run `onInit()`
2. **Method execution** - The route handler calls the method (`index()` above), which returns the view widget
3. **onInit()** - Runs once per controller, when the first `MagicStatefulView` backed by it mounts; a stateless `MagicView` never runs it. Nothing the method reads in step 2 can depend on it
4. **State updates** - `refreshUI()` (or a `MagicStateMixin` setter) triggers rebuilds
5. **onClose()** - Called when controller is disposed

A `SimpleMagicController` differs only in step 3: its constructor calls `onInit()`, so it has run before step 2.

## View Rendering

Views render using controller state:

```dart
class TaskListView extends MagicView<TaskController> {
  const TaskListView({super.key});

  @override
  Widget build(BuildContext context) {
    return controller.renderState(
      (tasks) => _buildList(tasks),
      onLoading: WText('Loading...'),
      onError: (msg) => WText('Error: $msg'),
      onEmpty: WText('No tasks'),
    );
  }
}
```

### 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 `refreshUI()` → View rebuilds

> [!TIP]
> Understanding this lifecycle helps you place logic in the right location: configuration in providers, authorisation in middleware, business logic in controllers, and UI in views.

## Related Documentation

- [Service Container](https://anilcancakir.com/magic/docs/architecture/service-container.md): the bindings `Magic.init()` builds.
- [Service Providers](https://anilcancakir.com/magic/docs/getting-started/service-providers.md): `register()` and `boot()` in depth.
- [Routing](https://anilcancakir.com/magic/docs/basics/routing.md): groups, layouts and `MagicRoute.page()`.
- [Middleware](https://anilcancakir.com/magic/docs/basics/middleware.md): `MagicMiddleware` and the `Kernel`.
- [Controllers](https://anilcancakir.com/magic/docs/basics/controllers.md): `MagicController` and `renderState`.
- [Views](https://anilcancakir.com/magic/docs/basics/views.md): `MagicView` and its controller.
