Spotify Web API wrapper for Kotlin, Java, JS, and Native

Overview

Kotlin Spotify Web API

A Kotlin implementation of the Spotify Web API, supporting Kotlin/JS, Kotlin/Android, Kotlin/JVM, and Kotlin/Native (macOS, Windows, Linux).

This library has first-class support for Java and is a viable alternative for Java development. Please see the Java section for more details.

Use this library in Kotlin, Java, JavaScript, Swift, or native code! Because this library targets both iOS and Android, it can also be used in KMM (Kotlin Multiplatform Mobile) applications as a shared source.

Maven CEntral codebeat badge

Table of Contents

Library installing

Current version:

Maven Central

JVM, Android, JS, Native Desktop (macOS, Windows, Linux), iOS/tvOS

repositories {
    mavenCentral()
}

implementation("com.adamratzman:spotify-api-kotlin-core:VERSION")

JS

Please see the JS Spotify Web Playback SDK wrapper to learn how to use Spotify's web playback SDK in a browser application.

Android

Note: For information on how to integrate implicit/PKCE authentication, Spotify app remote, and Spotify broadcast notifications into your application, please see the Android README.

If you declare any release types not named debug or release, you may see "Could not resolve com.adamratzman:spotify-api-kotlin-android:VERSION". You need to do the following for each release type not named debug or release:

android {
    buildTypes {
        yourReleaseType1 {
            // ...
            matchingFallbacks = ['release', 'debug'] 
        }
        yourReleaseType2 {
            // ...
            matchingFallbacks = ['release', 'debug'] 
        }
	...
    }
}

To successfully build, you might need to exclude kotlin_module files from the packaging. To do this, inside the android/buildTypes/release closure, you would put:

packagingOptions {
	exclude 'META-INF/*.kotlin_module'
}

Documentation

The spotify-web-api-kotlin JavaDocs are hosted here.

Have a question?

If you have a question, you can:

  1. Create an issue
  2. Join our Discord server
  3. Contact me using Adam#9261 on Discord

Unsupported features on each platform:

Feature JVM Android JS Native (Mac/Windows/Linux)
Edit client playlist ✔️ ✔️ ✔️ Unsupported
Remove playlist tracks ✔️ ✔️ ✔️ Unsupported

Please feel free to open an issue/discussion on GitHub or Discord if you need access to one of these features or have an interest in implementing one, as direction can be provided.

Creating a new api instance

To decide which api you need (SpotifyAppApi, SpotifyClientApi, SpotifyImplicitGrantApi), you can refer to the sections below or the Spotify authorization guide. In general:

  • If you don't need client resources, use SpotifyAppApi
  • If you're using the api in a backend application, use SpotifyClientApi (with or without PKCE)
  • If you're using the api in Kotlin/JS browser, use SpotifyImplicitGrantApi
  • If you need access to client resources in an Android or other application, use SpotifyClientApi with PKCE

Note: You can use the online Spotify OAuth Token Generator tool to generate a client token for local testing.

SpotifyAppApi

This provides access only to public Spotify endpoints. Use this when you have a server-side application. Note that implicit grant authorization provides a higher api ratelimit, so consider using implicit grant if your application has significant usage.

By default, the SpotifyApi Token automatically regenerates when needed. This can be changed by overriding the automaticRefresh builder setting.

There are four exposed builders, depending on the level of control you need over api creation. Please see the spotifyAppApi builder docs for a full list of available builders.

You will need:

  • Spotify application client id
  • Spotify application client secret

Example creation (default settings)

val api = spotifyAppApi("clientId", "clientSecret").build() // create and build api
println(api.browse.getNewReleases()) // use it

Example creation, using an existing Token and setting automatic token refresh to false

val token = spotifyAppApi(spotifyClientId, spotifyClientSecret).build().token
val api = spotifyAppApi(
    "clientId",
    "clientSecret",
    token,
    SpotifyApiOptionsBuilder(
        automaticRefresh = false
    )
)
println(api.browse.getNewReleases()) // use it

SpotifyClientApi

The SpotifyClientApi is a superset of SpotifyApi; thus, nothing changes if you want to access public data. This library does not provide a method to retrieve the code from your callback url; instead, you must implement that with a web server. Automatic Token refresh is available only when building with an authorization code or a Token object. Otherwise, it will expire Token.expiresIn seconds after creation.

Make sure your application has requested the proper Scopes in order to ensure proper function of this library. The api option requiredScopes allows you to verify that a client has actually authorized with the scopes you are expecting.

You will need:

  • Spotify application client id
  • Spotify application client secret (if not using PKCE)
  • Spotify application redirect uri
  • To choose which client authorization method (PKCE or non-PKCE) to use

PKCE

Use the PKCE builders and helper methods if you are using the Spotify client authorization PKCE flow. Building via PKCE returns a SpotifyClientApi which has modified refresh logic.

Use cases:

  1. You are using this library in an application (likely Android), or do not want to expose the client secret.

To learn more about the PKCE flow, please read the Spotify authorization guide. Some highlights about the flow are:

  • It is refreshable, but each refresh token can only be used once. This library handles token refresh automatically by default
  • It does not require a client secret; instead, a set redirect uri and a random code verifier are used to verify the authenticity of the authorization.
  • A code verifier is required. The code verifier is "a cryptographically random string between 43 and 128 characters in length. It can contain letters, digits, underscores, periods, hyphens, or tildes."
  • A code challenge is required. "In order to generate the code challenge, your app should hash the code verifier using the SHA256 algorithm. Then, base64url encode the hash that you generated."
  • When creating a pkce api instance, the code verifier is passed in by you and compared to the code challenge used to authorize the user.

This library contains helpful methods that can be used to simplify the PKCE authorization process. This includes getSpotifyPkceCodeChallenge, which SHA256 hashes and base64url encodes the code challenge, and getSpotifyPkceAuthorizationUrl, which allows you to generate an easy authorization url for PKCE flow.

Please see the spotifyClientPkceApi builder docs for a full list of available builders.

Takeaway: Use PKCE authorization flow in applications where you cannot secure the client secret.

To get a PKCE authorization url, to which you can redirect a user, you can use the getSpotifyPkceAuthorizationUrl top-level method. An example is shown below, requesting 4 different scopes.

