Fliin
On this page

Screens / lists

Music library

Lists a listener's songs, albums as a grid of artwork and playlists in tabs, searchable by title or artist, loaded page by page, with a mini player for the song now playing.

16 of 16 variants pass
Music library, songs state
Platform
Brightness
Text scale
Direction

Import

Music library is part of Fliin Pro. With a license key, sign in once with fliin login, then add it in a Flutter app where fliin init has run:

fliin login
fliin add library

Also installs: alert, app-bar, button, core, empty-state, list-row, mini-player, page, result-card, search-field, separator, skeleton, spinner, tabs, text, text-field, track-row

Examples

Songs

The songs, loaded ten at a time: tapping one plays it in the mini player, which nowPlaying drives. standalone holds the library over the LibraryService you pass.

Music library, songs state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibrarySongsExample extends StatefulWidget {
  const LibrarySongsExample({super.key});

  @override
  State<LibrarySongsExample> createState() => _LibrarySongsExampleState();
}

class _LibrarySongsExampleState extends State<LibrarySongsExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: LibraryShown(sampleLibraryTracks.take(10).toList(), total: 24, more: const LibraryMoreAvailable('10')),
      tab: LibraryTab.songs,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Albums

The albums tab, as a grid of covers.

Music library, albums state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryAlbumsExample extends StatefulWidget {
  const LibraryAlbumsExample({super.key});

  @override
  State<LibraryAlbumsExample> createState() => _LibraryAlbumsExampleState();
}

class _LibraryAlbumsExampleState extends State<LibraryAlbumsExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: LibraryShown(sampleLibraryAlbums, total: 6),
      tab: LibraryTab.albums,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Playlists

The playlists tab, each with its track count.

Music library, playlists state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryPlaylistsExample extends StatefulWidget {
  const LibraryPlaylistsExample({super.key});

  @override
  State<LibraryPlaylistsExample> createState() => _LibraryPlaylistsExampleState();
}

class _LibraryPlaylistsExampleState extends State<LibraryPlaylistsExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: LibraryShown(sampleLibraryPlaylists, total: 4),
      tab: LibraryTab.playlists,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

No match

A search that matches nothing in the library says so and offers to clear it.

Music library, no-match state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryNoMatchExample extends StatefulWidget {
  const LibraryNoMatchExample({super.key});

  @override
  State<LibraryNoMatchExample> createState() => _LibraryNoMatchExampleState();
}

class _LibraryNoMatchExampleState extends State<LibraryNoMatchExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: const LibraryNoMatch('violin'),
      tab: LibraryTab.songs,
      query: 'violin',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Empty

A tab with nothing in it yet.

Music library, empty state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryEmptyExample extends StatefulWidget {
  const LibraryEmptyExample({super.key});

  @override
  State<LibraryEmptyExample> createState() => _LibraryEmptyExampleState();
}

class _LibraryEmptyExampleState extends State<LibraryEmptyExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: const LibraryEmpty(),
      tab: LibraryTab.playlists,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Loading

While the first page loads, skeletons keep the place of the rows.

Music library, loading state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryLoadingExample extends StatefulWidget {
  const LibraryLoadingExample({super.key});

  @override
  State<LibraryLoadingExample> createState() => _LibraryLoadingExampleState();
}

class _LibraryLoadingExampleState extends State<LibraryLoadingExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: const LibraryLoading(),
      tab: LibraryTab.songs,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Error

When loading fails, the screen says so and offers to try again.

Music library, error state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryErrorExample extends StatefulWidget {
  const LibraryErrorExample({super.key});

  @override
  State<LibraryErrorExample> createState() => _LibraryErrorExampleState();
}

class _LibraryErrorExampleState extends State<LibraryErrorExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: const LibraryFailed(),
      tab: LibraryTab.songs,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Offline

Without a connection, the screen asks to reconnect.

Music library, offline state
import 'package:your_app/ui/fliin/components/result_card/fl_result_card.dart';
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryOfflineExample extends StatefulWidget {
  const LibraryOfflineExample({super.key});

  @override
  State<LibraryOfflineExample> createState() => _LibraryOfflineExampleState();
}

