Skip to content

MagicStarterServiceProvider

View raw .md
On this page

Introduction#

MagicStarterServiceProvider is the bootstrap entry point for the magic_starter plugin. It binds the MagicStarterManager singleton into the IoC container, registers an AuthRestored event listener that soft-reloads the app when a restore changed the user, defines 9 Gate abilities for profile section visibility, and sets up a primary color fallback for Wind UI.

The provider lives at lib/src/providers/magic_starter_service_provider.dart and extends ServiceProvider from package:magic/magic.dart.

Two-Phase Bootstrap#

The Magic Framework calls providers in two ordered phases:

Phase Method Constraint
1 register() Sync. Only bind into the container — no other service may be accessed yet.
2 boot() Async. All providers have been registered. Safe to resolve, configure, and wire services.

Splitting into two phases guarantees that when boot() runs, every binding registered by every other provider is already resolvable from the container.

Register Phase#

dart
@override
void register() {
  app.singleton('magic_starter', () => MagicStarterManager());

  EventDispatcher.instance
      .register(AuthRestored, [() => _ReloadOnAuthRestored()]);

  EventDispatcher.instance
      .register(AuthLogin, [() => _CallOnLoginHook()]);
}

Three things happen here:

  1. Manager singleton — binds MagicStarterManager under the key 'magic_starter'. The manager's constructor calls registerDefaultViews(), so all default views and layouts are immediately available.

  2. Auth restored listener: registers _ReloadOnAuthRestored, which calls Magic.reload() when AuthRestored fires with changed true, so screens refresh after a write that calls Auth.restore() (team rename, invitation accept, profile updates). A sync that only confirms the cached user (changed false, the usual cold boot) reloads nothing, so nothing on screen is torn down. A team switch does not come through here; it resets team-scoped screens through SessionScope.

  3. Auth login listener: registers _CallOnLoginHook, which calls the host's MagicStarter.useLogin() callback (unawaited, logged on failure) whenever AuthLogin fires: every fresh sign-in path, never a cold-boot restore. See Login Callback.

Boot Phase#

boot() is async. It checks the teams feature configuration, registers Gate abilities, and sets up the primary color fallback.

dart
@override
Future<void> boot() async {
  // 1. Teams feature warning
  // 2. Gate abilities
  // 3. Primary color fallback
  // 4. Locale application (see manager.md#locale-application)
}

Teams Feature Warning#

If magic_starter.features.teams is enabled in config but no team resolver has been registered, the provider emits a warning:

dart
final teamsEnabled =
    Config.get<bool>('magic_starter.features.teams', false) ?? false;
if (teamsEnabled && MagicStarter.manager.teamResolver == null) {
  Log.warning(
    '[MagicStarter] Teams feature is enabled but no team resolver '
    'is configured. Call MagicStarter.useTeamResolver() in your AppServiceProvider.',
  );
}

Gate Abilities#

The provider registers 9 Gate abilities that control profile section visibility. Each ability follows the same pattern: grant access when the user is not a guest (is_guest != true).

dart
bool isNotGuest(Model user, [dynamic _]) {
  return user.get<bool>('is_guest') != true;
}

Gate.define('starter.update-profile-photo', isNotGuest);
Gate.define('starter.update-email', isNotGuest);
// ...
Ability Controls
starter.update-profile-photo Profile photo upload/remove section
starter.update-email Email field in profile information
starter.update-phone Phone and country code in extended profile
starter.update-password Password change section
starter.verify-email Email verification banner
starter.manage-two-factor Two-factor authentication section
starter.manage-newsletter Newsletter preferences section
starter.logout-sessions Logout/revoke buttons in browser sessions
starter.delete-account Account deletion section

Host apps can override any ability by calling Gate.define() with the same key after this provider boots:

dart
// In AppServiceProvider.boot()
Gate.define('starter.delete-account', (user, [_]) => false); // disable for all

Primary Color Fallback#

If the host app has not defined a primary color in the Wind UI theme, the provider registers indigo as the fallback. Because boot() runs during Magic.init() — before runApp() builds the widget tree — the check is deferred to a post-frame callback:

dart
WidgetsBinding.instance.addPostFrameCallback((_) {
  final context = MagicRouter.instance.navigatorKey.currentContext;
  if (context == null) return;

  final windTheme = WindTheme.of(context);
  if (!windTheme.data.isValidColor('primary')) {
    windTheme.updateTheme(
      colors: {'primary': windTheme.data.colors['indigo']!},
    );
  }
});

IoC Bindings#

The manager can be resolved from the container in two ways:

dart
// Via facade (preferred)
final manager = MagicStarter.manager;

// Via IoC directly
final manager = Magic.make<MagicStarterManager>('magic_starter');

Add the provider to the providers list in your app's kernel:

dart
import 'package:magic_starter/magic_starter.dart';

Map<String, dynamic> get appConfig => {
  'app': {
    'providers': [
      // ... other providers
      (app) => MagicStarterServiceProvider(app),
    ],
  },
  'magic_starter': {
    'features': {
      'teams': true,
      'two_factor': true,
      'notifications': true,
      // ... other feature flags
    },
  },
};
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.