Skip to content

Commit

Permalink
feat: use react-native NativeEventEmitter (#189)
Browse files Browse the repository at this point in the history
* Use NativeEventEmitter
* Adopt react-native event emitter signature
* Update docs
  • Loading branch information
hoangvvo authored Jun 13, 2022
1 parent f820b55 commit 1f99933
Show file tree
Hide file tree
Showing 24 changed files with 337 additions and 635 deletions.
6 changes: 3 additions & 3 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@
]
},
{
"login": "hoang",
"login": "hoangvvo",
"name": "Hoang",
"avatar_url": "https://avatars.githubusercontent.com/u/170571?v=4",
"profile": "https://github.com/hoang",
"avatar_url": "https://avatars.githubusercontent.com/u/40987398?v=4",
"profile": "https://github.com/hoangvvo",
"contributions": [
"code"
]
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]


### Refactor

- feat: use react-native NativeEventEmitter (#189)

### Fix

- Update gradle to direct include aar files to fix [#194](https://github.com/cjam/react-native-spotify-remote/issues/194)


## [0.3.11-6] - 2022-06-04

### Fix

- Android null check to fix [#191](https://github.com/cjam/react-native-spotify-remote/issues/191) (Thanks @pretorh)

## [0.3.11-5] - 2022-02-10
Expand All @@ -30,15 +37,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- update `react-native-events` to `^1.0.21` (#174)

## [0.3.11-3] - 2021-12-19

### Fix

- Update react-native-events (Fix [#173](https://github.com/cjam/react-native-spotify-remote/issues/173))
- Fix PlayerRestrictions key name ([#172](https://github.com/cjam/react-native-spotify-remote/issues/172))

## [0.3.11-2] - 2021-11-11

### Documentation

- Add note for Android 11 setup

### Chore

- Fix build scripts, add back the postpack script
- update `.npmignore`

Expand Down
18 changes: 4 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ npm install --save react-native-spotify-remote
## Linking

As of React Native `> 0.61`, auto linking should work for both iOS and Android. There shouldn't be any modifications necessary and it _Should_ work out of the box. The one caveat, is that `react-native-events` needs to be linked as it doesn't yet support auto linking. If you do run into issues or are using an older version of React Native, the following sections should help get you up and running.
As of React Native `> 0.61`, auto linking should work for both iOS and Android. There shouldn't be any modifications necessary and it _Should_ work out of the box. If you do run into issues or are using an older version of React Native, the following sections should help get you up and running.

### iOS

Expand All @@ -86,7 +86,6 @@ As of React Native `> 0.61`, auto linking should work for both iOS and Android.
By far the easiest way to integrate into your project. In your `ios/PodFile` add the following lines to your projects target:

```rb
pod 'RNEventEmitter', :path => "../node_modules/react-native-events"
pod 'RNSpotifyRemote', :path => '../node_modules/react-native-spotify-remote'
```

Expand Down Expand Up @@ -139,15 +138,12 @@ Modifications are needed for the `AppDelegate.m`:
If you need to link your project manually, here are some things you'll need to do.
> ### `react-native-events` does not support autolinking at this point and will need to be manually linked into your application
1. Open up `android/app/src/main/java/[...]/MainApplication.java`
- Add the following imports to the top of the file
```
import com.reactlibrary.RNSpotifyRemotePackage;
import com.lufinkey.react.eventemitter.RNEventEmitterPackage;
```
- Add to the list returned by `getPackages()` for example:
Expand All @@ -158,7 +154,6 @@ import com.lufinkey.react.eventemitter.RNEventEmitterPackage;
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new RNEventEmitterPackage());
packages.add(new RNSpotifyRemotePackage());
return packages;
}
Expand All @@ -169,15 +164,11 @@ import com.lufinkey.react.eventemitter.RNEventEmitterPackage;
```
include ':react-native-spotify-remote'
project(':react-native-spotify-remote').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-spotify-remote/android')
include ':react-native-events'
project(':react-native-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-events/android')
```

3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
```
implementation project(':react-native-spotify-remote')
implementation project(':react-native-events')
```
4. As per the [Spotify Android SDK Docs](https://developer.spotify.com/documentation/android/guides/android-authentication/) Insert the following lines into `android/app/src/AndroidManifest.xml`

Expand Down Expand Up @@ -270,7 +261,7 @@ Please do not open issues about getting the module to work unless you have tried
Big thanks to [@lufinkey](https://github.com/lufinkey) and all of the great work that he has done in the [react-native-spotify](https://github.com/lufinkey/react-native-spotify) repo which was the original source of inspiration and some useful patterns for this package.

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-14-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
Expand All @@ -292,7 +283,7 @@ Big thanks to [@lufinkey](https://github.com/lufinkey) and all of the great work
<td align="center"><a href="https://github.com/reinhardholl"><img src="https://avatars.githubusercontent.com/u/4051986?v=4" width="100px;" alt=""/><br /><sub><b>Reinhard Höll</b></sub></a><br /><a href="https://github.com/cjam/react-native-spotify-remote/issues?q=author%3Areinhardholl" title="Bug reports">🐛</a> <a href="https://github.com/cjam/react-native-spotify-remote/commits?author=reinhardholl" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/gustavoggs"><img src="https://avatars.githubusercontent.com/u/793491?v=4" width="100px;" alt=""/><br /><sub><b>Gustavo Graña</b></sub></a><br /><a href="https://github.com/cjam/react-native-spotify-remote/issues?q=author%3Agustavoggs" title="Bug reports">🐛</a> <a href="https://github.com/cjam/react-native-spotify-remote/commits?author=gustavoggs" title="Code">💻</a></td>
<td align="center"><a href="https://www.companjenapps.com"><img src="https://avatars.githubusercontent.com/u/12894112?v=4" width="100px;" alt=""/><br /><sub><b>Dylan</b></sub></a><br /><a href="https://github.com/cjam/react-native-spotify-remote/commits?author=dylancom" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/hoang"><img src="https://avatars.githubusercontent.com/u/170571?v=4" width="100px;" alt=""/><br /><sub><b>Hoang</b></sub></a><br /><a href="https://github.com/cjam/react-native-spotify-remote/commits?author=hoang" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/hoangvvo"><img src="https://avatars.githubusercontent.com/u/40987398?v=4" width="100px;" alt=""/><br /><sub><b>Hoang</b></sub></a><br /><a href="https://github.com/cjam/react-native-spotify-remote/commits?author=hoangvvo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/pretorh"><img src="https://avatars.githubusercontent.com/u/4050990?v=4" width="100px;" alt=""/><br /><sub><b>Hendri Pretorius</b></sub></a><br /><a href="https://github.com/cjam/react-native-spotify-remote/issues?q=author%3Apretorh" title="Bug reports">🐛</a> <a href="https://github.com/cjam/react-native-spotify-remote/commits?author=pretorh" title="Code">💻</a></td>
</tr>
</table>
Expand All @@ -301,7 +292,6 @@ Big thanks to [@lufinkey](https://github.com/lufinkey) and all of the great work
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->


## Projects using this library

Checkout existing [Projects](./PROJECTS.md) that are using this library.
Checkout existing [Projects](./PROJECTS.md) that are using this library.
1 change: 0 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ dependencies {
implementation (name: "spotify-auth-release-1.2.3", ext: "aar")
implementation (name: "spotify-app-remote-release-0.7.2", ext: "aar")
implementation "com.google.code.gson:gson:2.8.5" // needed by spotify-app-remote
implementation project(path: ':react-native-events') // From node_module
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
}
126 changes: 85 additions & 41 deletions android/src/main/java/com/reactlibrary/RNSpotifyRemoteAppModule.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package com.reactlibrary;

import android.util.Log;
Expand All @@ -13,25 +12,27 @@
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import com.lufinkey.react.eventemitter.RNEventEmitter;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.spotify.android.appremote.api.ConnectionParams;
import com.spotify.android.appremote.api.Connector;
import com.spotify.android.appremote.api.SpotifyAppRemote;
import com.spotify.android.appremote.api.error.CouldNotFindSpotifyApp;
import com.spotify.android.appremote.api.error.NotLoggedInException;
import com.spotify.android.appremote.api.error.UserNotAuthorizedException;

import com.lufinkey.react.eventemitter.RNEventConformer;

import com.spotify.protocol.client.CallResult;
import com.spotify.protocol.client.ErrorCallback;
import com.spotify.protocol.types.ListItem;

import java.util.Stack;
import com.spotify.protocol.client.Subscription;
import com.spotify.protocol.types.PlayerContext;
import com.spotify.protocol.types.PlayerState;

import java.util.Stack;
import java.util.HashMap;

@ReactModule(name = "RNSpotifyRemoteAppRemote")
public class RNSpotifyRemoteAppModule extends ReactContextBaseJavaModule implements RNEventConformer {
public class RNSpotifyRemoteAppModule extends ReactContextBaseJavaModule {
private static final String LOG_TAG = "RNSpotifyAppRemote";

private final ReactApplicationContext reactContext;
Expand All @@ -41,19 +42,29 @@ public class RNSpotifyRemoteAppModule extends ReactContextBaseJavaModule impleme
private Connector.ConnectionListener mSpotifyRemoteConnectionListener;
private Stack<Promise> mConnectPromises = new Stack<Promise>();

private Subscription<PlayerContext> mPlayerContextSubscription;
private Subscription<PlayerState> mPlayerStateSubscription;

public static final String EventNamePlayerStateChanged = "playerStateChanged";
public static final String EventNamePlayerContextChanged = "playerContextChanged";
public static final String EventNameRemoteDisconnected = "remoteDisconnected";
public static final String EventNameRemoteConnected = "remoteConnected";

private HashMap<String, Boolean> subscriptionHasListeners = new HashMap<String, Boolean>();

public RNSpotifyRemoteAppModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
mSpotifyRemoteConnectionListener = new Connector.ConnectionListener() {

public void onConnected(SpotifyAppRemote spotifyAppRemote) {
mSpotifyAppRemote = spotifyAppRemote;
handleOnConnect();
handleEventSubscriptions();
while (!mConnectPromises.empty()) {
Promise promise = mConnectPromises.pop();
promise.resolve(true);
}
sendEvent("remoteConnected", null);
sendEvent(EventNameRemoteConnected, null);
}

public void onFailure(Throwable throwable) {
Expand All @@ -66,52 +77,85 @@ public void onFailure(Throwable throwable) {
} else if (throwable instanceof CouldNotFindSpotifyApp) {
promise.reject(new Error("Spotify connection failed: could not find the Spotify app, it may need to be installed."));
} else {
promise.reject(throwable);
promise.reject(throwable);
}
}
sendEvent("remoteDisconnected", null);
sendEvent(EventNameRemoteDisconnected, null);
}
};
}

@Override
@ReactMethod
public void __registerAsJSEventEmitter(int moduleId) {
RNEventEmitter.registerEventEmitterModule(this.reactContext, moduleId, this);
private void sendEvent(String eventName,
Object params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}

@Override
public void onNativeEvent(String eventName, Object... args) {
// Called when an event for this module is emitted from native code
@ReactMethod
public void addListener(String eventName) {
// Set up any upstream listeners or background tasks as necessary
}

@Override
public void onJSEvent(String eventName, Object... args) {
// Called when an event for this module is emitted from javascript
@ReactMethod
public void removeListeners(Integer count) {
// Remove upstream listeners, stop unnecessary background tasks
}

@Override
public void onEvent(String eventName, Object... args) {
// Called when any event for this module is emitted
@ReactMethod
public void eventStartObserving(String eventName) {
// Will be called when the event first listener is added.
subscriptionHasListeners.put(eventName, true);
handleEventSubscriptions();
}

private void sendEvent(String eventMame, Object data) {
RNEventEmitter.emitEvent(this.reactContext, this, eventMame, data);
@ReactMethod
public void eventStopObserving(String eventName) {
// Will be called when the event last listener is removed.
subscriptionHasListeners.put(eventName, false);
handleEventSubscriptions();
}

private void handleOnConnect() {
mSpotifyAppRemote.getPlayerApi()
.subscribeToPlayerContext()
.setEventCallback(playerContext -> {
ReadableMap map = Convert.toMap(playerContext);
sendEvent("playerContextChanged", map);
});
mSpotifyAppRemote.getPlayerApi()
.subscribeToPlayerState()
.setEventCallback(playerState -> {
WritableMap map = Convert.toMap(playerState);
sendEvent("playerStateChanged", map);
});

private void handleEventSubscriptions() {
if (mSpotifyAppRemote == null)
return;

Boolean hasContextListeners = subscriptionHasListeners.get(EventNamePlayerContextChanged);
Boolean hasPlayerStateListeners = subscriptionHasListeners.get(EventNamePlayerContextChanged);

if (hasContextListeners != null && hasContextListeners) {
if (mPlayerContextSubscription != null && !mPlayerContextSubscription.isCanceled()) {
return; // already subscribed
}
mPlayerContextSubscription = mSpotifyAppRemote.getPlayerApi()
.subscribeToPlayerContext()
.setEventCallback(playerContext -> {
ReadableMap map = Convert.toMap(playerContext);
sendEvent(EventNamePlayerContextChanged, map);
});
} else {
if (mPlayerContextSubscription != null && !mPlayerContextSubscription.isCanceled()) {
mPlayerContextSubscription.cancel();
mPlayerContextSubscription = null;
}
}

if (hasPlayerStateListeners != null && hasPlayerStateListeners) {
if (mPlayerStateSubscription != null && !mPlayerStateSubscription.isCanceled()) {
return; // already subscribed
}
mPlayerStateSubscription = mSpotifyAppRemote.getPlayerApi()
.subscribeToPlayerState()
.setEventCallback(playerContext -> {
ReadableMap map = Convert.toMap(playerContext);
sendEvent(EventNamePlayerStateChanged, map);
});
} else {
if (mPlayerStateSubscription != null && !mPlayerStateSubscription.isCanceled()) {
mPlayerStateSubscription.cancel();
mPlayerStateSubscription = null;
}
}
}

private <T> void executeAppRemoteCall(Function<SpotifyAppRemote, CallResult<T>> apiCall, CallResult.ResultCallback<T> resultCallback, ErrorCallback errorCallback) {
Expand All @@ -132,7 +176,7 @@ private void getPlayerStateInternal(CallResult.ResultCallback<ReadableMap> resul
.setResultCallback(playerState -> {
WritableMap map = Convert.toMap(playerState);
WritableMap eventMap = Convert.toMap(playerState);
sendEvent("playerStateChanged", eventMap);
sendEvent(EventNamePlayerStateChanged, eventMap);
resultCallback.onResult(map);
})
.setErrorCallback(errorCallback);
Expand Down Expand Up @@ -186,7 +230,7 @@ public void connect(String token, Promise promise) {
public void disconnect(Promise promise) {
if (mSpotifyAppRemote != null) {
SpotifyAppRemote.disconnect(mSpotifyAppRemote);
sendEvent("remoteDisconnected", null);
sendEvent(EventNameRemoteDisconnected, null);
}
promise.resolve(null);
}
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/js/search.js

Large diffs are not rendered by default.

Loading

0 comments on commit 1f99933

Please sign in to comment.