class _LibraryOfflineExampleState extends State<LibraryOfflineExample> {
  final _player = SampleNowPlaying(
    const LibraryNowPlaying(
      trackId: 'track-1',
      title: 'October Light',
      artist: 'The Tides',
      playing: true,
      artwork: FlSampleImage(FlSampleArt.lamp),
    ),
  );

  @override
  void dispose() {
    _player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      initial: const LibraryOffline(),
      tab: LibraryTab.songs,
      query: '',
      nowPlaying: _player,
      onPlay: _player.play,
      onPlayPause: _player.playPause,
      onOpenPlayer: () {},
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Tabs

Only the tabs your app has: tabs lists them in their order, here the playlists first, then the albums, and no tracks tab.

Music library, tabs state
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:flutter/widgets.dart';

class LibraryTabsExample extends StatelessWidget {
  const LibraryTabsExample({super.key});

  @override
  Widget build(BuildContext context) {
    return LibraryScreen.standalone(
      service: SampleLibraryService(latency: const Duration(milliseconds: 900)),
      tabs: const [LibraryTab.playlists, LibraryTab.albums],
      initial: LibraryShown(sampleLibraryPlaylists, total: 4),
      onOpenAlbum: (_) {},
      onOpenPlaylist: (_) {},
    );
  }
}

Routing

fliin add library prints the route for the router it finds in your pubspec, a file next to the page that wires its navigation. Each of them:

go_router

// lib/library_route.dart, next to library_page.dart.
import 'package:go_router/go_router.dart';

import 'library_page.dart';

/// Add libraryRoute to the routes of your GoRouter.
final libraryRoute = GoRoute(
  path: '/library',
  builder: (context, state) => LibraryScreenPage(
    onOpenPlayer: () => context.push('/player'),
    // The path of the page of an album in your app.
    onOpenAlbum: (album) => context.push('/albums/${Uri.encodeComponent(album.id)}'),
    // The path of the page of a playlist in your app.
    onOpenPlaylist: (playlist) => context.push('/playlists/${Uri.encodeComponent(playlist.id)}'),
  ),
);

navigator

// lib/library_route.dart, next to library_page.dart.
import 'package:flutter/material.dart';

import 'library_page.dart';
import 'player_route.dart';

/// Opens “Music library”: `Navigator.of(context).push(libraryRoute(albumPage: …, playlistPage: …))`.
/// [albumPage] builds the page of an album in your app.
/// [playlistPage] builds the page of a playlist in your app.
Route<void> libraryRoute({
  required Widget Function(String albumId) albumPage,
  required Widget Function(String playlistId) playlistPage,
}) => MaterialPageRoute<void>(
  builder: (context) => LibraryScreenPage(
    onOpenPlayer: () => Navigator.of(context).push(playerRoute()),
    onOpenAlbum: (album) => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => albumPage(album.id))),
    onOpenPlaylist: (playlist) => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => playlistPage(playlist.id))),
  ),
);

State

The controller is a ChangeNotifier from widgets.dart, with no package. fliin add library prints the page that holds it with the state management it finds, its imports written for your package; below they read your_app. Your backend implements the Service interface of the screen's _data.dart file:

none

// lib/library_page.dart
// Your backend: implement LibraryService (library_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:flutter/widgets.dart';

import 'player_page.dart';

// The mini player shows the app's player, the one of PlayerPage: a
// track tapped here plays there, and a pause there shows here.
final libraryNowPlaying = LibraryNowPlayingOf(
  samplePlayer.playback,
  (playback) => switch (playback) {
    PlaybackReady(:final track, :final playing, :final buffering) => LibraryNowPlaying(
      trackId: track.id,
      title: track.title,
      artist: track.artist,
      artwork: track.artwork,
      playing: playing,
      buffering: buffering,
    ),
    _ => null,
  },
);

void playLibraryTrack(LibraryTrack track) => samplePlayer.playTrack(
  PlayerTrack(
    id: track.id,
    title: track.title,
    artist: track.artist,
    album: track.album,
    duration: track.duration,
    artwork: track.artwork,
  ),
);

class LibraryScreenPage extends StatelessWidget {
  const LibraryScreenPage({
    super.key,
    required this.onOpenPlayer,
    required this.onOpenAlbum,
    required this.onOpenPlaylist,
  });