val codeVerifier = "thisisaveryrandomalphanumericcodeverifierandisgreaterthan43characters"
val codeChallenge = getSpotifyPkceCodeChallenge(codeVerifier) // helper method
val url: String = getSpotifyPkceAuthorizationUrl(
    SpotifyScope.PLAYLIST_READ_PRIVATE,
    SpotifyScope.PLAYLIST_MODIFY_PRIVATE,
    SpotifyScope.USER_FOLLOW_READ,
    SpotifyScope.USER_LIBRARY_MODIFY,
    clientId = "clientId",
    redirectUri = "your-redirect-uri",
    codeChallenge = codeChallenge
)

There is also an optional parameter state, which helps you verify the authorization.

Note: If you want automatic token refresh, you need to pass in your application client id and redirect uri when using the spotifyClientPkceApi.

Example: A user has authorized your application. You now have the authorization code obtained after the user was redirected back to your application. You want to create a new SpotifyClientApi.
val codeVerifier = "thisisaveryrandomalphanumericcodeverifierandisgreaterthan43characters"
val code: String = ...
val api = spotifyClientPkceApi(
    "clientId", // optional. include for token refresh
    "your-redirect-uri", // optional. include for token refresh
    code,
    codeVerifier, // the same code verifier you used to generate the code challenge
    SpotifyApiOptionsBuilder(
        retryWhenRateLimited = false
    )
).build()
println(api.library.getSavedTracks().take(10).filterNotNull().map { it.track.name })

Non-PKCE (backend applications, requires client secret)

To get a non-PKCE authorization url, to which you can redirect a user, you can use the getSpotifyAuthorizationUrl top-level method. An example is shown below, requesting 4 different scopes.

val url: String = getSpotifyAuthorizationUrl(
    SpotifyScope.PLAYLIST_READ_PRIVATE,
    SpotifyScope.PLAYLIST_MODIFY_PRIVATE,
    SpotifyScope.USER_FOLLOW_READ,
    SpotifyScope.USER_LIBRARY_MODIFY,
    clientId = "clientId",
    redirectUri = "your-redirect-uri",
    state = "your-special-state" // optional
)

There are also several optional parameters, allowing you to set whether the authorization url is meant for implicit grant flow, the state, and whether a re-authorization dialog should be shown to users.

There are several exposed builders, depending on the level of control you need over api creation. Please see the spotifyClientApi builder docs for a full list of available builders.

Example: You've redirected the user back to your web server and have an authorization code (code).

In this example, automatic token refresh is turned on by default.

val authCode = ""
val api = spotifyClientApi(
    "clientId",
    "clientSecret",
    "your-redirect-uri",
    authCode
).build() // create and build api
println(api.personalization.getTopTracks(limit = 5).items.map { it.name }) // print user top tracks
Example: You've saved a user's token from previous authorization and need to create an api instance.

In this case, if you provide a client id to the builder, automatic token refresh will also be turned on.

val token: Token = ... // your existing token
val api = spotifyClientApi(
    "clientId",
    "clientSecret",
    "your-redirect-uri",
    token,
    SpotifyApiOptionsBuilder(
        onTokenRefresh = { 
            println("Token refreshed at ${System.currentTimeMillis()}")
        }
    )
).build()
println(api.personalization.getTopTracks(limit = 5).items.map { it.name })

SpotifyImplicitGrantApi

Use the SpotifyImplicitGrantApi if you are using the Spotify implicit grant flow. SpotifyImplicitGrantApi is a superset of SpotifyClientApi. Unlike the other builders, the spotifyImplicitGrantApi builder method directly returns a SpotifyImplicitGrantApi instead of an api builder.

Use cases:

  1. You are using the Kotlin/JS target for this library.
  2. Your frontend Javascript passes the token received through the implicit grant flow to your backend, where it is then used to create an api instance.

To learn more about the implicit grant flow, please read the Spotify authorization guide. Some highlights about the flow are:

  • It is non-refreshable
  • It is client-side
  • It does not require a client secret

Please see the spotifyImplicitGrantApi builder docs for a full list of available builders.

The Kotlin/JS target contains the parseSpotifyCallbackHashToToken method, which will parse the hash for the current url into a Token object, with which you can then instantiate the api.

Takeaway: There are two ways to use implicit grant flow, browser-side only and browser and server. This library provides easy access for both.

Example
val token: Token = ...
val api = spotifyImplicitGrantApi(
    null,
    null,
    token
) // create api. there is no need to build it 
println(api.personalization.getTopArtists(limit = 1)[0].name) // use it

SpotifyApiBuilder Block & setting API options

There are three pluggable blocks in each api's corresponding builder

  1. credentials lets you set the client id, client secret, and redirect uri
  2. authorization lets you set the type of api authorization you are using. Acceptable types include: an authorization code, a Token object, a Token's access code string, and an optional refresh token string
  3. options lets you configure API options to your own specific needs

API options

This library does not attempt to be prescriptivist. All API options are located in SpotifyApiOptions and their default values can be overridden; however, use caution in doing so, as most of the default values either allow for significant performance or feature enhancements to the API instance.

  • useCache: Set whether to cache requests. Default: true
  • cacheLimit: The maximum amount of cached requests allowed at one time. Null means no limit. Default: 200
  • automaticRefresh: Enable or disable automatic refresh of the Spotify access token when it expires. Default: true
  • retryWhenRateLimited: Set whether to block the current thread and wait until the API can retry the request. Default: true
  • enableLogger: Set whether to enable to the exception logger. Default: true
  • testTokenValidity: After API creation, test whether the token is valid by performing a lightweight request. Default: false
  • defaultLimit: The default amount of objects to retrieve in one request. Default: 50
  • json: The Json serializer/deserializer instance.
  • allowBulkRequests: Allow splitting too-large requests into smaller, allowable api requests. Default: true
  • requestTimeoutMillis: The maximum time, in milliseconds, before terminating an http request. Default: 100000ms
  • refreshTokenProducer: Provide if you want to use your own logic when refreshing a Spotify token.
  • requiredScopes: Scopes that your application requires to function (only applicable to SpotifyClientApi and SpotifyImplicitGrantApi). This verifies that the token your user authorized with actually contains the scopes your application needs to function.

Notes:

  • Unless you have a good reason otherwise, useCache should be true
  • cacheLimit is per Endpoint, not per API. Don't be surprised if you end up with over 200 items in your cache with the default settings.
  • automaticRefresh is disabled when client secret is not provided, or if tokenString is provided in SpotifyClientApi
  • allowBulkRequests for example, lets you query 80 artists in one wrapper call by splitting it into 50 artists + 30 artists
  • refreshTokenProducer is useful when you want to re-authorize with the Spotify Auth SDK or elsewhere

Using the API

