Flutter Music App Example With API Deezer

Ecco Suprastyo
9 min readJan 22, 2020

--

You can see FULL SECTION this article in this http://camellabs.com/flutter-music-app-example-with-api-deezer/.

The Example will show about Flutter Music App Example With API Deezer. This article will creating a simple application using flutter_sound: ^2.0.0. This plugin handles file from remote url. This plugin can handle playback stream from native (To sync exact time with bridging). This example use API from Deezer to get music catalogue. Deezer API provides you some global parameters in order to simplify and organize your requests . Deezer give unlimited Access, without stress, without identification. Deezer Simple API provides a nice set of services to build up web applications allowing the discovery of Deezer’s music catalogue.

You can read other article with flutter in this section :

Preparation

  1. Create Deezer Account. Create Here

2. Go to https://developers.deezer.com/ then create app. You can find Application ID and Secret key after created. ( for Example : fluttermusic )

3. Manage Oauth Deezer API, he documentation in Here. Running the command in your browser like below:

https://connect.deezer.com/oauth/auth.php?app_id=YOUR_APP_ID&redirect_uri=YOUR_REDIRECT_URI&perms=basic_access,email

If the user is not connected, this login popup will open so that they can be authenticated.

If the user clicks on “don’t allow” your application is not authorized. The browser then redirects the user to the ‘redirect_uri’ (via HTTP 302) with the parameter ‘error_reason’ = ‘user_denied’

http://redirect_uri?error_reason=user_denied

If the user clicks on “allow” your application authorized. The browser then redirects the user to the ‘redirect_uri’ (via HTTP 302) with the parameter ‘code’ = an authorization code.

http://redirect_uri?code=A_CODE_GENERATED_BY_DEEZER

With this code, you will be able to request an access token which is necessary to make action requiring the permissions you asked. This step is needed to authenticate the application. To do so, you need to pass the code you just receive and your application secret code to the Deezer access_token url :

This will authenticate your application and deliver the access token.

https://connect.deezer.com/oauth/access_token.php?app_id=YOU_APP_ID&secret=YOU_APP_SECRET&code=THE_CODE_FROM_ABOVE

If your application is successfully authenticated and the authorization code is valid, the authorization server will return the access token.

Building

First, we must create a project using Visual Studio Code software with name “fluttermusic”. Let’s to create a new project using Visual Studio Code:

  1. Invoke View > Command Palette.
  2. Type “flutter”, and select the Flutter: New Project.
  3. Enter a project name, example such as “fluttermusic”, and press Enter.
  4. Create or select the parent directory for the new project folder.
  5. Wait for project creation progress to complete and the main.dart file to appear.

Next, add the flutter_sound package to your project. The flutter_sound is This plugin provides simple recorder and player functionalities for both `android` and `ios` platforms. This only supports default file extension for each platform. This plugin handles file from remote url. This plugin can handle playback stream from native (To sync exact time with bridging).

dependencies: 
flutter_sound: <latest_version>

Then we click the get package, wait until the process is complete. The file pubspec.yaml like that :

name: fluttermusic
description: A new Flutter project.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
http: ^0.12.0+4
flutter_sound: ^2.0.0
intl: ^0.15.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.7.0
flutter_icons:
android: true
ios: true
image_path: "res/icon/icon.png"
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
assets:
- res/icons/ic_play.png
- res/icons/2.0x/ic_play.png
- res/icons/3.0x/ic_play.png
- res/icons/ic_stop.png
- res/icons/2.0x/ic_stop.png
- res/icons/3.0x/ic_stop.png
- res/icons/ic_pause.png
- res/icons/2.0x/ic_pause.png
- res/icons/3.0x/ic_pause.png
- res/icons/ic_mic.png
- res/icons/2.0x/ic_mic.png
- res/icons/3.0x/ic_mic.png
- res/icons/ic_volume_down.png
- res/icons/2.0x/ic_volume_down.png
- res/icons/3.0x/ic_volume_down.png
- res/icons/ic_volume_up.png
- res/icons/2.0x/ic_volume_up.png
- res/icons/3.0x/ic_volume_up.png
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

Later, create RouteClass to access Deezer API. (example : musicservice.dart)