  final VoidCallback onOpenPlayer;
  final ValueChanged<LibraryAlbum> onOpenAlbum;
  final ValueChanged<LibraryPlaylist> onOpenPlaylist;

  @override
  Widget build(BuildContext context) => LibraryScreen.standalone(
    service: SampleLibraryService(),
    nowPlaying: libraryNowPlaying,
    onPlay: playLibraryTrack,
    onPlayPause: samplePlayer.playPause,
    onOpenPlayer: onOpenPlayer,
    onOpenAlbum: onOpenAlbum,
    onOpenPlaylist: onOpenPlaylist,
  );
}

provider

// lib/library_page.dart
// Your backend: implement LibraryService (library_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';

import 'player_page.dart';

// The mini player shows the app's player, the one of PlayerPage: a
// track tapped here plays there, and a pause there shows here.
final libraryNowPlaying = LibraryNowPlayingOf(
  samplePlayer.playback,
  (playback) => switch (playback) {
    PlaybackReady(:final track, :final playing, :final buffering) => LibraryNowPlaying(
      trackId: track.id,
      title: track.title,
      artist: track.artist,
      artwork: track.artwork,
      playing: playing,
      buffering: buffering,
    ),
    _ => null,
  },
);

void playLibraryTrack(LibraryTrack track) => samplePlayer.playTrack(
  PlayerTrack(
    id: track.id,
    title: track.title,
    artist: track.artist,
    album: track.album,
    duration: track.duration,
    artwork: track.artwork,
  ),
);

class LibraryScreenPage extends StatelessWidget {
  const LibraryScreenPage({
    super.key,
    required this.onOpenPlayer,
    required this.onOpenAlbum,
    required this.onOpenPlaylist,
  });

  final VoidCallback onOpenPlayer;
  final ValueChanged<LibraryAlbum> onOpenAlbum;
  final ValueChanged<LibraryPlaylist> onOpenPlaylist;

  @override
  Widget build(BuildContext context) => ChangeNotifierProvider(
    create: (_) => LibraryController(service: SampleLibraryService())..load(),
    child: Builder(
      builder: (context) => LibraryScreen(
        controller: context.read<LibraryController>(),
        nowPlaying: libraryNowPlaying,
        onPlay: playLibraryTrack,
        onPlayPause: samplePlayer.playPause,
        onOpenPlayer: onOpenPlayer,
        onOpenAlbum: onOpenAlbum,
        onOpenPlaylist: onOpenPlaylist,
      ),
    ),
  );
}

riverpod

// lib/library_page.dart
// Your backend: implement LibraryService (library_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import 'player_page.dart';

// The mini player shows the app's player, the one of PlayerPage: a
// track tapped here plays there, and a pause there shows here.
final libraryNowPlaying = LibraryNowPlayingOf(
  samplePlayer.playback,
  (playback) => switch (playback) {
    PlaybackReady(:final track, :final playing, :final buffering) => LibraryNowPlaying(
      trackId: track.id,
      title: track.title,
      artist: track.artist,
      artwork: track.artwork,
      playing: playing,
      buffering: buffering,
    ),
    _ => null,
  },
);

void playLibraryTrack(LibraryTrack track) => samplePlayer.playTrack(
  PlayerTrack(
    id: track.id,
    title: track.title,
    artist: track.artist,
    album: track.album,
    duration: track.duration,
    artwork: track.artwork,
  ),
);

final libraryControllerProvider = Provider.autoDispose<LibraryController>((ref) {
  final controller = LibraryController(service: SampleLibraryService())..load();
  ref.onDispose(controller.dispose);
  return controller;
});

class LibraryScreenPage extends ConsumerWidget {
  const LibraryScreenPage({
    super.key,
    required this.onOpenPlayer,
    required this.onOpenAlbum,
    required this.onOpenPlaylist,
  });

  final VoidCallback onOpenPlayer;
  final ValueChanged<LibraryAlbum> onOpenAlbum;
  final ValueChanged<LibraryPlaylist> onOpenPlaylist;

