spotify_sdk 0.3.4

  • Readme
  • Changelog
  • Example
  • Installing
  • 86

spotify_sdk #

pub package Test and Build licence

Description #

This will be a spotify_sdk package for flutter using both the spotify-app-remote sdk and spotify-auth library. The auth library is needed to get the authentication token to work with the web api.

Setup #

Android #

From the Spotify Android SDK Quick Start. You need two things:

  1. register your app in the spotify developer portal. You also need to create a sha-1 fingerprint and add this and your package name to the app settings on the dashboard as well as a redirect url.
  2. download the current Spotify Android SDK. Here you need the spotify-app-remote-.aar and spotify-auth-.aar.

After you are all setup you need to add the *.aar files to your Android Project as Modules. See the Spotify Android SDK Quick Start for detailed information.

Important here is the naming so that the package can find the modules.

  • Remote: spotify-app-remote
  • Auth: spotify-auth

Usage #

To start using this package you first have to connect to Spotify. To only connect you can do this with connectToSpotifyRemote(...) or getAuthenticationToken(...) in both of these methods you need the client id, given in the spotify dashboard and the redirect url you set in the settings on the dashboard.

await SpotifySdk.connectToSpotifyRemote(clientId: "", redirectUrl: "")

If you want to use the web api aswell you have to use this method to get the authentication token.

var authenticationToken = await SpotifySdk.getAuthenticationToken(clientId: "", redirectUrl: "");

tbd...

Have a look here for a more detailed example.

Api #

Connecting/Authenticating

FunctionDescriptionAndroidiOS
connectToSpotifyRemoteConnects the App to Spotify✔️:construction_worker:
getAuthenticationTokenGets the Authentication Token that you can use to work with the Web Api✔️:construction_worker:
logoutlogs the user out and disconnects the app connection:construction_worker::construction_worker:

Player Api

FunctionDescriptionAndroidiOS
getCrossfadeStateGets the current crossfade state✔️:construction_worker:
getPlayerStateGets the current player state✔️:construction_worker:
pausePauses the current track✔️:construction_worker:
playPlays the given spotifyUri✔️:construction_worker:
queueQueues given spotifyUri✔️:construction_worker:
resumeResumes the current track✔️:construction_worker:
skipNextSkips to next track✔️:construction_worker:
skipPreviousSkips to previous track✔️:construction_worker:
seekToSeeks the current track to the given position in milliseconds✔️:construction_worker:
seekToRelativePositionAdds to the current position of the track the given milliseconds✔️:construction_worker:
subscribeToPlayerContextSubscribes to the current player context✔️:construction_worker:
subscribeToPlayerStateSubscribes to the current player state✔️:construction_worker:
getCrossfadeStateGets the current crossfade state✔️:construction_worker:
toggleShuffleCycles through the shuffle modes✔️:construction_worker:
toggleRepeatCycles through the repeat modes✔️:construction_worker:

Images Api

FunctionDescriptionAndroidiOS
getImageGet the image from the given spotifyUri✔️:construction_worker:

User Api

FunctionDescriptionAndroidiOS
addToLibraryAdds the given spotifyUri to the users library✔️:construction_worker:
getCapabilitiesGets the current users capabilities✔️:construction_worker:
getLibraryStateGets the current library state✔️:construction_worker:
removeFromLibraryRemoves the given spotifyUri to the users library✔️:construction_worker:
subscribeToCapabilitiesSubscribes to the current users capabilities✔️:construction_worker:
subscribeToUserStatusSubscrives to the current users status✔️:construction_worker:

Connect Api

FunctionDescriptionAndroidiOS
connectSwitchToLocalDeviceSwitch to play music on this (local) device:construction_worker::construction_worker:

Content Api

FunctionDescriptionAndroidiOS
getChildrenOfItemtbd:construction_worker::construction_worker:
getRecommendedContentItemstbd:construction_worker::construction_worker:
playContentItemtbd:construction_worker::construction_worker:

Official Spotify Docs #

0.3.4 #

  • adds handling of unexpected disconnects from Spotify via subscribeConnectionStatus()-Stream(thanks itsMatoosh)
  • adds usage of .env file for the example project
  • fixes some minor error message issues

