MagicStarterServiceProvider
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#
@override
void register() {
app.singleton('magic_starter', () => MagicStarterManager());
EventDispatcher.instance
.register(AuthRestored, [() => _ReloadOnAuthRestored()]);
EventDispatcher.instance
.register(AuthLogin, [() => _CallOnLoginHook()]);
}
Three things happen here:
-
Manager singleton — binds
MagicStarterManagerunder the key'magic_starter'. The manager's constructor callsregisterDefaultViews(), so all default views and layouts are immediately available. -
Auth restored listener: registers
_ReloadOnAuthRestored, which callsMagic.reload()whenAuthRestoredfires withchangedtrue, so screens refresh after a write that callsAuth.restore()(team rename, invitation accept, profile updates). A sync that only confirms the cached user (changedfalse, 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 throughSessionScope. -
Auth login listener: registers
_CallOnLoginHook, which calls the host'sMagicStarter.useLogin()callback (unawaited, logged on failure) wheneverAuthLoginfires: 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.
@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:
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).
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:
// 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:
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:
// 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:
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
},
},
};
Related#
- MagicStarterManager — central singleton holding all customization registrations
- View Registry — string-keyed view factory for overridable UI
- Controllers & State Registration — lazy singleton pattern, consumer app state registration guide
- Magic Framework — Service Providers — two-phase lifecycle reference
- Magic Framework — Gate — authorization abilities reference