  @override
  Widget build(BuildContext context, WidgetRef ref) => LibraryScreen(
    controller: ref.watch(libraryControllerProvider),
    nowPlaying: libraryNowPlaying,
    onPlay: playLibraryTrack,
    onPlayPause: samplePlayer.playPause,
    onOpenPlayer: onOpenPlayer,
    onOpenAlbum: onOpenAlbum,
    onOpenPlaylist: onOpenPlaylist,
  );
}

bloc

// lib/library_page.dart
// Your backend: implement LibraryService (library_data.dart) and pass it instead of the sample service.
import 'package:your_app/ui/fliin/screens/library/library_controller.dart';
import 'package:your_app/ui/fliin/screens/library/library_data.dart';
import 'package:your_app/ui/fliin/screens/library/library_screen.dart';
import 'package:your_app/ui/fliin/screens/player/player_data.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'player_page.dart';

// The mini player shows the app's player, the one of PlayerPage: a
// track tapped here plays there, and a pause there shows here.
final libraryNowPlaying = LibraryNowPlayingOf(
  samplePlayer.playback,
  (playback) => switch (playback) {
    PlaybackReady(:final track, :final playing, :final buffering) => LibraryNowPlaying(
      trackId: track.id,
      title: track.title,
      artist: track.artist,
      artwork: track.artwork,
      playing: playing,
      buffering: buffering,
    ),
    _ => null,
  },
);

void playLibraryTrack(LibraryTrack track) => samplePlayer.playTrack(
  PlayerTrack(
    id: track.id,
    title: track.title,
    artist: track.artist,
    album: track.album,
    duration: track.duration,
    artwork: track.artwork,
  ),
);

/// The screen's controller as a bloc, for your BlocListener and BlocBuilder:
/// one emission per controller notification. `version` tells them apart
/// when `state` does not change (mode, filters, text…, read them on
/// `controller`). The screen listens to the controller itself.
class LibraryCubit extends Cubit<({LibraryState state, int version})> {
  LibraryCubit(this.controller) : super((state: controller.state, version: 0)) {
    controller.addListener(_changed);
  }

  final LibraryController controller;

  void _changed() => emit((state: controller.state, version: state.version + 1));

  @override
  Future<void> close() {
    controller
      ..removeListener(_changed)
      ..dispose();
    return super.close();
  }
}

class LibraryScreenPage extends StatelessWidget {
  const LibraryScreenPage({
    super.key,
    required this.onOpenPlayer,
    required this.onOpenAlbum,
    required this.onOpenPlaylist,
  });

  final VoidCallback onOpenPlayer;
  final ValueChanged<LibraryAlbum> onOpenAlbum;
  final ValueChanged<LibraryPlaylist> onOpenPlaylist;

  @override
  Widget build(BuildContext context) => BlocProvider(
    create: (_) => LibraryCubit(LibraryController(service: SampleLibraryService())..load()),
    child: Builder(
      builder: (context) => LibraryScreen(
        controller: context.read<LibraryCubit>().controller,
        nowPlaying: libraryNowPlaying,
        onPlay: playLibraryTrack,
        onPlayPause: samplePlayer.playPause,
        onOpenPlayer: onOpenPlayer,
        onOpenAlbum: onOpenAlbum,
        onOpenPlaylist: onOpenPlaylist,
      ),
    ),
  );
}

The snippets lead to player: install these screens too and paste their snippets.

API