APIs available in all SpotifyApi instances, including SpotifyClientApi and SpotifyImplicitGrantApi:

  • SearchApi (searching items)
  • AlbumApi (get information about albums)
  • BrowseApi (browse new releases, featured playlists, categories, and recommendations)
  • ArtistApi (get information about artists)
  • PlaylistApi (get information about playlists)
  • UserApi (get public information about users on Spotify)
  • TrackApi (get information about tracks)
  • FollowingApi (check whether users follow playlists)

APIs available only in SpotifyClientApi and SpotifyImplicitGrantApi instances:

  • ClientSearchApi (all the methods in SearchApi, and searching shows and episodes)
  • EpisodeApi (get information about episodes)
  • ShowApi (get information about shows)
  • ClientPlaylistApi (all the methods in PlaylistApi, and get and manage user playlists)
  • ClientProfileApi (all the methods in UserApi, and get the user profile, depending on scopes)
  • ClientFollowingApi (all the methods in FollowingApi, and get and manage following of playlists, artists, and users)
  • ClientPersonalizationApi (get user top tracks and artists)
  • ClientLibraryApi (get and manage saved tracks and albums)
  • ClientPlayerApi (view and control Spotify playback)

Platform-specific wrappers and information

Java

This library has first-class support for Java! You have two choices when using this library: async-only with Kotlin suspend functions (using SpotifyContinuation), or by using the SpotifyRestAction class. Using SpotifyRestActions are recommended.

What is the SpotifyRestAction class and how do I use it?

Abstracting requests into a SpotifyRestAction class allows for a lot of flexibility in sending and receiving requests. This class includes options for asynchronous and blocking execution in all endpoints. However, due to this, you must call one of the provided methods in order for the call to execute! The SpotifyRestAction provides many methods and fields for use, including blocking and asynchronous ones. For example,

  • hasRun() tells you whether the rest action has been started
  • hasCompleted() tells you whether this rest action has been fully executed and completed
  • complete() blocks the current thread and returns the result
  • suspendComplete(context: CoroutineContext = Dispatchers.Default) switches to given context, invokes the supplier, and synchronously retrieves the result.
  • suspendQueue() suspends the coroutine, invokes the supplier asynchronously, and resumes with the result
  • queue() executes and immediately returns
  • queue(consumer: (T) -> Unit) executes the provided callback as soon as the request is asynchronously evaluated
  • queueAfter(quantity: Int, timeUnit: TimeUnit, consumer: (T) -> Unit) executes the provided callback after the provided time. As long as supplier execution is less than the provided time, this will likely be accurate within a few milliseconds.
  • asFuture() transforms the supplier into a CompletableFuture (only JVM)

Here's an example of how easy it is to use spotify-web-api-kotlin with a SpotifyRestAction:

var api = SpotifyApiBuilderKt.spotifyAppApi(Const.clientId, Const.clientSecret).buildRestAction(true).complete();
var album = api.getAlbums().getAlbumRestAction("spotify:album:0b23AHutIA1BOW0u1dZ6wM", null).complete();
System.out.println("Album name is: " + album.getName());

Integrating with Kotlin suspend functions via Java Continuations

Unfortunately, coroutines don't play very nicely with Java code. Fortunately, however, we provide a wrapper around Kotlin's Continuation class that allows you to directly implement onSuccess and onFailure handlers on API methods.

Please see below for an example:

() { @Override public void onSuccess(SpotifyAppApi spotifyAppApi) { api = spotifyAppApi; runAlbumSearch(); } @Override public void onFailure(@NotNull Throwable throwable) { throwable.printStackTrace(); } }); Thread.sleep(1000000); } public static void runAlbumSearch() { api.getAlbums().getAlbum("spotify:album:0b23AHutIA1BOW0u1dZ6wM", null, new SpotifyContinuation<>() { @Override public void onSuccess(Album album) { System.out.println("Album name is: " + album.getName() + ". Exiting now.."); System.exit(0); } @Override public void onFailure(@NotNull Throwable throwable) { throwable.printStackTrace(); } }); } }">
import com.adamratzman.spotify.SpotifyApiBuilderKt;
import com.adamratzman.spotify.SpotifyAppApi;
import com.adamratzman.spotify.javainterop.SpotifyContinuation;
import com.adamratzman.spotify.models.Album;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.ExecutionException;

public class SpotifyTestApp {
    static SpotifyAppApi api;

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        var id = "spotify-client-id";
        var secret = "spotify-client-secret";
        SpotifyApiBuilderKt.spotifyAppApi(id, secret).build(true, new SpotifyContinuation<>() {
            @Override
            public void onSuccess(SpotifyAppApi spotifyAppApi) {
                api = spotifyAppApi;
                runAlbumSearch();
            }

            @Override
            public void onFailure(@NotNull Throwable throwable) {
                throwable.printStackTrace();
            }
        });

        Thread.sleep(1000000);
    }

    public static void runAlbumSearch() {
        api.getAlbums().getAlbum("spotify:album:0b23AHutIA1BOW0u1dZ6wM", null, new SpotifyContinuation<>() {
            @Override
            public void onSuccess(Album album) {
                System.out.println("Album name is: " + album.getName() + ". Exiting now..");

                System.exit(0);
            }

            @Override
            public void onFailure(@NotNull Throwable throwable) {
                throwable.printStackTrace();
            }
        });
    }
}

Android authentication

For information on how to integrate implicit/PKCE authentication, Spotify app remote, and Spotify broadcast notifications into your application, please see the Android README.

JS Spotify Web Playback SDK wrapper

spotify-web-api-kotlin provides a wrapper around Spotify's Web Playback SDK for playing music via Spotify in the browser on your own site.

To do this, you need to create a Player instance and then use the associated methods to register listeners, play, and get current context.

Please see an example of how to do this here. An example project, spotify-web-api-browser-example, demonstrates how to create a frontend JS Kotlin application with Spotify integration and that will play music in the browser.

Notes:

  1. You must include the Spotify player JS script by including
  2. You must define a window.onSpotifyWebPlaybackSDKReady function immediately afterwards - this should load your main application bundle. Otherwise, you will get errors. An example is below:
.... ">
<html>
<head>
    ...
    <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous">script>

    <script>
        jQuery.loadScript = function (url, callback) {
            jQuery.ajax({
                url: url,
                dataType: 'script',
                success: callback,
                async: true
            });
        }
    script>

    <script src="https://sdk.scdn.co/spotify-player.js">script>
    <script>
        window.onSpotifyWebPlaybackSDKReady = () => {
            $.loadScript("main.bundle.js")
        }
    script>
head>
<body>
....
body> html>

Tips

Building the API

The easiest way to build the API is using .build() after a builder