0.3.3 #

  • adds getImage to get an Image from any spotifyURI (thanks eddwhite)
  • fixes some minor issues
  • raised dart-sdk version to 2.7.0

0.3.2 #

  • fixes compatibility with spotify-auth dependency above version 1.2.0 (thanks itsMatoosh)
    • spotify introduced some breaking changes: Rename classes from AuthenticationClassName to AuthorizationClassName

0.3.1 #

  • fixes wrong links and incorrect docs

0.3.0 #

  • android user api implementation finished
    • remove from library
    • subscribe to user status
    • subscribe to capabilities
    • get librarystate
  • updated package references

0.2.0 #

  • android player api implementation subscriptions finished
    • subscribe to playerContext and playerState now possible
  • added more instructions for android
  • code refactoring
  • extended the example
  • extended the documentation

0.1.0 #

  • android player api implementation finished
  • error handling finished
  • setup instructions for android finished
  • naming for modules finished

0.0.4 #

  • added instructions for android
  • fixed naming for modules

0.0.3 #

  • added the following implementations for android:
    • getCrossfadeState
    • getPlayerState
  • general refactoring of the native android implementation
  • adds documentation to all methods
  • adds json_annotation: ^3.0.0, build_runner: ^1.0.0, json_serializable: ^3.2.0 to make use of some json serializing functionality for the crossfadeState and PlayerState

0.0.2 #

  • added the following implementations for android:
    • resume
    • skip next
    • skip previous
    • seek to
    • seek to relative
  • splitted remote authorization and token retrieval in two seperate functions
  • implemented the example project
  • added logger package for prettier logs

0.0.1 #

  • Added latests spotify-app-remote (v7.0.0) and spotify-auth (v1.1.0) from https://github.com/spotify/android-sdk/releases
  • finished android native implementation for
    • authorization with token response
    • play
    • pause
    • queue
    • toggleShuffle
    • toggleRepeat
    • addToLibrary

example/lib/main.dart

import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:spotify_sdk/models/crossfade_state.dart';
import 'package:spotify_sdk/spotify_sdk.dart';
import 'package:spotify_sdk/models/player_state.dart';
import 'package:spotify_sdk/models/player_context.dart';
import 'package:spotify_sdk/models/connection_status.dart';
import 'package:spotify_sdk/models/image_uri.dart';
import 'package:logger/logger.dart';

import 'widgets/sized_icon_button.dart';

Future<void> main() async {
  await DotEnv().load('.env');
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Home());
  }
}

class Home extends StatefulWidget {
  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  bool _loading = false;
  final Logger _logger = Logger();