LibraryScreen: class extends StatefulWidget
LibraryScreen.new: const new({super.key, required LibraryController controller, ValueListenable<LibraryNowPlaying?>? nowPlaying, ValueChanged<LibraryTrack>? onPlay, VoidCallback? onPlayPause, VoidCallback? onOpenPlayer, ValueChanged<LibraryAlbum>? onOpenAlbum, ValueChanged<LibraryPlaylist>? onOpenPlaylist, VoidCallback? onBack, List<LibraryTab> tabs})
LibraryScreen.standalone: const new({super.key, required LibraryService service, LibraryState? initial, LibraryTab tab, String query, ValueListenable<LibraryNowPlaying?>? nowPlaying, ValueChanged<LibraryTrack>? onPlay, VoidCallback? onPlayPause, VoidCallback? onOpenPlayer, ValueChanged<LibraryAlbum>? onOpenAlbum, ValueChanged<LibraryPlaylist>? onOpenPlaylist, VoidCallback? onBack, List<LibraryTab> tabs})
LibraryScreen.controller: get LibraryController?
LibraryScreen.service: get LibraryService?
LibraryScreen.initial: get LibraryState?
LibraryScreen.tab: get LibraryTab
LibraryScreen.query: get String
LibraryScreen.nowPlaying: get ValueListenable<LibraryNowPlaying?>?
LibraryScreen.onPlay: get ValueChanged<LibraryTrack>?
LibraryScreen.onPlayPause: get VoidCallback?
LibraryScreen.onOpenPlayer: get VoidCallback?
LibraryScreen.onOpenAlbum: get ValueChanged<LibraryAlbum>?
LibraryScreen.onOpenPlaylist: get ValueChanged<LibraryPlaylist>?
LibraryScreen.onBack: get VoidCallback?
LibraryScreen.tabs: get List<LibraryTab>
LibraryScreen.createState: State<LibraryScreen>()
LibraryState: sealed class permits {LibraryEmpty, LibraryFailed, LibraryLoading, LibraryNoMatch, LibraryOffline, LibraryShown}
LibraryState.new: const new()
LibraryLoading: final class extends LibraryState
LibraryLoading.new: const new()
LibraryFailed: final class extends LibraryState
LibraryFailed.new: const new()
LibraryOffline: final class extends LibraryState
LibraryOffline.new: const new()
LibraryEmpty: final class extends LibraryState
LibraryEmpty.new: const new()
LibraryNoMatch: final class extends LibraryState
LibraryNoMatch.new: const new(String query)
LibraryNoMatch.query: get String
LibraryMore: sealed class permits {LibraryMoreAvailable, LibraryMoreEnd, LibraryMoreFailed, LibraryMoreLoading}
LibraryMore.new: const new()
LibraryMoreAvailable: final class extends LibraryMore
LibraryMoreAvailable.new: const new(String after)
LibraryMoreAvailable.after: get String
LibraryMoreLoading: final class extends LibraryMore
LibraryMoreLoading.new: const new(String after)
LibraryMoreLoading.after: get String
LibraryMoreFailed: final class extends LibraryMore
LibraryMoreFailed.new: const new(String after, {required bool offline})
LibraryMoreFailed.after: get String
LibraryMoreFailed.offline: get bool
LibraryMoreEnd: final class extends LibraryMore
LibraryMoreEnd.new: const new()
LibraryNotice: enum {refreshFailed, refreshOffline}
LibraryNotice.refreshFailed: static final LibraryNotice
LibraryNotice.refreshOffline: static final LibraryNotice
LibraryShown: final class extends LibraryState
LibraryShown.new: new(List<LibraryEntry> entries, {required int total, LibraryMore more, bool refreshing, LibraryNotice? notice})
LibraryShown.entries: get List<LibraryEntry>
LibraryShown.total: get int
LibraryShown.more: get LibraryMore
LibraryShown.refreshing: get bool
LibraryShown.notice: get LibraryNotice?
LibraryController: class extends ChangeNotifier
LibraryController.new: new({required LibraryService service, LibraryTab tab, String query, LibraryState initial, Duration debounce})
LibraryController.service: get LibraryService
LibraryController.debounce: get Duration
LibraryController.state: get LibraryState
LibraryController.tab: get LibraryTab
LibraryController.query: get String
LibraryController.shownQuery: get String
LibraryController.load: Future<void>()
LibraryController.setTab: void(LibraryTab tab)
LibraryController.setQuery: void(String query)
LibraryController.submit: void()
LibraryController.refresh: Future<void>()
LibraryController.loadMore: Future<bool>()
LibraryController.dispose: void()
LibraryTab: enum {songs, albums, playlists}
LibraryTab.songs: static final LibraryTab
LibraryTab.albums: static final LibraryTab
LibraryTab.playlists: static final LibraryTab
LibraryEntry: sealed class permits {LibraryAlbum, LibraryPlaylist, LibraryTrack}
LibraryEntry.new: const new({required String id, required String title, ImageProvider? artwork})
LibraryEntry.id: get String
LibraryEntry.title: get String
LibraryEntry.artwork: get ImageProvider?
LibraryTrack: final class extends LibraryEntry
LibraryTrack.new: const new({required super.id, required super.title, required String artist, required String album, required Duration duration, super.artwork})
LibraryTrack.artist: get String
LibraryTrack.album: get String
LibraryTrack.duration: get Duration
LibraryAlbum: final class extends LibraryEntry
LibraryAlbum.new: const new({required super.id, required super.title, required String artist, required int year, super.artwork})
LibraryAlbum.artist: get String
LibraryAlbum.year: get int
LibraryPlaylist: final class extends LibraryEntry
LibraryPlaylist.new: const new({required super.id, required super.title, required int trackCount, super.artwork})
LibraryPlaylist.trackCount: get int
LibraryPage: final class
LibraryPage.new: const new({required List<LibraryEntry> entries, required int total, String? next})
LibraryPage.entries: get List<LibraryEntry>
LibraryPage.total: get int
LibraryPage.next: get String?
LibraryNoNetwork: final class implements Exception
LibraryNoNetwork.new: const new()
LibraryService: abstract interface class
LibraryService.new: new()
LibraryService.entries: abstract Future<LibraryPage>(LibraryTab tab, {String query, String? after})
LibraryNowPlaying: final class
LibraryNowPlaying.new: const new({required String trackId, required String title, required String artist, required bool playing, bool buffering, ImageProvider? artwork})
LibraryNowPlaying.trackId: get String
LibraryNowPlaying.title: get String
LibraryNowPlaying.artist: get String
LibraryNowPlaying.playing: get bool
LibraryNowPlaying.buffering: get bool
LibraryNowPlaying.artwork: get ImageProvider?
LibraryNowPlayingOf: final class<T> implements ValueListenable<LibraryNowPlaying?>
LibraryNowPlayingOf.new: const new(ValueListenable<T> source, LibraryNowPlaying? Function(T state) read)
LibraryNowPlayingOf.source: get ValueListenable<T>
LibraryNowPlayingOf.read: get LibraryNowPlaying? Function(T state)
LibraryNowPlayingOf.value: get LibraryNowPlaying?
LibraryNowPlayingOf.addListener: void(VoidCallback listener)
LibraryNowPlayingOf.removeListener: void(VoidCallback listener)
sampleLibraryTracks: get List<LibraryTrack>
sampleLibraryAlbums: const List<LibraryAlbum>
sampleLibraryPlaylists: const List<LibraryPlaylist>
sampleLibraryOf: List<LibraryEntry>(LibraryTab tab)
SampleLibraryService: final class implements LibraryService
SampleLibraryService.new: new({int pageSize, Duration latency, List<LibraryEntry> Function(LibraryTab tab) entriesOf})
SampleLibraryService.pageSize: get int
SampleLibraryService.latency: get Duration
SampleLibraryService.entriesOf: get List<LibraryEntry> Function(LibraryTab tab)
SampleLibraryService.requests: get List<(LibraryTab, String, String?)>
SampleLibraryService.entries: Future<LibraryPage>(LibraryTab tab, {String query, String? after})
SampleNowPlaying: final class extends ValueNotifier<LibraryNowPlaying?>
SampleNowPlaying.new: new([super.initial])
SampleNowPlaying.play: void(LibraryTrack track)
SampleNowPlaying.playPause: void()
libraryL10n: const Map<String, Map<String, String>>
LibraryStrings: final class
LibraryStrings.new: const new(String language)
LibraryStrings.of: factory new(BuildContext context)
LibraryStrings.language: get String
LibraryStrings.digits: String(int n)
LibraryStrings.clock: String(Duration time)
LibraryStrings.spoken: String(Duration time)
LibraryStrings.title: get String
LibraryStrings.back: get String
LibraryStrings.refresh: get String
LibraryStrings.loading: get String
LibraryStrings.tab: String(LibraryTab tab)
LibraryStrings.search: get String
LibraryStrings.searchHint: get String
LibraryStrings.clear: get String
LibraryStrings.nowPlaying: get String
LibraryStrings.paused: get String
LibraryStrings.openPlayer: String(LibraryNowPlaying now)
LibraryStrings.play: get String
LibraryStrings.pause: get String
LibraryStrings.buffering: get String
LibraryStrings.trackDetail: String(LibraryTrack track)
LibraryStrings.tracks: String(int count)
LibraryStrings.results: String(int count)
LibraryStrings.emptyTitle: String(LibraryTab tab)
LibraryStrings.emptyMessage: get String
LibraryStrings.noMatchTitle: String(String query)
LibraryStrings.noMatchMessage: get String
LibraryStrings.loadingMore: get String
LibraryStrings.moreFailed: get String
LibraryStrings.moreOffline: get String
LibraryStrings.end: get String
LibraryStrings.refreshFailed: get String
LibraryStrings.refreshOffline: get String
LibraryStrings.errorTitle: get String
LibraryStrings.errorMessage: get String
LibraryStrings.offlineTitle: get String
LibraryStrings.offlineMessage: get String
LibraryStrings.retry: get String