import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'genre.dart';
import 'song.dart';
import 'chart.dart';
import 'album.dart';
import 'home.dart';
import 'artist.dart';
import 'search.dart';
class AppleMusicStore {
AppleMusicStore._privateConstructor();
static final AppleMusicStore instance = AppleMusicStore._privateConstructor();

static const BASE_URL = 'https://api.deezer.com';
static const GENRE_URL = "$BASE_URL/genres";
static const _SONG_URL = "$BASE_URL/songs";
static const _ALBUM_URL = "$BASE_URL/album";
static const _CHART_URL = "$BASE_URL/chart";
static const _ARTIST_URL = "$BASE_URL/artist";
static const _SEARCH_URL = "$BASE_URL/search";
static const JWT_KEY = 'access_token';
Future<dynamic> _fetchJSON(String url) async {
final response =
await http.get(url, headers: {'Authorization': "Bearer $JWT_KEY"});
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to fetch data');
}
}
Future<Song> fetchSongById(String id) async {
final json = await _fetchJSON("$_SONG_URL/$id");
return Song.fromJson(json['data'][0]);
}
Future<Album> fetchAlbumById(String id) async {
final json = await _fetchJSON("$_ALBUM_URL/$id");
return Album.fromJson(json);
}
Future<Artist> fetchArtistById(int id) async {
final json = await _fetchJSON("$_ARTIST_URL/$id?include=albums,songs");
return Artist.fromJson(json['data'][0]);
}
Future<Chart> fetchAlbumsAndSongsTopChart() async {
final url = "$_CHART_URL";
final json = await _fetchJSON(url);
final songChartJSON = json['tracks'];
final songChart = SongChart.fromJson(songChartJSON);
final albumChartJSON = json['albums']; final albumChart = AlbumChart.fromJson(albumChartJSON); final chart = Chart(albumChart: albumChart, songChart: songChart);
return chart;
}
Future<Search> search(String query) async {
final urlArtist = "$_SEARCH_URL/artist?q=$query";
final urlAlbum = "$_SEARCH_URL/album?q=$query";
final urlTrack = "$_SEARCH_URL/track?q=$query";
final encodedArtist = Uri.encodeFull(urlArtist);
final encodedAlbum = Uri.encodeFull(urlAlbum);
final encodedTrack = Uri.encodeFull(urlTrack);
final jsonArtist = await _fetchJSON(encodedArtist);
final jsonAlbum = await _fetchJSON(encodedAlbum);
final jsonTrack = await _fetchJSON(encodedTrack);
final List<Album> albums = [];
final List<Song> songs = [];
final List<Artist> artists = [];
final artistJSON = jsonArtist['data'] as List;
if (artistJSON != null) {
artists
.addAll((artistJSON).map((a) => Artist.fromJson(a)));
}
final albumsJSON = jsonAlbum['data'] as List;
if (albumsJSON != null) {
albums.addAll((albumsJSON).map((a) => Album.fromJson(a)));
}
final songJSON = jsonTrack['data'] as List;
if (songJSON != null) {
songs.addAll((songJSON).map((a) => Song.fromJson(a)));
}
return Search(albums: albums, songs: songs, artists: artists, term: query);
}
Future<Home> fetchBrowseHome() async {
final chart = await fetchAlbumsAndSongsTopChart();
final List<Album> albums = [];
final album3 = await fetchAlbumById(chart.albumChart.albums[0].id); albums.add(album3); return Home(chart: chart, albums: albums);
}
}

For Serializing JSON we need model classes, The models are song.dart , album.dart , artist.dart, chart.dart.

We will create browse_widget.dart to main screen, Our browse screen can be allow the user to select Top Album Chart,Top Songs Chart and List Songs from Top One Album Chart. The browse screen code like below:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'musicservice.dart';
import 'carousel_song_widget.dart';
import 'carousel_album.dart';
import 'album_widget.dart';
import 'home.dart';
class BrowseWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _BrowseWidgetState();
}
}
class _BrowseWidgetState extends State<BrowseWidget> {
final musicStore = AppleMusicStore.instance;
Future<Home> _home;
@override
void initState() {
super.initState();
_home = musicStore.fetchBrowseHome();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Browse'),
),
child: FutureBuilder<Home>(
future: _home,
builder: (context, snapshot) {
if (snapshot.hasData) {
final albumChart = snapshot.data.chart.albumChart;
final List<Widget> list = []; if (albumChart != null) {
list.add(Padding(
padding: EdgeInsets.only(top: 16),
));
list.add(CarouselAlbumWidget(
title: "Top Albums",
albums: albumChart.albums,
));
}
final songChart = snapshot.data.chart.songChart; if (songChart != null) {
list.add(Padding(
padding: EdgeInsets.only(top: 16),
));
list.add(CarouselSongWidget(
title: "Top Songs",
songs: songChart.songs,
));
}
snapshot.data.albums.forEach((f) {
list.add(Padding(
padding: EdgeInsets.only(top: 16),
));
list.add(CarouselSongWidget(
title: f.title,
songs: f.songs,
cta: 'See Album',
onCtaTapped: () {
Navigator.of(context, rootNavigator: true)
.push(CupertinoPageRoute(
builder: (context) => AlbumWidget(
albumId: f.id,
albumName: f.title,
)));
},
));
});
return ListView(
children: list,
);
} else if (snapshot.hasError) {
return Center(child: Text("${snapshot.error}"));
}
return Center(child: CircularProgressIndicator());
},
));
}
}

