Documentation Index
Fetch the complete documentation index at: https://mintlify.com/CesarArellano/Music-Player-App/llms.txt
Use this file to discover all available pages before exploring further.
Musynth wires its services, repositories, and Cubits together through a single GetIt service locator defined in lib/dependency_injection/di.dart. Instead of passing dependencies down through constructors all the way from main(), any class can call setupLocator<T>() to resolve a registered instance.
The Service Locator
// lib/dependency_injection/di.dart
GetIt setupLocator = GetIt.instance;
GetIt.instance is a process-wide singleton. The alias setupLocator is used throughout the codebase both for registration (setupLocator.registerSingleton<T>(...)) and for resolution (setupLocator<T>()).
Two-Phase Startup
Registration is split into two ordered functions that must be called in sequence from main():
// main.dart
setupAudioHandlers(); // Phase 1
final audioHandler = await AudioService.init(...);
// ... create cubits ...
setupServiceLayer(libraryCubit: ..., ...); // Phase 2
setupServiceLayer depends on Cubits that are created after AudioService.init returns. Never call it before the Cubits exist, and never call setupAudioHandlers after AudioService.init.
Phase 1 — setupAudioHandlers()
Registers all infrastructure that must exist before AudioService.init is called.
void setupAudioHandlers() {
// Eagerly created — used immediately at startup.
setupLocator.registerSingleton<FileImageCache>(FileImageCache());
setupLocator.registerSingleton<SnackbarService>(SnackbarService());
// Lazy — created on first access.
setupLocator.registerLazySingleton(() => AudioPlayer());
setupLocator.registerLazySingleton(() => MusicQuerySelector());
setupLocator.registerLazySingleton(() => ImagePicker());
setupLocator.registerLazySingleton<PreferencesRepository>(
() => SharedPreferencesRepository(UserPreferences()),
);
setupLocator.registerLazySingleton<AudioRepository>(
() => MusicQuerySelectorRepository(setupLocator<MusicQuerySelector>()),
);
// ArtworkRepository is the same instance as AudioRepository.
setupLocator.registerLazySingleton<ArtworkRepository>(
() => setupLocator<AudioRepository>() as ArtworkRepository,
);
setupLocator.registerLazySingleton<ArtworkCacheService>(
() => ArtworkCacheService(
artworkRepository: setupLocator<ArtworkRepository>(),
preferences: setupLocator<PreferencesRepository>(),
),
);
setupLocator.registerLazySingleton<FileManagementService>(
() => const FileManagementService(),
);
setupLocator.registerLazySingleton<TagEditorService>(
() => TagEditorService(),
);
}
| Registered Type | Implementation | Registration |
|---|
FileImageCache | FileImageCache | Singleton (eager) |
SnackbarService | SnackbarService | Singleton (eager) |
AudioPlayer | AudioPlayer (just_audio) | Lazy singleton |
MusicQuerySelector | MusicQuerySelector | Lazy singleton |
ImagePicker | ImagePicker | Lazy singleton |
PreferencesRepository | SharedPreferencesRepository | Lazy singleton |
AudioRepository | MusicQuerySelectorRepository | Lazy singleton |
ArtworkRepository | same instance as AudioRepository | Lazy singleton |
ArtworkCacheService | ArtworkCacheService | Lazy singleton |
FileManagementService | FileManagementService | Lazy singleton |
TagEditorService | TagEditorService | Lazy singleton |
Phase 2 — setupServiceLayer(...)
Called after the five Cubits are created in main(). Registers the Cubits themselves as singletons so services can resolve them without a BuildContext, then builds the service layer on top.
void setupServiceLayer({
required LibraryCubit libraryCubit,
required PlaybackStateCubit playbackStateCubit,
required FavoritesCubit favoritesCubit,
required AudioControlCubit audioControlCubit,
required UICubit uiCubit,
required AudioPlayerHandler audioHandler,
}) {
// Register Cubits so services can resolve them without BuildContext.
setupLocator.registerSingleton<LibraryCubit>(libraryCubit);
setupLocator.registerSingleton<PlaybackStateCubit>(playbackStateCubit);
setupLocator.registerSingleton<FavoritesCubit>(favoritesCubit);
setupLocator.registerSingleton<AudioControlCubit>(audioControlCubit);
setupLocator.registerSingleton<UICubit>(uiCubit);
setupLocator.registerSingleton<FavoritesService>(
FavoritesService(
favoritesCubit: favoritesCubit,
preferences: setupLocator<PreferencesRepository>(),
),
);
// Eagerly creating PlaybackService starts positionStream and
// currentIndexStream subscriptions immediately at app startup.
setupLocator.registerSingleton<PlaybackService>(
JustAudioPlaybackService(
audioPlayer: setupLocator<AudioPlayer>(),
playbackStateCubit: playbackStateCubit,
audioControlCubit: audioControlCubit,
preferences: setupLocator<PreferencesRepository>(),
handler: audioHandler,
favoritesCubit: favoritesCubit,
),
);
// Wire the notification favorite-toggle button.
audioHandler.onFavoriteToggle = () {
final song = playbackStateCubit.state.songPlayed;
setupLocator<FavoritesService>().toggle(
song,
favoritesState: favoritesCubit.state,
allSongs: libraryCubit.state.songList,
);
};
setupLocator.registerSingleton<MusicOrchestratorService>(
buildMusicOrchestratorService(),
);
}
| Registered Type | Implementation | Registration |
|---|
LibraryCubit | same instance from main() | Singleton (eager) |
PlaybackStateCubit | same instance from main() | Singleton (eager) |
FavoritesCubit | same instance from main() | Singleton (eager) |
AudioControlCubit | same instance from main() | Singleton (eager) |
UICubit | same instance from main() | Singleton (eager) |
FavoritesService | FavoritesService | Singleton (eager) |
PlaybackService | JustAudioPlaybackService | Singleton (eager) |
MusicOrchestratorService | built by buildMusicOrchestratorService() | Singleton (eager) |
Singleton vs Lazy Singleton
| Strategy | GetIt API | Created when |
|---|
| Singleton | registerSingleton<T>(instance) | Immediately, at registration time |
| Lazy singleton | registerLazySingleton<T>(() => ...) | On first call to setupLocator<T>() |
Infrastructure that is always needed (e.g., SnackbarService, Cubits, PlaybackService) is registered as an eager singleton so it is ready before any widget requests it. Heavy or conditional dependencies (e.g., ImagePicker, TagEditorService) are lazy so they are only initialized when a user action actually needs them.
Resolving Dependencies
Any Dart code — widget, service, or cubit — can resolve a registered instance without a BuildContext:
// Resolve a service
final orchestrator = setupLocator<MusicOrchestratorService>();
// Resolve a repository
final prefs = setupLocator<PreferencesRepository>();
// Resolve a cubit (useful inside services that must act without a widget tree)
final library = setupLocator<LibraryCubit>();
final songs = library.state.songList;
Widgets should prefer context.read<XCubit>() for Cubits that are already in the widget tree via MultiBlocProvider. Reserve setupLocator<XCubit>() for service-layer code that has no BuildContext.