runBlocking {
    val api = spotifyAppApi(clientId, clientSecret).build()
}

Notes

Re-authentication

If you are using an authorization flow or token that does not support automatic token refresh, SpotifyException.ReAuthenticationNeededException will be thrown. You should put your requests, if creating an application, behind a try/catch block to re-authenticate users if this exception is thrown.

LinkedResults, PagingObjects, and Cursor-based Paging Objects

Spotify provides these three object models in order to simplify our lives as developers. So let's see what we can do with them!

PagingObjects

PagingObjects are a container for the requested objects (items), but also include important information useful in future calls. It contains the request's limit and offset, along with (sometimes) a link to the next and last page of items and the total number of items returned.

If a link to the next or previous page is provided, we can use the getNext and getPrevious methods to retrieve the respective PagingObjects

Cursor-Based Paging Objects

A cursor-based paging object is a PagingObject with a cursor added on that can be used as a key to find the next page of items. The value in the cursor, after, describes after what object to begin the query.

Just like with PagingObjects, you can get the next page of items with getNext. However, there is no provided implementation of after in this library. You will need to do it yourself, if necessary.

LinkedResults

Some endpoints, like PlaylistAPI.getPlaylistTracks, return a LinkedResult, which is a simple wrapper around the list of objects. With this, we have access to its Spotify API url (with href), and we provide simple methods to parse that url.

Generic Request

For obvious reasons, in most cases, making asynchronous requests via queue or queueAfter is preferred. However, the synchronous format is also shown.

val api = spotifyAppApi(
        System.getenv("SPOTIFY_CLIENT_ID"),
        System.getenv("SPOTIFY_CLIENT_SECRET")
).build()

// print out the names of the twenty most similar songs to the search
println(api.search.searchTrack("Début de la Suite").joinToString { it.name })

// simple, right? what about if we want to print out the featured playlists message from the "Overview" tab?
println(api.browse.getFeaturedPlaylists().message)

// easy! let's try something a little harder
// let's find out Bénabar's Spotify ID, find his top tracks, and print them out
val benabarId = api.search.searchArtist("Bénabar")[0].id
// this works; a redundant way would be: api.artists.getArtist("spotify:artist:6xoAWsIOZxJVPpo7Qvqaqv").id
println(api.artists.getArtistTopTracks(benabarId).joinToString { it.name })

Track Relinking

Spotify keeps many instances of most tracks on their servers, available in different markets. As such, if we use endpoints that return tracks, we do not know if these tracks are playable in our market. That's where track relinking comes in.

To relink in a specified market, we must supply a market parameter for endpoints where available. In both Track and SimpleTrack objects in an endpoint response, there is a nullable field called linked_from. If the track is unable to be played in the specified market and there is an alternative that is playable, this will be populated with the href, uri, and, most importantly, the id of the track.

You can then use this track in SpotifyClientApi endpoints such as playing or saving the track, knowing that it will be playable in your market!

Contributing

See CONTRIBUTING.md

