search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs
You are viewing an older version (1.0.0-alpha.10). Go to the latest.

Request Lifecycle

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:

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:

'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)
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/:

// 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:

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:

// 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:

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.