Skip to content

Commit 4a42098

Browse files
authored
Merge pull request #265 from adamint/dev
bump kotlin version and add SpotifyContinuation for JVM
2 parents 68038d9 + 0b91b46 commit 4a42098

File tree

4 files changed

+208
-6
lines changed

4 files changed

+208
-6
lines changed

README.md

+58-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ supporting Kotlin/JS, Kotlin/Android, Kotlin/JVM, and Kotlin/Native
3131
* [API options](#api-options)
3232
+ [Using the API](#using-the-api)
3333
* [Platform-specific wrappers and information](#platform-specific-wrappers-and-information)
34+
+ [Java](#java)
35+
+ [Android authentication](#android-authentication)
3436
+ [JavaScript: Spotify Web Playback SDK wrapper](#js-spotify-web-playback-sdk-wrapper)
3537
* [Tips](#tips)
3638
+ [Building the API](#building-the-api)
@@ -54,10 +56,10 @@ repositories {
5456
implementation("com.adamratzman:spotify-api-kotlin-core:VERSION")
5557
```
5658

59+
5760
### JS
5861
Please see the [JS Spotify Web Playback SDK wrapper](#js-spotify-web-playback-sdk-wrapper) to learn how to use Spotify's web playback SDK in a browser application.
5962

60-
6163
### Android
6264
**Note**: For information on how to integrate implicit/PKCE authentication, Spotify app remote, and Spotify broadcast notifications into
6365
your application, please see the [Android README](README_ANDROID.md).
@@ -383,6 +385,61 @@ APIs available only in `SpotifyClientApi` and `SpotifyImplicitGrantApi` instance
383385

384386
## Platform-specific wrappers and information
385387

388+
### Java
389+
Unfortunately, coroutines don't play very nicely with Java code. Fortunately, however, we provide a wrapper around Kotlin's
390+
`Continuation` class that allows you to directly implement `onSuccess` and `onFailure` handlers on API methods.
391+
392+
Please see below for an example:
393+
394+
```java
395+
import com.adamratzman.spotify.SpotifyApiBuilderKt;
396+
import com.adamratzman.spotify.SpotifyAppApi;
397+
import com.adamratzman.spotify.javainterop.SpotifyContinuation;
398+
import com.adamratzman.spotify.models.Album;
399+
import org.jetbrains.annotations.NotNull;
400+
401+
import java.util.concurrent.ExecutionException;
402+
403+
public class SpotifyTestApp {
404+
static SpotifyAppApi api;
405+
406+
public static void main(String[] args) throws ExecutionException, InterruptedException {
407+
var id = "spotify-client-id";
408+
var secret = "spotify-client-secret";
409+
SpotifyApiBuilderKt.spotifyAppApi(id, secret).build(true, new SpotifyContinuation<>() {
410+
@Override
411+
public void onSuccess(SpotifyAppApi spotifyAppApi) {
412+
api = spotifyAppApi;
413+
runAlbumSearch();
414+
}
415+
416+
@Override
417+
public void onFailure(@NotNull Throwable throwable) {
418+
throwable.printStackTrace();
419+
}
420+
});
421+
422+
Thread.sleep(1000000);
423+
}
424+
425+
public static void runAlbumSearch() {
426+
api.getAlbums().getAlbum("spotify:album:0b23AHutIA1BOW0u1dZ6wM", null, new SpotifyContinuation<>() {
427+
@Override
428+
public void onSuccess(Album album) {
429+
System.out.println("Album name is: " + album.getName() + ". Exiting now..");
430+
431+
System.exit(0);
432+
}
433+
434+
@Override
435+
public void onFailure(@NotNull Throwable throwable) {
436+
throwable.printStackTrace();
437+
}
438+
});
439+
}
440+
}
441+
```
442+
386443
### Android authentication
387444
For information on how to integrate implicit/PKCE authentication, Spotify app remote, and Spotify broadcast notifications into
388445
your application, please see the [Android README](README_ANDROID.md).

build.gradle.kts

+9-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
signing
88
id("io.codearte.nexus-staging") version "0.22.0"
99
id("com.android.library")
10-
kotlin("multiplatform") version "1.4.30"
10+
kotlin("multiplatform") version "1.4.31"
1111
kotlin("plugin.serialization") version "1.4.30"
1212
id("com.diffplug.spotless") version "5.9.0"
1313
id("com.moowork.node") version "1.3.1"
@@ -27,7 +27,7 @@ buildscript {
2727
}
2828
dependencies {
2929
classpath("com.android.tools.build:gradle:3.5.4")
30-
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30")
30+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.31")
3131
}
3232
}
3333

@@ -199,7 +199,12 @@ kotlin {
199199
}
200200
}
201201

202+
val commonJvmLikeMain by creating {
203+
dependsOn(commonMain)
204+
}
205+
202206
val jvmMain by getting {
207+
dependsOn(commonJvmLikeMain)
203208
repositories {
204209
mavenCentral()
205210
jcenter()
@@ -236,6 +241,8 @@ kotlin {
236241
}
237242

238243
val androidMain by getting {
244+
dependsOn(commonJvmLikeMain)
245+
239246
repositories {
240247
mavenCentral()
241248
jcenter()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
@file:JvmName("SpotifyContinuation")
2+
3+
package com.adamratzman.spotify.javainterop
4+
5+
import kotlin.coroutines.Continuation
6+
import kotlin.coroutines.CoroutineContext
7+
import kotlin.coroutines.EmptyCoroutineContext
8+
9+
/**
10+
* A [Continuation] wrapper to allow you to directly implement [onSuccess] and [onFailure], when exceptions are hidden
11+
* on JVM via traditional continuations. **Please use this class as a callback anytime you are using Java code with this library.**
12+
*
13+
*/
14+
abstract class SpotifyContinuation<in T> : Continuation<T> {
15+
/**
16+
* Invoke a function with the callback [value]
17+
*
18+
* @param value The value retrieved from the Spotify API.
19+
*/
20+
abstract fun onSuccess(value: T)
21+
22+
/**
23+
* Handle exceptions during this API call.
24+
*
25+
* @param exception The exception that was thrown during the call.
26+
*/
27+
abstract fun onFailure(exception: Throwable)
28+
29+
override fun resumeWith(result: Result<T>) {
30+
result.fold(::onSuccess, ::onFailure)
31+
}
32+
33+
override val context: CoroutineContext = EmptyCoroutineContext
34+
}

src/commonMain/kotlin/com.adamratzman.spotify/Builder.kt renamed to src/commonMain/kotlin/com.adamratzman.spotify/SpotifyApiBuilder.kt

+107-3
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,29 @@ public fun getSpotifyPkceCodeChallenge(codeVerifier: String): String {
114114
public fun spotifyImplicitGrantApi(
115115
clientId: String?,
116116
token: Token,
117-
block: SpotifyApiOptions.() -> Unit = { }
117+
): SpotifyImplicitGrantApi = SpotifyImplicitGrantApi(
118+
clientId,
119+
token,
120+
SpotifyApiOptions()
121+
)
122+
123+
124+
/**
125+
* Instantiate a new [SpotifyImplicitGrantApi] using a Spotify [clientId], and [token] retrieved from the implicit
126+
* grant flow.
127+
*
128+
* Use case: I have a token obtained after implicit grant authorization.
129+
*
130+
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
131+
* @param token Token created from the hash response in the implicit grant callback
132+
* @param block Block to set API options
133+
*
134+
* @return [SpotifyImplicitGrantApi] that can immediately begin making calls
135+
*/
136+
public fun spotifyImplicitGrantApi(
137+
clientId: String?,
138+
token: Token,
139+
block: SpotifyApiOptions.() -> Unit
118140
): SpotifyImplicitGrantApi = SpotifyImplicitGrantApi(
119141
clientId,
120142
token,
@@ -135,6 +157,28 @@ public fun spotifyImplicitGrantApi(
135157
|| ||
136158
*/
137159

160+
/**
161+
* Instantiate a new [SpotifyAppApiBuilder] using a Spotify [clientId] and [clientSecret].
162+
*
163+
* Use case: I am using the client credentials flow.
164+
* I only need access to public Spotify API endpoints, might have an existing token,
165+
* and might want to deal with advanced configuration.
166+
*
167+
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
168+
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
169+
*
170+
* @return Configurable [SpotifyAppApiBuilder] that, when built, creates a new [SpotifyAppApi]
171+
*/
172+
public fun spotifyAppApi(
173+
clientId: String,
174+
clientSecret: String
175+
): SpotifyAppApiBuilder = SpotifyAppApiBuilder().apply {
176+
credentials {
177+
this.clientId = clientId
178+
this.clientSecret = clientSecret
179+
}
180+
}
181+
138182
/**
139183
* Instantiate a new [SpotifyAppApiBuilder] using a Spotify [clientId] and [clientSecret], with the ability to configure
140184
* the api settings by providing a builder initialization [block]
@@ -251,6 +295,35 @@ public fun spotifyAppApi(block: SpotifyAppApiBuilder.() -> Unit): SpotifyAppApiB
251295
|| ||
252296
*/
253297

298+
/**
299+
* Instantiate a new [SpotifyClientApiBuilder] using a Spotify [clientId], [clientSecret], and [redirectUri].
300+
*
301+
* **Note**: If trying to build [SpotifyClientApi], you **must** provide client authorization in the [SpotifyClientApiBuilder.authorization]
302+
* block
303+
*
304+
* Use case: I am using the client authorization flow.
305+
* I want access to both public and client Spotify API endpoints, and I want
306+
* to configure authorization and other settings myself.
307+
*
308+
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
309+
* @param clientSecret Spotify [client secret](https://developer.spotify.com/documentation/general/guides/app-settings/)
310+
* @param redirectUri Spotify [redirect uri](https://developer.spotify.com/documentation/general/guides/app-settings/)
311+
*
312+
* @return Configurable [SpotifyClientApiBuilder] that, when built, creates a new [SpotifyClientApi]
313+
*/
314+
public fun spotifyClientApi(
315+
clientId: String,
316+
clientSecret: String,
317+
redirectUri: String
318+
): SpotifyClientApiBuilder = SpotifyClientApiBuilder().apply {
319+
credentials {
320+
this.clientId = clientId
321+
this.clientSecret = clientSecret
322+
this.redirectUri = redirectUri
323+
}
324+
}
325+
326+
254327
/**
255328
* Instantiate a new [SpotifyClientApiBuilder] using a Spotify [clientId], [clientSecret], and [redirectUri], with the ability to configure
256329
* the api settings by providing a builder initialization [block]
@@ -273,7 +346,7 @@ public fun spotifyClientApi(
273346
clientId: String,
274347
clientSecret: String,
275348
redirectUri: String,
276-
block: SpotifyClientApiBuilder.() -> Unit = {}
349+
block: SpotifyClientApiBuilder.() -> Unit
277350
): SpotifyClientApiBuilder = SpotifyClientApiBuilder().apply(block).apply {
278351
credentials {
279352
this.clientId = clientId
@@ -411,6 +484,37 @@ public fun spotifyClientPkceApi(
411484
usesPkceAuth = true
412485
}
413486

487+
/**
488+
* Instantiate a new [SpotifyClientApiBuilder]. This is for **PKCE authorization**.
489+
*
490+
* Use case: I am using the PKCE client authorization flow.
491+
*
492+
* @param clientId Spotify [client id](https://developer.spotify.com/documentation/general/guides/app-settings/)
493+
* @param redirectUri Spotify [redirect uri](https://developer.spotify.com/documentation/general/guides/app-settings/)
494+
* @param pkceCodeVerifier The code verifier generated that the client authenticated with (using its code challenge)
495+
* @param authorizationCode Only available when building [SpotifyClientApi]. Spotify auth code
496+
*
497+
* @return Configurable [SpotifyClientApiBuilder] that, when built, creates a new [SpotifyClientApi]
498+
*/
499+
public fun spotifyClientPkceApi(
500+
clientId: String?,
501+
redirectUri: String?,
502+
authorizationCode: String,
503+
pkceCodeVerifier: String
504+
): SpotifyClientApiBuilder = SpotifyClientApiBuilder().apply {
505+
credentials {
506+
this.clientId = clientId
507+
this.redirectUri = redirectUri
508+
}
509+
510+
authorization {
511+
this.authorizationCode = authorizationCode
512+
this.pkceCodeVerifier = pkceCodeVerifier
513+
}
514+
515+
usesPkceAuth = true
516+
}
517+
414518
/**
415519
* Instantiate a new [SpotifyClientApiBuilder]. This is for **PKCE authorization**.
416520
*
@@ -429,7 +533,7 @@ public fun spotifyClientPkceApi(
429533
redirectUri: String?,
430534
authorizationCode: String,
431535
pkceCodeVerifier: String,
432-
block: SpotifyApiOptions.() -> Unit = {}
536+
block: SpotifyApiOptions.() -> Unit
433537
): SpotifyClientApiBuilder = SpotifyClientApiBuilder().apply {
434538
credentials {
435539
this.clientId = clientId

0 commit comments

Comments
 (0)