Comments
  • Issue during Playlist URI instantiation when there's a fourth part present

    Issue during Playlist URI instantiation when there's a fourth part present

    Describe the bug

    During the deserializing phase of a PlayHistoryContext, in case there's an URI that contains 4 parts like spotify:playlist:4SIcFp6g2h85IX7jgIsuRL:recommended, it ends up breaking the safe initialization of a PlaylistUri and throws a SpotifyUriException.

    Code mentions:

    In the SpotifyUris.init block, there's a function that removes the 4th part and then during SpotifyUris:110 the safe initialization comparison made between the processed uri (without :recommended) and the original one (with :recommended) returns false, thus, the exception.

    Dependency def: com.adamratzman:spotify-api-kotlin-core:3.5.03

    bug 
    opened by duberton 13
  • ClassCastException on simple search

    ClassCastException on simple search

    Hi, I tried a simple track search according to the documentation and example files, but I'm getting a ClassCast Exception when calling the search method. What is wrong with my code? I profiled the network an the REST Calls / Response seems to be OK

    val result = api.search.searchTrack("quand je serai grand", market = Market.FR).complete();
    println(result.items)
    

    throws this:

    java.lang.ClassCastException: org.json.JSONArray cannot be cast to java.lang.Iterable
         at com.adamratzman.spotify.endpoints.pub.search.SearchAPI$searchTrack$1.get(SearchAPI.kt:96)
         at com.adamratzman.spotify.endpoints.pub.search.SearchAPI$searchTrack$1.get(SearchAPI.kt:10)
         at com.adamratzman.spotify.utils.SpotifyRestAction.complete(SpotifyRestAction.kt:11)
    
    opened by dhelleberg 12
  • Allow for creation of a SpotifyClientAPI using a refresh token

    Allow for creation of a SpotifyClientAPI using a refresh token

    Hi there! I'd like to be able to write my own code to fetch a refresh token from Spotify, then save that refresh token somewhere persistent, then have my app create a SpotifyClientAPI using that refresh token. Is there a way to do that?

    I know that if I provide an auth code, this library will fetch its own refresh token. But if I rely on that, I need to go through the authentication process more or less every time I restart my app (unless my last restart was so recent that the old auth code is still valid). It'd be better if I could provide this library a refresh token manually.

    (I'm new to Kotlin, by the way, so it's possible that there's a totally-obvious way to do this. I didn't see it in the main README, and I wasn't able to figure it out by looking around in SpotifyAPI.kt.)

    Thanks for making this library!

    opened by jdpopkin 9
  • getAlbumTracks 400 Error when called often

    getAlbumTracks 400 Error when called often

    Describe the bug when I call api.albums.getAlbumTracks() for a lot of albums (e.g. 40 or 80), I get Received Status Code 400. Error cause: malformed request sent for some requests

    To Reproduce

    val api = spotifyAppApi()
    val tracks = albumIds.flatMap { albumId ->
        api.albums.getAlbumTracks(albumId).getAllItemsNotNull()
    }
    

    create albumIds list from this file: albumIds.txt

    Desktop:

    • OS: Windows
    • Kotlin/JVM
    • Project with Java 17
    • Kotlin 1.6
    opened by michaeldomanek 8
  • Get recently played not working when there's no data

    Get recently played not working when there's no data

    Whenever i perform a call to spotifyClientApi.player.getRecentlyPlayed and it returns no data, it fails to deserialize the Json to CursorBasedPagingObject at the exension function Serializationutils.kt:String.toCursorBasedPagingObject:191:

    Content from the response:

    {
      "items" : [ ],
      "next" : null,
      "cursors" : null,
      "limit" : 50,
      "href" : "https://api.spotify.com/v1/me/player/recently-played?after=1611402105401&limit=50"
    }
    

    I guess that's due to 'cursors', it isn't nullable.

    Dependency def: com.adamratzman:spotify-api-kotlin-core:3.4.03

    bug 
    opened by duberton 8
  • Why are there so many execution methods for SpotifyRestAction?

    Why are there so many execution methods for SpotifyRestAction?

    First off, great work on the library.

    My question is regarding this design choice:

    What is the SpotifyRestAction class?

    Abstracting requests into a SpotifyRestAction class allows for a lot of flexibility in sending and receiving requests. This class includes options for asynchronous and blocking execution in all endpoints. However, due to this, you must call one of the provided methods in order for the call to execute! The SpotifyRestAction provides many methods and fields for use, including blocking and asynchronous ones. For example,

    • hasRun() tells you whether the rest action has been started
    • hasCompleted() tells you whether this rest action has been fully executed and completed
    • complete() blocks the current thread and returns the result
    • suspendComplete(context: CoroutineContext = Dispatchers.Default) switches to given context, invokes the supplier, and synchronously retrieves the result.
    • suspendQueue() suspends the coroutine, invokes the supplier asynchronously, and resumes with the result
    • queue() executes and immediately returns
    • queue(consumer: (T) -> Unit) executes the provided callback as soon as the request is asynchronously evaluated
    • queueAfter(quantity: Int, timeUnit: TimeUnit, consumer: (T) -> Unit) executes the provided callback after the provided time. As long as supplier execution is less than the provided time, this will likely be accurate within a few milliseconds.
    • asFuture() transforms the supplier into a CompletableFuture (only JVM)

    My least favorite aspect of this library is how verbose this whole set of execution methods is and I don't see any benefit to it. No other library in the Kotlin ecosystem follows this pattern to my knowledge.

    Couldn't the actions simply be suspend functions? (suspendComplete())

    All the other execution methods could be easily attained by using the corresponding coroutine methods/builders; we can already make a Future from a coroutine with an adapter, we can transform async actions into sync ones by runBlocking { }, we can queue them with actors or flows and we can detect when they complete by returning from either of these Async/Sync calls. At the core of all these operations is something async, so why not just make them suspend funs and rely on the language to bridge out of async-land?

    I think ideally all these actions would be simple suspend funs, but If they need to be classes for some reason, the classes could simply provide invoke overrides with something like:

    suspend operator fun invoke(parameters: P): Result<R>
    

    to make them work like suspend funs in the typical usecase. The other execution methods could simply be provided as extension functions on a base SpotifyRestAction that are optionally in scope if they are really needed for some reason.

    A good example of what I am suggesting here can be found in Google's kotlin reference Android app IO-Schedule here with the UseCase class.

    opened by Volatile-Memory 8
  • getCurrentlyPlayingRestAction unable to parse song in users

    getCurrentlyPlayingRestAction unable to parse song in users "Liked Songs"

    Describe the bug When trying to call getCurrentlyPlayingRestAction().complete() an "com.adamratzman.spotify.SpotifyException$ParseException" exception is thrown. Stack trace says:

    (Illegal Spotify ID/URI: 'spotify:user:XXXXXXXXX:collection' isn't convertible to 'playlist' or 'album' or 'artist' or 'show' id) at com.adamratzman.spotify.models.serialization.SerializationUtilsKt.parseJson(SerializationUtils.kt:33) at com.adamratzman.spotify.models.serialization.SerializationUtilsKt.toObject(SerializationUtils.kt:41) at com.adamratzman.spotify.endpoints.client.ClientPlayerApi.getCurrentlyPlaying(ClientPlayerApi.kt:203) at com.adamratzman.spotify.endpoints.client.ClientPlayerApi$getCurrentlyPlaying$1.invokeSuspend(ClientPlayerApi.kt).......

    To Reproduce Steps to reproduce the behavior: Call getCurrentlyPlayingRestAction().complete() while user is listening to song in his "Liked Songs" playlist.

    Expected behavior A clear and concise description of what you expected to happen.

    Screenshots If applicable, add screenshots to help explain your problem.

    Desktop (please complete the following information):

    • OS: [Windows]
    opened by tobiasarndt 7
  • Fix refresh token

    Fix refresh token

    • Deleted line which caused crash
    • #27 is tackled but it creates an other problem. The next API request will probably be earlier than the refresh request. The refresh is non-blocking ?!
    opened by molikuner 6
  • Android invalid grant, code verifier was incorrect

    Android invalid grant, code verifier was incorrect

    Description

    I'm trying to use the PKCE flow on Android and I'm getting the following error:

    responseCode=400, body={"error":"invalid_grant","error_description":"code_verifier was incorrect"}
    

    Apparently, the reason is that the AbstractSpotifyPkceLoginActivity:pkceCodeVerifier attribute gets recreated after redirecting from the opened URL from the device browser.

    To reproduce

    1. Open the target app
    2. Click in a "Login" button to launch the Activity extending the AbstractSpotifyPkceLoginActivity
    3. On creation the pkceCodeVerifier is Gxo3to4KqcxPSN9udvacLxrq3xWmyrohY5yIMhHrFhqhMKmopFefepux9nZ1M2cXwbCUHaDnndSb2ZUtwq4POGmADh2RMlyeu
    4. User is redirected to the browser to accept the app
    5. User accepts the permission and gets redirected to the app
    6. When user is redirected back to the app, a new instance of the class extending AbstractSpotifyPkceLoginActivity is created. Now, the pkceCodeVerifier code is eBId4eXjoWrRrY6xdJCTDHe9ETcorXqLEWU4rKbdyTIqzDUNNskTHcS5awd2dH4UPDcyWPHodnbi1teNdo8U68dYDsNqBDGRv
    7. Function handleSpotifyAuthenticationResponse is called with the response. No errors
    8. When trying to create a new spotify api client, the pkceCodeVerifier is different than the original request on step 3, now using the one created on step 6
    9. Error on this line is thrown. Full stacktrace:
    Click to expand
    com.adamratzman.spotify.SpotifyException$AuthenticationException: Invalid credentials provided in the login process (clientId=..., clientSecret=null, authCode=....)
    2022-03-14 15:53:50.165 W/System.err:     at com.adamratzman.spotify.SpotifyClientApiBuilder.build(SpotifyApiBuilder.kt:962)
    2022-03-14 15:53:50.165 W/System.err:     at com.adamratzman.spotify.SpotifyClientApiBuilder$build$1.invokeSuspend(Unknown Source:15)
    2022-03-14 15:53:50.165 W/System.err:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    2022-03-14 15:53:50.165 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:191)
    2022-03-14 15:53:50.165 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:147)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:15)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:93)
    2022-03-14 15:53:50.166 W/System.err:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:191)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:147)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:15)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:93)
    2022-03-14 15:53:50.166 W/System.err:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:191)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:147)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:15)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:93)
    2022-03-14 15:53:50.166 W/System.err:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:191)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:147)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:15)
    2022-03-14 15:53:50.166 W/System.err:     at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:93)
    2022-03-14 15:53:50.166 W/System.err:     at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:279)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source:1)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
    2022-03-14 15:53:50.166 W/System.err:     at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source:1)
    2022-03-14 15:53:50.166 W/System.err:     at com.adamratzman.spotify.auth.pkce.AbstractSpotifyPkceLoginActivity.onResume(AbstractSpotifyPkceLoginActivity.kt:106)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1571)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.Activity.performResume(Activity.java:8141)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:4626)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4668)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:52)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2251)
    2022-03-14 15:53:50.166 W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
    2022-03-14 15:53:50.166 W/System.err:     at android.os.Looper.loop(Looper.java:233)
    2022-03-14 15:53:50.166 W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:8068)
    2022-03-14 15:53:50.166 W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
    2022-03-14 15:53:50.166 W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
    2022-03-14 15:53:50.166 W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)
    2022-03-14 15:53:50.167 W/System.err: Caused by: com.adamratzman.spotify.SpotifyException$ParseException: Unable to parse {"error":"invalid_grant","error_description":"code_verifier was incorrect"} (Fields [access_token, token_type, expires_in] are required for type with serial name 'com.adamratzman.spotify.models.Token', but they were missing)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.serialization.SerializationUtilsKt.parseJson(SerializationUtils.kt:33)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.serialization.SerializationUtilsKt.toObject(SerializationUtils.kt:41)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.SpotifyClientApiBuilder.build(SpotifyApiBuilder.kt:952)
    2022-03-14 15:53:50.167 W/System.err: 	... 44 more
    2022-03-14 15:53:50.167 W/System.err: Caused by: kotlinx.serialization.MissingFieldException: Fields [access_token, token_type, expires_in] are required for type with serial name 'com.adamratzman.spotify.models.Token', but they were missing
    2022-03-14 15:53:50.167 W/System.err:     at kotlinx.serialization.internal.PluginExceptionsKt.throwMissingFieldException(PluginExceptions.kt:20)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.Token.<init>(Authentication.kt:25)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.Token$$serializer.deserialize(Authentication.kt:25)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.Token$$serializer.deserialize(Authentication.kt:25)
    2022-03-14 15:53:50.167 W/System.err:     at kotlinx.serialization.json.internal.PolymorphicKt.decodeSerializableValuePolymorphic(Polymorphic.kt:59)
    2022-03-14 15:53:50.167 W/System.err:     at kotlinx.serialization.json.internal.StreamingJsonDecoder.decodeSerializableValue(StreamingJsonDecoder.kt:36)
    2022-03-14 15:53:50.167 W/System.err:     at kotlinx.serialization.json.Json.decodeFromString(Json.kt:100)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.serialization.SerializationUtilsKt$toObject$1.invoke(SerializationUtils.kt:42)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.serialization.SerializationUtilsKt$toObject$1.invoke(SerializationUtils.kt:41)
    2022-03-14 15:53:50.167 W/System.err:     at com.adamratzman.spotify.models.serialization.SerializationUtilsKt.parseJson(SerializationUtils.kt:31)
    2022-03-14 15:53:50.167 W/System.err: 	... 46 more
    

    Workaround

    Overriding the variable pkceCodeVerifier on my Activity, setting to a fixed string works as expected:

    class SpotifyPkceLoginActivityImpl : AbstractSpotifyPkceLoginActivity() {
        ...
        override val pkceCodeVerifier: String
            get() = "testingaoirao9reiw9riw4e9ifdksa9fjsk9ujw3495iuwe9ids"
    
        override fun onSuccess(api: SpotifyClientApi) {
            //Success is called with the api
            setResult(Activity.RESULT_OK)
        }
    
        override fun onFailure(exception: Exception) {
            setResult(Activity.RESULT_CANCELED)
        }
    }
    

    Expected behavior For what I understood, the pkceCodeVerifier attribute must be preserved/saved after user gets back to the app.

    Project configuration

    //build.gradle.kts
    implementation("com.adamratzman:spotify-api-kotlin-android:3.8.5")
    
    <application>
        <activity
            android:name=".presentation.SpotifyPkceLoginActivityImpl"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/Theme.AppCompat.Light">
            <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:host="..." //redacted
                    android:scheme="..." //redacted />
            </intent-filter>
        </activity>
    </application>
    

    Smartphone:

    • Device: Oneplus 6
    • OS: Android 11

    Additional info

    • I've tried to change the activity launch mode to singleInstance and I got the same error. Also tried to remove the launchMode at all, and still the same error
    opened by rsicarelli 5
  • BrowseApi.search fails on 3.5.04, Android with ProGuard enabled

    BrowseApi.search fails on 3.5.04, Android with ProGuard enabled

    Relevant conversation, as reported by @Nielssg:

    Niels02/03/2021
    I seem to find an issue when using the search endpoint in the most recent version of the library (Android). The weird part is that it only seems to happen when using Android with Proguard, without Proguard it works just fine. In seems to be isolated to the Search api, as I can use the rest of the endpoints without problems. I was wondering if anything has changed that could cause this issue, or if I'm doing something wrong?
    
    note: I think it's specifically isolated to spotifyApi.search.search(). It doesn't matter which searchType you enter, or if you use searchAllTypes, it will fail.
    
    Adam02/03/2021
    @Niels could you try to use getArtistAlbums
    my suspicion is that it's either the fact that id is internal in SearchType or because it's a nested enum
    using getArtistAlbums could rule out the second
    
    NielsYesterday at 12:05 PM
    getArtist, getArtistAlbums and their top tracks does not seem to crash
    
    NielsYesterday at 12:15 PM
    I can also confirm, using the separate calls (e.g. .searchARtist, .searchAlbum, .searchPlaylist, .searchTrack) seems fine, it is probably isolated to solely the search.search function
    
    AdamYesterday at 1:59 PM
    huh, thanks for the info. does still seem like it’s related to enums. I’ll open an issue and look into it
    
    NielsYesterday at 3:25 PM
    Yeah weird issue, wish I could find out what the problem is, any changes to the search endpoint which could cause it? I previously had v3.2.14, which I believe did not have the problem
    
    AdamYesterday at 10:03 PM
    Hmm, ok.. there was this issue with search before, but I thought it was fixed
    which version is this?
    
    NielsToday at 2:46 AM
    3.2.14 does not have the issue, the latest 3.5.04 does have it, though I think it might have slipped in inbetwee n
    
    AdamToday at 9:31 AM
    Sorry for asking, and it’s totally fine if you’re not able to, but would it be possible to test a 3.3.x and 3.4.x version?
    I’m guessing it’s a dependency added between 3.2 and 3.5
    (probably klock)
    
    bug 
    opened by adamint 5
  • switch to a faster JSON parser

    switch to a faster JSON parser

    I dislike Gson's performance and many of the applications that can implement this library will already be using Jackson. We actually have a few good options, mainly

    1. Use Jackson
    2. Manually map each JSONObject to its respective class
    enhancement 
    opened by adamint 5
  • Spotify follow/unfollow endpoints are timing out

    Spotify follow/unfollow endpoints are timing out

    This is not a library problem, as the console is also affected. See: https://developer.spotify.com/console/delete-following/?type=artist&ids=7eCmccnRwPmRnWPw61x6jM

    Related tests are to be temporarily disabled

    bug wontfix 
    opened by adamint 0
  • Create wrapper around spotify-auth-api and spotify-app-remote

    Create wrapper around spotify-auth-api and spotify-app-remote

    Issue: API instantiation on android is still too confusing and DIY. spotify-web-api-kotlin has gradually expanded its targets, but its JVM beginnings are still evident in the lack of documentation around mobile authentication.

    Solution:

    1. Two dependencies, to spotify-auth-api and app-remote, will be added for the Android target. iOS target is TBD.
    2. Wrapper around authentication APIs will be provided in the style of existing builders (3.6.0)
    3. Wrapper around SpotifyAppRemote APIs will be provided in the style of a new API inside SpotifyClientApi (AppRemoteWrapperApi) (3.6.1)
    4. Succinct documentation around how to use both wrappers will be provided (3.6.0-3.6.1)
    5. Example application for Android will be developed (3.6.2)
    6. Samples will be developed and fully cover the library (3.6.3)

    Target for 3.6.0 is March 14, 2021.

    spotify-auth wrapper is now complete and needs testing.

    enhancement 
    opened by adamint 1