  CrossfadeState crossfadeState;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SpotifySdk Example"),
      ),
      body: _sampleFlowWidget(context),
    );
  }

  Widget _sampleFlowWidget(BuildContext context2) {
    return StreamBuilder<ConnectionStatus>(
      stream: SpotifySdk.subscribeConnectionStatus(),
      builder: (context, snapshot) {
        bool _connected = false;
        if (snapshot.data != null) {
          _connected = snapshot.data.connected;
        }

        return Stack(
          children: [
            ListView(
              padding: EdgeInsets.all(8),
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    FlatButton(
                      child: Icon(Icons.settings_remote),
                      onPressed: () => connectToSpotifyRemote(),
                    ),
                    FlatButton(
                      child: Text("get auth token "),
                      onPressed: () => getAuthenticationToken(),
                    ),
                  ],
                ),
                Divider(),
                Text("Player State", style: TextStyle(fontSize: 16)),
                _connected
                    ? PlayerStateWidget()
                    : Center(
                        child: Text("Not connected"),
                      ),
                Divider(),
                Text("Player Context", style: TextStyle(fontSize: 16)),
                _connected
                    ? PlayerContextWidget()
                    : Center(
                        child: Text("Not connected"),
                      ),
                Divider(),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    SizedIconButton(
                      width: 50,
                      icon: Icons.skip_previous,
                      onPressed: () => skipPrevious(),
                    ),
                    SizedIconButton(
                      width: 50,
                      icon: Icons.play_arrow,
                      onPressed: () => resume(),
                    ),
                    SizedIconButton(
                      width: 50,
                      icon: Icons.pause,
                      onPressed: () => pause(),
                    ),
                    SizedIconButton(
                      width: 50,
                      icon: Icons.skip_next,
                      onPressed: () => skipNext(),
                    ),
                  ],
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: <Widget>[
                    SizedIconButton(
                      width: 50,
                      icon: Icons.queue_music,
                      onPressed: () => queue(),
                    ),
                    SizedIconButton(
                      width: 50,
                      icon: Icons.play_circle_filled,
                      onPressed: () => play(),
                    ),
                    SizedIconButton(
                      width: 50,
                      icon: Icons.repeat,
                      onPressed: () => toggleRepeat(),
                    ),
                    SizedIconButton(
                      width: 50,
                      icon: Icons.shuffle,
                      onPressed: () => toggleShuffle(),
                    ),
                  ],
                ),
                FlatButton(
                    child: Icon(Icons.favorite),
                    onPressed: () => addToLibrary()),
                Row(
                  children: <Widget>[
                    FlatButton(
                        child: Text("seek to"), onPressed: () => seekTo()),
                    FlatButton(
                        child: Text("seek to relative"),
                        onPressed: () => seekToRelative()),
                  ],
                ),
                Divider(),
                Text("Crossfade State", style: TextStyle(fontSize: 16)),
                FlatButton(
                    child: Text("getCrossfadeState"),
                    onPressed: () => getCrossfadeState()),
                Text("Is enabled: ${crossfadeState?.isEnabled}"),
                Text("Duration: ${crossfadeState?.duration}"),
                Divider(),
                _connected
                    ? SpotifyImageWidget()
                    : Text('Connect to see an image...'),
              ],
            ),
            _loading
                ? Container(
                    color: Colors.black12,
                    child: Center(child: CircularProgressIndicator()))
                : SizedBox(),
          ],
        );
      },
    );
  }

  Widget PlayerStateWidget() {
    return StreamBuilder<PlayerState>(
      stream: SpotifySdk.subscribePlayerState(),
      initialData: PlayerState(null, false, 1, 1, null, null),
      builder: (BuildContext context, AsyncSnapshot<PlayerState> snapshot) {
        if (snapshot.data != null && snapshot.data.track != null) {
          var playerState = snapshot.data;
          return Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(
                  "${playerState.track.name} by ${playerState.track.artist.name} from the album ${playerState.track.album.name} "),
              Text("Speed: ${playerState.playbackSpeed}"),
              Text(
                  "Progress: ${playerState.playbackPosition}ms/${playerState.track.duration}ms"),
              Text("IsPaused: ${playerState.isPaused}"),
              Text("Is Shuffling: ${playerState.playbackOptions.isShuffling}"),
              Text("RepeatMode: ${playerState.playbackOptions.repeatMode}"),
              Text("Image URI: ${playerState.track.imageUri.raw}"),
              Text(
                  "Is episode? ${playerState.track.isEpisode}. Is podcast?: ${playerState.track.isPodcast}"),
            ],
          );
        } else {
          return Center(
            child: Text("Not connected"),
          );
        }
      },
    );
  }

  Widget PlayerContextWidget() {
    return StreamBuilder<PlayerContext>(
      stream: SpotifySdk.subscribePlayerContext(),
      initialData: PlayerContext("", "", "", ""),
      builder: (BuildContext context, AsyncSnapshot<PlayerContext> snapshot) {
        if (snapshot.data != null && snapshot.data.uri != "") {
          var playerContext = snapshot.data;
          return Column(
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text("Title: ${playerContext.title}"),
              Text("Subtitle: ${playerContext.subtitle}"),
              Text("Type: ${playerContext.type}"),
              Text("Uri: ${playerContext.uri}"),
            ],
          );
        } else {
          return Center(
            child: Text("Not connected"),
          );
        }
      },
    );
  }

  Widget SpotifyImageWidget() {
    return FutureBuilder(
        future: SpotifySdk.getImage(
          imageUri: ImageUri(
              'spotify:image:ab67616d0000b2736b4f6358fbf795b568e7952d'),
          dimension: ImageDimension.large,
        ),
        builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
          if (snapshot.hasData) {
            return Image.memory(snapshot.data);
          } else if (snapshot.hasError) {
            setStatus(snapshot.error.toString());
            return SizedBox(
              width: ImageDimension.large.value.toDouble(),
              height: ImageDimension.large.value.toDouble(),
              child: Center(child: Text('Error getting image')),
            );
          } else {
            return SizedBox(
              width: ImageDimension.large.value.toDouble(),
              height: ImageDimension.large.value.toDouble(),
              child: Center(child: Text('Getting image...')),
            );
          }
        });
  }

  Future<void> connectToSpotifyRemote() async {
    try {
      setState(() {
        _loading = true;
      });
      var result = await SpotifySdk.connectToSpotifyRemote(
          clientId: DotEnv().env['CLIENT_ID'],
          redirectUrl: DotEnv().env['REDIRECT_URL']);
      setStatus(result
          ? "connect to spotify successful"
          : "conntect to spotify failed");
      setState(() {
        _loading = false;
      });
    } on PlatformException catch (e) {
      setState(() {
        _loading = false;
      });
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setState(() {
        _loading = false;
      });
      setStatus("not implemented");
    }
  }

  Future<void> getAuthenticationToken() async {
    try {
      var authenticationToken = await SpotifySdk.getAuthenticationToken(
          clientId: "", redirectUrl: "");
      setStatus("Got a token: $authenticationToken");
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future getPlayerState() async {
    try {
      return await SpotifySdk.getPlayerState();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future getCrossfadeState() async {
    try {
      var crossfadeStateValue = await SpotifySdk.getCrossFadeState();
      setState(() {
        crossfadeState = crossfadeStateValue;
      });
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> queue() async {
    try {
      await SpotifySdk.queue(
          spotifyUri: "spotify:track:58kNJana4w5BIjlZE2wq5m");
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> toggleRepeat() async {
    try {
      await SpotifySdk.toggleRepeat();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> toggleShuffle() async {
    try {
      await SpotifySdk.toggleShuffle();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> play() async {
    try {
      await SpotifySdk.play(spotifyUri: "spotify:track:58kNJana4w5BIjlZE2wq5m");
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> pause() async {
    try {
      await SpotifySdk.pause();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> resume() async {
    try {
      await SpotifySdk.resume();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> skipNext() async {
    try {
      await SpotifySdk.skipNext();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> skipPrevious() async {
    try {
      await SpotifySdk.skipPrevious();
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> seekTo() async {
    try {
      await SpotifySdk.seekTo(positionedMilliseconds: 20000);
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> seekToRelative() async {
    try {
      await SpotifySdk.seekToRelativePosition(relativeMilliseconds: 20000);
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  Future<void> addToLibrary() async {
    try {
      await SpotifySdk.addToLibrary(
          spotifyUri: "spotify:track:58kNJana4w5BIjlZE2wq5m");
    } on PlatformException catch (e) {
      setStatus(e.code, message: e.message);
    } on MissingPluginException {
      setStatus("not implemented");
    }
  }

  void setStatus(String code, {String message = ""}) {
    var text = message.isEmpty ? "" : " : $message";
    _logger.d("$code$text");
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  spotify_sdk: ^0.3.4

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:spotify_sdk/spotify_sdk.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
77
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
86
Learn more about scoring.

We analyzed this package on May 26, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.1
  • pana: 0.13.8-dev
  • Flutter: 1.17.0

Health suggestions

Format lib/models/connection_status.dart.

Run flutter format to format lib/models/connection_status.dart.

Format lib/spotify_sdk.dart.

Run flutter format to format lib/spotify_sdk.dart.

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (logger).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.7.0 <3.0.0
flutter 0.0.0
json_annotation ^3.0.0 3.0.1
logger ^0.8.0 0.8.3 0.9.1
Transitive dependencies
charcode 1.1.3
collection 1.14.12
io 0.3.4
meta 1.1.8
path 1.7.0
sky_engine 0.0.99
source_span 1.7.0
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
build_runner ^1.0.0
flutter_test
json_serializable ^3.2.0
pedantic ^1.8.0+1