And the second screen is search widget. This widget works to find track,album,or artis you want. The code like below :

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:fluttermusic/album.dart';
import 'cupertino_search_bar.dart';
import 'search.dart';
import 'musicservice.dart';
import 'carousel_song_widget.dart';
import 'carousel_album.dart';
import 'divider_widget.dart';
import 'artist_widget.dart';
import 'artist.dart';
import 'song.dart';
class SearchWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return SearchWidgetState();
}
}
class SearchWidgetState extends State<SearchWidget>
with SingleTickerProviderStateMixin {
TextEditingController _searchTextController = TextEditingController();
FocusNode _searchFocusNode = FocusNode();
Animation _animation;
AnimationController _animationController;
Future<Search> _search;
AppleMusicStore musicStore = AppleMusicStore.instance;
String _searchTextInProgress;
@override
initState() {
super.initState();
_animationController = new AnimationController(
duration: new Duration(milliseconds: 200),
vsync: this,
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
reverseCurve: Curves.easeInOut,
);
_searchFocusNode.addListener(() {
if (!_animationController.isAnimating) {
_animationController.forward();
}
});
_searchTextController.addListener(_performSearch);
}
_performSearch() {
final text = _searchTextController.text;
if (_search != null && text == _searchTextInProgress) {
return;
}
if (text.isEmpty) {
this.setState(() {
_search = null;
_searchTextInProgress = null;
});
return;
}
this.setState(() {
_search = musicStore.search(text);
_searchTextInProgress = text;
});
}
_cancelSearch() {
_searchTextController.clear();
_searchFocusNode.unfocus();
_animationController.reverse();
this.setState(() {
_search = null;
_searchTextInProgress = null;
});
}
_clearSearch() {
_searchTextController.clear();
this.setState(() {
_search = null;
_searchTextInProgress = null;
});
}
@override
dispose() {
_searchTextController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: IOSSearchBar(
controller: _searchTextController,
focusNode: _searchFocusNode,
animation: _animation,
onCancel: _cancelSearch,
onClear: _clearSearch,
)),
child: GestureDetector(
onTapUp: (TapUpDetails _) {
_searchFocusNode.unfocus();
if (_searchTextController.text == '') {
_animationController.reverse();
}
},
child: _search != null
? FutureBuilder<Search>(
future: _search,
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState != ConnectionState.waiting) {
final searchResult = snapshot.data;

List<Song> songs = searchResult.songs;
if (songs.length > 3) {
songs = songs.sublist(0, 10);
}
List<Album> albums = searchResult.albums;
if (albums.length > 3) {
albums = albums.sublist(0, 10);
}
List<Artist> artists = searchResult.artists;
if (artists.length > 3) {
artists = artists.sublist(0, 3);
}
final List<Widget> list = []; if (artists != null && artists.isNotEmpty) {
list.add(Padding(
padding:
EdgeInsets.only(top: 16, left: 20, right: 20),
child: Text(
'Artists',
style: Theme.of(context).textTheme.title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)));
list.add(
DividerWidget(
margin: const EdgeInsets.only(
top: 8.0, left: 20.0, right: 20.0),
),
);
artists.forEach((a) {
list.add(
Padding(
padding: EdgeInsets.only(
top: 16, left: 20, right: 20, bottom: 16),
child: Material(
color: Colors.white,
child: InkWell(
onTap: () {
Navigator.of(context, rootNavigator: true)
.push(CupertinoPageRoute(
builder: (context) =>
ArtistWidget(
artistId: a.id,
artistName: a.name)));
},
child: Text(
a.name,
style: Theme.of(context).textTheme.body1,
overflow: TextOverflow.ellipsis,
maxLines: 1,
)),
)),
);
list.add(
DividerWidget(
margin: const EdgeInsets.only(
top: 0.0, left: 36.0, right: 20.0),
),
);
});
}
if (albums != null && albums.isNotEmpty) {
list.add(Padding(
padding: EdgeInsets.only(top: 16),
));
list.add(CarouselAlbumWidget(
title: 'Albums',
albums: albums,
));
}
if (songs != null && songs.isNotEmpty) {
list.add(Padding(
padding: EdgeInsets.only(top: 16),
));
list.add(CarouselSongWidget(
title: 'Songs',
songs: songs,
));
}
return ListView(
children: list,
);
} else if (snapshot.hasError) {
return Center(child: Text("${snapshot.error}"));
}
return Center(child: CircularProgressIndicator());
},
)
: Center(
child: Text(
'Type on search bar to begin')), // Add search body here
),
);
}
}

The application can be run to show output below :

For complete source code you can see flutter music app github in Here.

Thank you for reading this article about Flutter Music Player App using Deezer API, I hope this article is useful for you. Visit My Github about Flutter in Here.

--

--