Strings

Every label in English, French and Arabic. The screen follows the closest Localizations; the same table is in the manifest, ready for your ARB files.

Keyenfrar
libraryTitleLibraryBibliothèqueالمكتبة
libraryBackBackRetourرجوع
libraryRefreshRefresh the libraryActualiser la bibliothèqueتحديث المكتبة
libraryLoadingLoading your libraryChargement de la bibliothèqueجارٍ تحميل المكتبة
librarySongsSongsMorceauxالمقاطع
libraryAlbumsAlbumsAlbumsالألبومات
libraryPlaylistsPlaylistsPlaylistsقوائم التشغيل
librarySearchSearch the libraryRechercher dans la bibliothèqueالبحث في المكتبة
librarySearchHintTitle or artistTitre ou artisteالعنوان أو الفنان
libraryClearClear searchEffacer la rechercheمسح البحث
libraryNowPlayingNow playingEn cours de lectureقيد التشغيل
libraryPausedPausedEn pauseمتوقف مؤقتًا
libraryOpenPlayerNow playing: {title}, {artist}Lecture en cours : {title}, {artist}قيد التشغيل: ⁨{title}⁩، ⁨{artist}⁩
libraryOpenPlayerPausedPaused: {title}, {artist}En pause : {title}, {artist}متوقف مؤقتًا: ⁨{title}⁩، ⁨{artist}⁩
libraryPlayPlayLireتشغيل
libraryPausePauseMettre en pauseإيقاف مؤقت
libraryBufferingBuffering…Mise en mémoire tampon…جارٍ التخزين المؤقت…
libraryTrackDetail{artist} · {album}{artist} · {album}⁨{artist}⁩ · ⁨{album}⁩
libraryZero0 seconds0 seconde٠ ثانية
libraryHoursOne1 hour1 heureساعة واحدة
libraryHoursTwo2 hours2 heuresساعتان
libraryHours{count} hours{count} heures{count} ساعات
libraryHoursMany{count} hours{count} heures{count} ساعة
libraryHoursOther{count} hours{count} heures{count} ساعة
libraryMinutesOne1 minute1 minuteدقيقة واحدة
libraryMinutesTwo2 minutes2 minutesدقيقتان
libraryMinutes{count} minutes{count} minutes{count} دقائق
libraryMinutesMany{count} minutes{count} minutes{count} دقيقة
libraryMinutesOther{count} minutes{count} minutes{count} دقيقة
librarySecondsOne1 second1 secondeثانية واحدة
librarySecondsTwo2 seconds2 secondesثانيتان
librarySeconds{count} seconds{count} secondes{count} ثوانٍ
librarySecondsMany{count} seconds{count} secondes{count} ثانية
librarySecondsOther{count} seconds{count} secondes{count} ثانية
libraryTracksZeroNo songsAucun morceauلا توجد مقاطع
libraryTracksOne1 song1 morceauمقطع واحد
libraryTracksTwo2 songs2 morceauxمقطعان
libraryTracks{count} songs{count} morceaux{count} مقاطع
libraryTracksMany{count} songs{count} morceaux{count} مقطعًا
libraryTracksOther{count} songs{count} morceaux{count} مقطع
libraryResultsZeroNo results foundAucun résultat trouvéلم يُعثر على أي نتيجة
libraryResultsOne1 result1 résultatنتيجة واحدة
libraryResultsTwo2 results2 résultatsنتيجتان
libraryResults{count} results{count} résultats{count} نتائج
libraryResultsMany{count} results{count} résultats{count} نتيجة
libraryResultsOther{count} results{count} résultats{count} نتيجة
libraryEmptySongsNo songs yetAucun morceau pour l’instantلا توجد مقاطع بعد
libraryEmptyAlbumsNo albums yetAucun album pour l’instantلا توجد ألبومات بعد
libraryEmptyPlaylistsNo playlists yetAucune playlist pour l’instantلا توجد قوائم تشغيل بعد
libraryEmptyMessageWhat you save will show here.Ce que vous enregistrez s’affichera ici.سيظهر هنا ما تحفظه.
libraryNoMatchTitleNothing matches “{query}”Rien ne correspond à « {query} »لا شيء يطابق «⁨{query}⁩»
libraryNoMatchMessageSearch by title or by artist.Cherchez par titre ou par artiste.ابحث بالعنوان أو باسم الفنان.
libraryLoadingMoreLoading moreChargement de la suiteجارٍ تحميل المزيد
libraryMoreFailedMore could not load.La suite n’a pas pu se charger.تعذّر تحميل المزيد.
libraryMoreOfflineYou are offline. Connect to see more.Vous êtes hors ligne. Connectez-vous pour voir la suite.أنت غير متصل. اتصل لعرض المزيد.
libraryEndThat is everything.Vous avez tout vu.هذا كل شيء.
libraryRefreshFailedYour library could not refresh.La bibliothèque n’a pas pu se mettre à jour.تعذّر تحديث المكتبة.
libraryRefreshOfflineYou are offline. Your library may be out of date.Vous êtes hors ligne. La bibliothèque n’est peut-être pas à jour.أنت غير متصل. قد لا تكون المكتبة محدّثة.
libraryErrorTitleYour library could not loadImpossible de charger la bibliothèqueتعذّر تحميل المكتبة
libraryErrorMessageTry again in a moment.Réessayez dans un instant.أعد المحاولة بعد قليل.
libraryOfflineTitleYou are offlineVous êtes hors ligneأنت غير متصل
libraryOfflineMessageConnect to see your library.Connectez-vous pour voir votre bibliothèque.اتصل لعرض مكتبتك.
libraryRetryTry againRéessayerأعد المحاولة

