Skip to content

Commit b77d806

Browse files
authored
Merge pull request #263 from adamint/dev
add spotify-auth wrapper, IR target for Kotlin/JS
2 parents 3fe0d78 + 1c9d0b4 commit b77d806

20 files changed

+1222
-26
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,11 @@ implementation("com.adamratzman:spotify-api-kotlin-core:VERSION")
5858
Please see
5959

6060
### Android
61-
**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:**
61+
**Note**: For information on how to integrate implicit/PKCE authentication, Spotify app remote, and Spotify broadcast notifications into
62+
your application, please see the [Android README](README_ANDROID.md).
63+
64+
65+
*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:*
6266
```
6367
android {
6468
buildTypes {
@@ -436,6 +440,11 @@ runBlocking {
436440
```
437441

438442
## Notes
443+
### Re-authentication
444+
If you are using an authorization flow or token that does not support automatic token refresh, `SpotifyException.ReAuthenticationNeededException`
445+
will be thrown. You should put your requests, if creating an application, behind a try/catch block to re-authenticate users if this
446+
exception is thrown.
447+
439448
### LinkedResults, PagingObjects, and Cursor-based Paging Objects
440449
Spotify provides these three object models in order to simplify our lives as developers. So let's see what we
441450
can do with them!

README_ANDROID.md

Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
# spotify-web-api-kotlin Android target extended features
2+
3+
Please also read the Android section of the spotify-web-api-kotlin readme, as it contains details about how to support
4+
non-debug/release release types.
5+
6+
spotify-web-api-kotlin contains wrappers around Spotify's `android-auth` and `android-sdk` (Spotify remote) libraries
7+
that make it easier to implement authentication and playback features, while only needing to learn how to use one
8+
library.
9+
10+
## Table of Contents
11+
12+
* [Sample application](#sample-application)
13+
* [Authentication](#authentication)
14+
+ [Credential store](#spotify-credential-store)
15+
+ [Authentication prerequisites](#authentication-prerequisites)
16+
+ [PKCE auth (refreshable client authentication)](#pkce-auth)
17+
+ [spotify-auth integration (implicit auth)](#implicit-auth)
18+
+ [Using SpotifyApi in your application](#using-spotifyapi-in-your-application)
19+
* [spotify-remote integration (WIP)](#spotify-remote-integration)
20+
* [Broadcast Notifications](#broadcast-notifications)
21+
+ [JVM, Android, JS, Native](#jvm-android-js)
22+
+ [Android information](#android)
23+
24+
## Sample application
25+
26+
There is a sample application demonstrating authentication, remote integration, and broadcast notifications. You may
27+
find it useful to scaffold parts of your application, or just to learn more about the Android features in this library.
28+
See https://github.com/Nielssg/Spotify-Api-Test-App
29+
30+
## Authentication
31+
32+
spotify-web-api-kotlin comes with two built-in authorization schemes. The library includes a full implementation of the
33+
PKCE authorization scheme, which allows you to refresh the token you obtain indefinitely*, as well as wrapping around
34+
the `spotify-auth` to provide a simple way to perform implicit grant authorization (non-refreshable tokens).
35+
36+
\* PKCE tokens are refreshable unless they are revoked. If they are revoked, requests will fail with error 400, and you
37+
should begin authorization flow again.
38+
39+
### Spotify Credential Store
40+
41+
By default, credentials are stored in the `SpotifyDefaultCredentialStore`, which under-the-hood creates and updates
42+
an `EncryptedSharedPreferences` instance.
43+
44+
#### Creating an instance of the credential store
45+
46+
```kotlin
47+
val credentialStore by lazy {
48+
SpotifyDefaultCredentialStore(
49+
clientId = "YOUR_SPOTIFY_CLIENT_ID",
50+
redirectUri = "YOUR_SPOTIFY_REDIRECT_URI",
51+
applicationContext = YOUR_APPLICATION.context
52+
)
53+
}
54+
```
55+
56+
It is recommended to maintain only one instance of the credential store.
57+
58+
#### Setting credentials
59+
60+
You can set credentials in several different ways. The first two are recommended, for simplicity.
61+
62+
1. You can pass an instance of `SpotifyApi` using `SpotifyDefaultCredentialStore.setSpotifyApi(api: GenericSpotifyApi)`.
63+
This will directly set the `token` property.
64+
2. You can set the `token` property using `SpotifyDefaultCredentialStore.token = YOUR_TOKEN`. This will set all three
65+
saved properties, mentioned below.
66+
3. You can set the `spotifyTokenExpiresAt`, `spotifyAccessToken`, and `spotifyRefreshToken` properties. Please note that
67+
all of them are used to create a `Token`, so failing to update any single property may result in unintended
68+
consequences.
69+
70+
Example:
71+
72+
```kotlin
73+
val credentialStore = (application as MyApplication).model.credentialStore
74+
credentialStore.setSpotifyApi(spotifyApi)
75+
```
76+
77+
#### Getting an instance of SpotifyApi from the credential store
78+
79+
Based on the type of authorization you used to authenticate the user, you will either call `getSpotifyImplicitGrantApi`
80+
or `getSpotifyClientPkceApi`. Both methods allow you to optionally pass parameters to configure the returned
81+
`SpotifyApi`.
82+
83+
#### Saving credentials somewhere other than the credential store (TBD)
84+
85+
Unfortunately, you are only able to store credentials in the credential store at this time if you decide to use the
86+
authentication features of this library. PRs are welcome to address this limitation.
87+
88+
### Authentication prerequisites
89+
90+
1. You
91+
must [register your application](https://developer.spotify.com/documentation/general/guides/app-settings/#register-your-app)
92+
on Spotify. You must specify at least one application redirect uri (such as myapp://myauthcallback) - you will need
93+
this later.
94+
2. Though this is not required, for security reasons you should follow the **Register Your App** part of the Spotify
95+
Android guide listed
96+
[here](https://developer.spotify.com/documentation/android/quick-start/) to generate a fingerprint for your app.
97+
98+
**Note**: Ensure that you are not using the same redirect uri for both PKCE/implicit authorization. If you need to use
99+
both, please register two distinct redirect uris.
100+
101+
### PKCE Auth
102+
103+
PKCE authorization lets you obtain a refreshable Spotify token. This means that you do not need to keep prompting your
104+
users to re-authenticate (or force them to wait a second for automatic login). Please read the "PKCE" section of
105+
the [README](README.md) if you'd like to learn more.
106+
107+
#### 1. Create a class implementing AbstractSpotifyPkceLoginActivity
108+
109+
You first need to create a class that extends AbstractSpotifyPkceLoginActivity that will be used for the actual user
110+
authorization.
111+
112+
Example:
113+
114+
```kotlin
115+
internal var pkceClassBackTo: Class<out Activity>? = null
116+
117+
class SpotifyPkceLoginActivityImpl : AbstractSpotifyPkceLoginActivity() {
118+
override val clientId = BuildConfig.SPOTIFY_CLIENT_ID
119+
override val redirectUri = BuildConfig.SPOTIFY_REDIRECT_URI_PKCE
120+
override val scopes = SpotifyScope.values().toList()
121+
122+
override fun onSuccess(api: SpotifyClientApi) {
123+
val model = (application as SpotifyPlaygroundApplication).model
124+
model.credentialStore.setSpotifyApi(api)
125+
val classBackTo = pkceClassBackTo ?: ActionHomeActivity::class.java
126+
pkceClassBackTo = null
127+
toast("Authentication via PKCE has completed. Launching ${classBackTo.simpleName}..")
128+
startActivity(Intent(this, classBackTo))
129+
}
130+
131+
override fun onFailure(exception: Exception) {
132+
exception.printStackTrace()
133+
pkceClassBackTo = null
134+
toast("Auth failed: ${exception.message}")
135+
}
136+
}
137+
```
138+
139+
#### 2. Add the following activity to your Android Manifest
140+
Note: the protocol of your redirect uri corresponds to the Android scheme, and the path corresponds to the
141+
host. Ex: for the redirect uri `myapp://authcallback`, the scheme is `myapp` and the host is `authcallback`.
142+
143+
```xml
144+
145+
<application>
146+
...
147+
<activity android:name="YOUR_CLASS_IMPLEMENTING_AbstractSpotifyPkceLoginActivity"
148+
android:launchMode="singleTop">
149+
<intent-filter>
150+
<action android:name="android.intent.action.VIEW"/>
151+
152+
<category android:name="android.intent.category.DEFAULT"/>
153+
<category android:name="android.intent.category.BROWSABLE"/>
154+
155+
<data android:scheme="YOUR_REDIRECT_SCHEME" android:host="YOUR_REDIRECT_HOST"/>
156+
</intent-filter>
157+
</activity>
158+
</application>
159+
```
160+
161+
#### 3. Begin Spotify authorization flow with Activity.startSpotifyClientPkceLoginActivity
162+
Now, you just need to begin the authorization flow by calling `Activity.startSpotifyClientPkceLoginActivity` in any
163+
activity in your application.
164+
165+
Example:
166+
```kotlin
167+
pkceClassBackTo = classBackTo // from the previous code sample, return to an activity after auth success
168+
startSpotifyClientPkceLoginActivity(YOUR_CLASS_IMPLEMENTING_PKCE_LOGIN_ACTIVITY::class.java)
169+
```
170+
171+
#### 4. ???
172+
173+
#### 5. Profit (more accurately, your onSuccess or onFailure methods will be called)
174+
175+
### Implicit auth
176+
Implicit grant authorization, provided by wrapping the `spotify-auth` library, returns a temporary, non-refreshable
177+
access token. Implementing this authorization method is very similar to PKCE authorization, as both follow the
178+
same general format.
179+
180+
#### 1. Create a class implementing AbstractSpotifyAppLoginActivity or AbstractSpotifyAppCompatImplicitLoginActivity.
181+
You first need to create a class that extends `AbstractSpotifyAppLoginActivity` or `AbstractSpotifyAppCompatImplicitLoginActivity`
182+
that will be used for the actual user authorization. The only difference between these two classes is that
183+
`AbstractSpotifyAppLoginActivity` extends from `Activity`, while `AbstractSpotifyAppCompatImplicitLoginActivity` extends
184+
from `AppCompatActivity`.
185+
186+
Example:
187+
188+
```kotlin
189+
class SpotifyImplicitLoginActivityImpl : AbstractSpotifyAppImplicitLoginActivity() {
190+
override val state: Int = 1337
191+
override val clientId: String = BuildConfig.SPOTIFY_CLIENT_ID
192+
override val redirectUri: String = BuildConfig.SPOTIFY_REDIRECT_URI_AUTH
193+
override val useDefaultRedirectHandler: Boolean = false
194+
override fun getRequestingScopes(): List<SpotifyScope> = SpotifyScope.values().toList()
195+
196+
override fun onSuccess(spotifyApi: SpotifyImplicitGrantApi) {
197+
val model = (application as SpotifyPlaygroundApplication).model
198+
model.credentialStore.setSpotifyApi(spotifyApi)
199+
toast("Authentication via spotify-auth has completed. Launching TrackViewActivity..")
200+
startActivity(Intent(this, ActionHomeActivity::class.java))
201+
}
202+
203+
override fun onFailure(errorMessage: String) {
204+
toast("Auth failed: $errorMessage")
205+
}
206+
}
207+
```
208+
209+
#### 2. Add the following activity to your Android Manifest
210+
Note: the protocol of your redirect uri corresponds to the Android scheme, and the path corresponds to the
211+
host. Ex: for the redirect uri `myapp://authcallback`, the scheme is `myapp` and the host is `authcallback`.
212+
213+
```xml
214+
215+
<application>
216+
...
217+
<activity
218+
android:name="YOUR_CLASS_IMPLEMENTING_AbstractSpotifyAppImplicitLoginActivity"
219+
android:theme="@android:style/Theme.Translucent.NoTitleBar">
220+
<intent-filter>
221+
<action android:name="android.intent.action.VIEW" />
222+
<category android:name="android.intent.category.DEFAULT" />
223+
<category android:name="android.intent.category.BROWSABLE" />
224+
<data android:scheme="YOUR_REDIRECT_SCHEME" android:host="YOUR_REDIRECT_HOST"/>
225+
</intent-filter>
226+
</activity>
227+
228+
<activity
229+
android:name="com.spotify.sdk.android.auth.LoginActivity"
230+
android:theme="@android:style/Theme.Translucent.NoTitleBar">
231+
232+
<intent-filter>
233+
<data android:scheme="YOUR_REDIRECT_SCHEME" android:host="YOUR_REDIRECT_HOST"/>
234+
</intent-filter>
235+
</activity>
236+
</application>
237+
```
238+
239+
#### 3. Begin Spotify authorization flow with Activity.startSpotifyImplicitLoginActivity
240+
Now, you just need to begin the authorization flow by calling `Activity.startSpotifyImplicitLoginActivity(spotifyLoginImplementationClass: Class<T>)` in any
241+
activity in your application.
242+
243+
Example:
244+
```kotlin
245+
SpotifyDefaultCredentialStore.activityBackOnImplicitAuth = classBackTo // use if you're using guardValidSpotifyImplicitApi, though this is not recommended
246+
startSpotifyImplicitLoginActivity(SpotifyImplicitLoginActivityImpl::class.java)
247+
```
248+
249+
#### 4. ???
250+
251+
#### 5. Profit (more accurately, your onSuccess or onFailure methods will be called)
252+
253+
254+
### Using SpotifyApi in your application
255+
Based on the type of authorization you used to authenticate the user, you will either call `SpotifyDefaultCredentialStore.getSpotifyImplicitGrantApi`
256+
or `SpotifyDefaultCredentialStore.getSpotifyClientPkceApi`. Both methods allow you to optionally pass parameters to configure the returned
257+
`SpotifyApi`.
258+
259+
You will want to write a guard to handle what happens when `SpotifyException.ReAuthenticationNeededException` is thrown.
260+
261+
A basic guard is `Activity.guardValidImplicitSpotifyApi`, which will launch the provided
262+
activity after a user authenticates successfully, if the implicit token has expired.
263+
264+
A more complex guard can be found in the [sample application](https://github.com/Nielssg/Spotify-Api-Test-App/blob/main/app/src/main/java/com/adamratzman/spotifyandroidexample/auth/VerifyLoggedInUtils.kt).
265+
266+
## Spotify Remote integration
267+
Spotify remote integration is still a WIP.
268+
269+
## Broadcast Notifications
270+
You can easily add support for handling Spotify app broadcast notifications by implementing
271+
the `AbstractSpotifyBroadcastReceiver` class and registering the receiver in a Fragment or
272+
Activity.
273+
274+
Supported broadcast types: queue changes, playback state changes, and metadata changes.
275+
276+
Note that "Device Broadcast Status" must be enabled in the Spotify app and the active Spotify device must be the Android
277+
device that your app is on to receive notifications.
278+
279+
This library provides a `registerSpotifyBroadcastReceiver` method that you can use to
280+
easily register your created broadcast receiver.
281+
282+
An example implementation of `AbstractSpotifyBroadcastReceiver` and use of `registerSpotifyBroadcastReceiver`
283+
are provided below. Please see the sample app for a complete implementation.
284+
285+
```kotlin
286+
class SpotifyBroadcastReceiver(val activity: ViewBroadcastsActivity) : AbstractSpotifyBroadcastReceiver() {
287+
override fun onMetadataChanged(data: SpotifyMetadataChangedData) {
288+
activity.broadcasts += data
289+
println("broadcast: ${data}")
290+
}
291+
292+
override fun onPlaybackStateChanged(data: SpotifyPlaybackStateChangedData) {
293+
activity.broadcasts += data
294+
println("broadcast: $data")
295+
}
296+
297+
override fun onQueueChanged(data: SpotifyQueueChangedData) {
298+
activity.broadcasts += data
299+
println("broadcast: $data")
300+
}
301+
}
302+
303+
class ViewBroadcastsActivity : BaseActivity() {
304+
lateinit var spotifyBroadcastReceiver: SpotifyBroadcastReceiver
305+
val broadcasts: MutableList<SpotifyBroadcastEventData> = mutableStateListOf()
306+
307+
override fun onCreate(savedInstanceState: Bundle?) {
308+
super.onCreate(savedInstanceState)
309+
spotifyBroadcastReceiver = SpotifyBroadcastReceiver(this)
310+
311+
...
312+
313+
registerSpotifyBroadcastReceiver(spotifyBroadcastReceiver, *SpotifyBroadcastType.values())
314+
}
315+
}
316+
317+
```

0 commit comments

Comments
 (0)