Releases(v3.8.8)
  • v3.8.8(Sep 19, 2022)

    Publishes tvosArm64 and iosArm64 versions

    What's Changed

    • update dependencies and save scopes to credential store by @adamint in https://github.com/adamint/spotify-web-api-kotlin/pull/314
    • Fixed issue where endpoint would infinitely retry for a nullable resource (#312) by @Nielssg in https://github.com/adamint/spotify-web-api-kotlin/pull/313

    Full Changelog: https://github.com/adamint/spotify-web-api-kotlin/compare/3.8.6...v3.8.8

    Source code(tar.gz)
    Source code(zip)
  • 3.8.6(Apr 2, 2022)

    • Fixes #305
    • Fixes #306

    What's Changed

    • add getNullable method for 204-available methods to use, update dependencies by @adamint in https://github.com/adamint/spotify-web-api-kotlin/pull/307
    • only set pkce verifier on android pkce activity launch by @adamint in https://github.com/adamint/spotify-web-api-kotlin/pull/308

    Full Changelog: https://github.com/adamint/spotify-web-api-kotlin/compare/3.8.5...3.8.6

    Source code(tar.gz)
    Source code(zip)
  • 3.8.5(Dec 28, 2021)

    What's Changed

    • Fixed #302: PlayHistory should contain a Track object rather than a SimpleTrack, due to a change in the Spotify API by @Nielssg in https://github.com/adamint/spotify-web-api-kotlin/pull/303
    • update playhistory to take Track by @adamint in https://github.com/adamint/spotify-web-api-kotlin/pull/304

    Full Changelog: https://github.com/adamint/spotify-web-api-kotlin/compare/3.8.4...3.8.5

    Source code(tar.gz)
    Source code(zip)
  • 3.8.4(Nov 22, 2021)

  • 3.8.3(Sep 1, 2021)

    • Add Market.FROM_TOKEN (#296)
    • Add Player.activeElement to web playback sdk
    • Update PlayerInit interface to make getOAuthToken a var
    • Make AuthenticationError.error_description nullable
    Source code(tar.gz)
    Source code(zip)
  • 3.8.2(Sep 1, 2021)

  • 3.8.0(May 25, 2021)

  • 3.7.0(Apr 9, 2021)

    • (Re)adds SpotifyRestAction as an alternative to suspend methods. All endpoints have a corresponding rest action method - e.g. getTracks + getTracksRestAction
    • Rename com.adamratzman.spotify.endpoints.public to com.adamratzman.endpoints.pub
    • Updates kotlin dependency to 1.4.32
    Source code(tar.gz)
    Source code(zip)
  • 3.6.03(Mar 31, 2021)

    • Resolved #271 by adding tvOS (Arm/X64) and iOS (Arm/X64) targets
    • Replace klock dependency with kotlinx.datetime
    • Update kotlinx.serialization version to 1.1.0
    Source code(tar.gz)
    Source code(zip)
  • 3.6.02(Mar 29, 2021)

  • 3.6.01(Mar 10, 2021)

  • 3.6.0-rc.2(Feb 24, 2021)

  • 3.6.0-rc.1(Feb 23, 2021)

  • 3.5.05(Feb 7, 2021)

    Fixes #252 and fixes #253 Fixes #254 and fixes #255 Reduces platform-specific logic to one file with 5 methods([Desktop]PlatformUtils.kt) instead of the package it was before. tl;dr: now very easy to add new targets Adds in experimental Spotify Web Playback SDK (JS) wrapper Remove SpotifyLogger Fixes #248 Replaces retryIfInternalServerError with retryOnInternalServerErrorTimes in SpotifyApiOptions Fixes #258 by adding definitions for the api provides a link to an example project that showcases a Kotlin frontend application with Spotify web playback sdk integration

    Source code(tar.gz)
    Source code(zip)
  • 3.5.04(Jan 27, 2021)

    • Makes PagingObject, CursorBasedPagingObject inherit from List
    • Exposes ShowApi and EpisodeApi to SpotifyAppApi
    • Cleans up ClientPlayerApi endpoints
    • Adds new ContextUri
    • Implements take for PagingObjects (instead of Kotlin Iterable.take)
    Source code(tar.gz)
    Source code(zip)
  • 3.5.03(Jan 23, 2021)

  • 3.5.02(Jan 18, 2021)

  • 3.5.01(Jan 17, 2021)

    • Remove kotlinx-datetime dependency from targets and replace in Native targets with klock.
    • Fix issue where responses with 401 and 429 status codes throw BadRequestException even if a refresh/retry should be performed instead.
    Source code(tar.gz)
    Source code(zip)
  • 3.5.0(Jan 16, 2021)

    • Introduces three new targets - mingwX64, macosX64, linuxX64 #198
    • Change cache from per-endpoint to global
    • Add documentation for public actual functions in each of the targets
    • Fixes #237 by recursively checking NeedsAPI objects and instantiating api field
    • Refactors URIs and simplifies (de)serialization utilities
    • Add option in SpotifyApiOptions to retry (once) on internal server error (5xx)
    • Native logic is abstracted into desktopMain to allow for more native targets to be easily added
    • Rename spotify-api-kotlin (JVM target) to spotify-api-kotlin-jvm
    Source code(tar.gz)
    Source code(zip)
  • v3.4.03(Jan 5, 2021)

  • v2.1.0(Jan 5, 2019)

Owner
Adam Ratzman
Microsoft. Full-stack Kotlin, React, Java, JS, C# engineer.
Adam Ratzman
Netflix inspired OTT Home Screen, Contains implementation in Reactjs, Kotlin React Wrapper, Jetpack Compose Web

Netflix-Clone-React Practising React by building Netflix Clone Requirements TMDB api key : Add TMDB API key to AppApi.kt Learning Resourcce Build Netf

Chetan Gupta 61 Nov 13, 2022
Yaspeller-kt - Asynchronous Yandex.Speller API wrapper for Kotlin/JVM.

yaspeller-kt Asynchronous Yandex.Speller API wrapper for Kotlin/JVM. Installation repositories { maven { url 'https://jitpack.io' }

Mikhail Koshkin 6 Jun 27, 2022
A Kotlin Multiplatform Project using TMDB Api. Currently supports Android,iOS,Desktop and web platforms

A Kotlin Multiplatform Project using TMDB Api(https://www.themoviedb.org/). Currently this project is implemented in following platforms Andr

Jackson E J 11 Nov 10, 2022
Swift-friendly api generator for Kotlin/Native frameworks

MOKO KSwift KSwift it's gradle plugin for generation Swift-friendly API for Kotlin/Native framework. Kotlin sealed interface/class to Swift enum Kotli

IceRock Development 226 Dec 28, 2022
⏰ A powerful and simple-to-use guilded wrapper made in Kotlin.

⏰ guilded-kt [WIP] A powerful yet simple-to-use guilded wrapper made entirely in Kotlin with supporting multiplatform. Take a look at an example of th

Gabriel 13 Jul 30, 2022
🎲 A powerful and simple-to-use guilded wrapper made in Kotlin.

?? deck [WIP] Deck is a powerful yet simple-to-use guilded wrapper made entirely in Kotlin with support to multiplatform. Implementating In case you'r

Gabriel 13 Jul 30, 2022
A Kotlin DSL wrapper around the mikepenz/MaterialDrawer library.

MaterialDrawerKt Create navigation drawers in your Activities and Fragments without having to write any XML, in pure Kotlin code, with access to all t

Márton Braun 517 Nov 19, 2022
🔓 Kotlin version of the popular google/easypermissions wrapper library to simplify basic system permissions logic on Android M or higher.

EasyPermissions-ktx Kotlin version of the popular googlesample/easypermissions wrapper library to simplify basic system permissions logic on Android M

Madalin Valceleanu 326 Dec 23, 2022
A simple Kotlin wrapper around Anvil.

AnvilKotlin A simple Kotlin wrapper around Anvil. The only purpose of this library is to provide type safety to Anvil through Kotlin. Nothing more, no

Andre Artus 15 Oct 3, 2022
Android AsyncTask wrapper library, written in Kotlin

KillerTask This is a Kotlin Android library to create async background tasks. Inspired by TinyTask, but more beautiful and easy to use for Kotlin Andr

Inaka 26 Oct 3, 2022
A lightweight Kotlin friendly wrapper around Couchbase lite for Android.

CouchBaseKtx ?? Work In-Progress ?? A lightweight Kotlin friendly wrapper around Couchbase-lite for Android Read up a little bit of documentation abou

Jaya Surya Thotapalli 5 Feb 15, 2022
A Kotlin coroutines wrapper for IndexedDB.

Kotlin IndexedDB A wrapper around IndexedDB which allows for access from Kotlin/JS code using suspend blocks and linear, non-callback based control fl

JUUL Labs 10 Dec 11, 2022
SavedStateFlow - A Kotlin StateFlow wrapper around SavedStateHandle

SavedStateFlow SavedStateFlow is a Kotlin StateFlow wrapper around SavedStateHan

Andrew Steinmetz 13 Aug 4, 2022
Kotlin and Java API for generating .swift source files.

SwiftPoet SwiftPoet is a Kotlin and Java API for generating .swift source files. Source file generation can be useful when doing things such as annota

Outfox 232 Jan 2, 2023
Run Kotlin/JS libraries in Kotlin/JVM and Kotlin/Native programs

Zipline This library streamlines using Kotlin/JS libraries from Kotlin/JVM and Kotlin/Native programs. It makes it possible to do continuous deploymen

Cash App 1.5k Dec 30, 2022
Wrapper of FusedLocationProviderClient for Android to support modern usage like LiveData and Flow

FancyLocationProvider Wrapper of FusedLocationProviderClient for Android to support modern usage like LiveData or Flow. Install Add Jitpack repository

Jintin 66 Aug 15, 2022
A Kotlin Multiplatform and Compose template that allows you to easily set up your project targeting: Android, Desktop, and Web

A Kotlin Multiplatform and Compose template that allows you to easily set up your project targeting: Android, Desktop, and Web

Carlos Mota 3 Oct 27, 2021
Unsplash application for Android, Desktop and Web. Built using Kotlin Multiplatform and Compose

Unsplash Unsplash application for Android, Desktop and Web. Built using Kotlin Multiplatform and Compose with ❤️ ?? Presentation Set up the environmen

Carlos Mota 15 Nov 11, 2022
Permissionmanager is a small wrapper for handling permission requests.

Permissionmanager Permissionmanager is a small wrapper for handling permission requests. Installation Add jitpack to your repositories in Project buil

Thomas Cirksena 11 Nov 17, 2020