Spotikit
Flutter plugin for integrating Spotify on Android using both the Spotify App Remote SDK (realtime playback control/state) and the Spotify Web API (rich metadata, search, etc.).
Version 1.0.0 - First stable release! Android only for now. iOS support planned.
Highlights (TL;DR)
- Auth (Authorization Code + refresh)
- Play / pause / resume / next / previous / seek / skip +/- seconds
- Realtime playback state stream (track, artist, progress, paused, image)
- One‑shot search & play first result
- Full track metadata via Web API
- Centralized logging & auto token refresh
Platform Support
| Platform | Status | Notes |
|---|---|---|
| Android | ✅ | Uses App Remote SDK + Web API |
| iOS | ⏳ | Planned |
| Web/Desktop | ❌ | Not targeted |
Prerequisites
- Spotify Developer account: https://developer.spotify.com/dashboard
- Create an app → copy Client ID (and Client Secret if using backend‑less flow here).
- Add redirect URI (e.g.
your.app://callback). - Use the SAME redirect URI in your AndroidManifest intent filter if you customize it.
Scopes requested by default (override if you want fewer):
user-read-playback-state user-modify-playback-state user-read-currently-playing app-remote-control streaming playlist-read-private playlist-read-collaborative playlist-modify-public playlist-modify-private user-library-modify user-library-read user-top-read user-read-playback-position user-read-recently-played user-follow-read user-follow-modify user-read-email user-read-private
Trim to the minimum you actually need.
Installation & REQUIRED Android Init
Quick steps:
- Add to
pubspec.yaml:dependencies: spotikit: ^1.0.0 - Fetch packages:
flutter pub get - IMPORTANT (one-time per clone / after cleaning android dir): run the init script so the Spotify AARs are downloaded & Gradle includes are inserted at the top of
android/settings.gradle:
By default, this usesdart run spotikit:android_initspotify-sdk://authas the redirect URI. To use a custom redirect URI:
If you skip this, Gradle will fail because the requireddart run spotikit:android_init --fallback-url=your.app://callbackspotify-app-remoteandspotify-authmodules won't exist. - (Optional) If Gradle metadata gets messy or you want to re-download AARs, clean with:
dart run spotikit:android_clean && dart run spotikit:android_init - Ensure
minSdkVersion >= 21.
Intent filter (only if you changed the default scheme/host):
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="your.app" android:host="callback" />
</intent-filter>
Quick Start
import 'package:spotikit/spotikit.dart';
import 'package:spotikit/models/auth_state.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final spotikit = Spotikit.instance;
spotikit.configureLogging(loggingEnabled: true);
await spotikit.initialize(
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
redirectUri: 'your.app://callback',
);
spotikit.onAuthStateChanged.listen((state) async {
if (state is AuthSuccess) {
await spotikit.connectToSpotify();
await spotikit.playUri(spotifyUri: 'spotify:track:4cOdK2wGLETKBW3PvgPWqT');
}
});
await spotikit.authenticateSpotify();
spotikit.onPlaybackStateChanged.listen((state) {
print('Now playing: ${state.name} by ${state.artist} ${(state.progress * 100).toStringAsFixed(1)}%');
});
}
Auth State Stream
final spotikit = Spotikit.instance;
spotikit.onAuthStateChanged.listen((state) {
switch (state) {
case AuthSuccess(:final accessToken):
print('Authenticated. Token: $accessToken');
case AuthFailure(:final error, :final message):
print('Auth failed: $error ${message ?? ''}');
case AuthCancelled():
print('User cancelled Spotify login');
}
});
Playback State Stream
final spotikit = Spotikit.instance;
final sub = spotikit.onPlaybackStateChanged.listen((state) {
print('Track: ${state.name} | Paused: ${state.isPaused} | Position: ${state.positionMs}/${state.durationMs}');
});
Fields: uri, name, artist, isPaused, positionMs, durationMs, imageUrl, helpers: progress, id.
Core Control APIs
| Action | Method |
|---|---|
| Play by URI | spotikit.playUri(spotifyUri: ...) |
| Pause / Resume | spotikit.pause() / spotikit.resume() |
| Next / Previous | spotikit.skipTrack() / spotikit.previousTrack() |
| Seek absolute | spotikit.seekTo(positionMs: ...) |
| Skip fwd/back seconds | spotikit.skipForward(seconds: ...) / spotikit.skipBackward(seconds: ...) |
| Playing (basic) | spotikit.getPlayingTrackInfo() |
| Full metadata | spotikit.getPlayingTrackFull() |
| Search & play first | spotikit.playSong(query: ...) |
| Is playing? | spotikit.isPlaying() |
| Disconnect | spotikit.disconnect() |
| Logout (clear tokens) | spotikit.logout() |
Example App
Located in example/ (shows auth → connect → playback + search + progress slider). Run:
cd example
flutter run
Add real credentials in example/lib/main.dart.
Token Handling
- Authorization Code flow
- Access + refresh cached (SharedPreferences)
- Automatic refresh when expired on demand
- Future: proactive refresh events
Security:
- Never commit secrets
- Prefer
--dart-definefor CI/builds - Consider backend proxy for token exchange in production
Error Handling
Native issues surface as PlatformException. Future: richer SpotikitException wrapper.
Roadmap
- iOS
- Shuffle / repeat
- Queue ops
- Volume, context metadata
- EventChannel optimization
- More Web API (playlists, library, user)
- Proactive token refresh
- Filtered track change stream + caching
Contributing
- Fork / branch
- Implement + update example/docs
flutter format .& fix analyzer warnings- PR with description + test notes
Dev Scripts (repeat: init is REQUIRED) ✅
dart run spotikit:android_init # REQUIRED after adding plugin / fresh clone / cleaning android
dart run spotikit:android_clean # Optional helper (then rerun android_init)
License
MIT © 2026 spotikit contributors
Attribution
Uses Spotify App Remote SDK & Web API. Not affiliated with or endorsed by Spotify.
Support
Issues / ideas: https://github.com/ArdaKoksall/spotikit/issues
Enjoy building with Spotikit! 🎧