Reference renders

The 16 images published with version 0.2.5, rendered by the shared harness on macOS with Flutter 3.47.7. Each shows every state side by side, at the size of an iPhone (390 × 844) or of an Android phone (412 × 915). fliin verify library compares your copy to them.

iOS · Light
Music library, iOS, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Music library, iOS, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Music library, iOS, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Music library, iOS, Light, text 2.0×, Right to left
Text 2.0× · Right to left
iOS · Dark
Music library, iOS, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Music library, iOS, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Music library, iOS, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Music library, iOS, Dark, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Light
Music library, Android, Light, text 1.0×, Left to right
Text 1.0× · Left to right
Music library, Android, Light, text 1.0×, Right to left
Text 1.0× · Right to left
Music library, Android, Light, text 2.0×, Left to right
Text 2.0× · Left to right
Music library, Android, Light, text 2.0×, Right to left
Text 2.0× · Right to left
Android · Dark
Music library, Android, Dark, text 1.0×, Left to right
Text 1.0× · Left to right
Music library, Android, Dark, text 1.0×, Right to left
Text 1.0× · Right to left
Music library, Android, Dark, text 2.0×, Left to right
Text 2.0× · Left to right
Music library, Android, Dark, text 2.0×, Right to left
Text 2.0× · Right to left

Use it to

  • show a listener's music library: songs, albums and playlists
  • pick a song by its title or its artist
  • show albums as a grid of artwork
  • open an album or a playlist from the music library
  • make more songs appear while scrolling the library
Not for
  • the player currently playing: use the music player (player)
  • a search in a product catalog: use the search screen (search)
States
  • Songs
  • Albums
  • Playlists
  • No match
  • Empty
  • Loading
  • Error
  • Offline

Arrows to move, Enter to open, Escape to close.