Skip to content
This repository has been archived by the owner on Sep 14, 2024. It is now read-only.

Commit

Permalink
fix: finishes to android
Browse files Browse the repository at this point in the history
  • Loading branch information
gtokman committed Apr 20, 2024
1 parent 1b7b0a4 commit 36103a5
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 65 deletions.
105 changes: 73 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ Android support is coming soon. Check out [#1](https://github.com/candlefinance/
1. You'll need to update your `AppDelegate.swift` to handle push check the example app [here](./example/ios/AppDelegate.swift) for an example.
2. If your AppDelegate is in Objective-C (`.mm|.m|.h`), create a new `AppDelegate.swift` file and bridging header, then delete the Objective-C AppDelegate and main.m file. Finally, copy the contents of the example app's [AppDelegate.swift](./example/ios/AppDelegate.swift) and [bridge header](./example/ios/PushExample-Bridging-Header.h) to your project.
3. Make sure you're on `iOS 15` or later.
4. You can also use Objective-C just add bridging header and import the module.
5. `UNUserNotificationCenterDelegate` set in AppDelegate.

### Android

- [x] Request permissions
- [x] Register for FCM token
- [x] Remote push notifications
- [x] Foreground
- [ ] Background
- [ ] Opened by tapping on the notification
- [x] Background + Headless JS
- [x] Opened by tapping on the notification
- [ ] Local push notifications

#### Setup
Expand All @@ -69,46 +71,61 @@ Android support is coming soon. Check out [#1](https://github.com/candlefinance/
The following code is used to handle push notifications on the React Native side:

```js
import Push from '@candlefinance/push';

// Init
const push = useMemo(() => new Push(), [])
import type { PushNotificationPermissionStatus } from '@candlefinance/push';
import { module as Push } from '@candlefinance/push';

// Shows dialog to request permission to send push notifications, gets APNS token
const isGranted = await push.requestPermissions();

// Get the APNS token w/o showing permission, useful if you want silent push notifications
await push.registerForToken();
push.registerForToken();

// Check permission status: 'granted', 'denied', or 'notDetermined'
const status = await push.getAuthorizationStatus();

// Check if APNS token is registered
const isRegistered = await push.isRegisteredForRemoteNotifications();

// Listeners
push.addListener('notificationReceived', (data) => {
switch (data.kind) {
case 'opened':
console.log('opened');
break;
case 'foreground':
case 'background':
console.log('foreground/background');
const { uuid } = data;
await push.onFinish(uuid);
break;
}
const { title, body } = data;
});

push.addListener('deviceTokenReceived', (token) => {});
push.addListener('errorReceived', (error) => {});

// Remove listeners
push.removeListener('notificationReceived');
push.removeListener('deviceTokenReceived');
push.removeListener('errorReceived');
React.useEffect(() => {
const { NativeEvent, NativeHeadlessTaskKey } = Push.getConstants();
console.log(NativeEvent, NativeHeadlessTaskKey);
Push.addTokenEventListener(NativeEvent.TOKEN_RECEIVED, (token) => {
console.log('TOKEN_RECEIVED:', token);
});
Push.addMessageEventListener(
NativeEvent.BACKGROUND_MESSAGE_RECEIVED,
(message, id) => {
console.log('BACKGROUND_MESSAGE_RECEIVED:', message);
if (id !== undefined) {
console.log('Completing notification:', id);
Push.completeNotification(id);
}
}
);
Push.addErrorListener(NativeEvent.FAILED_TO_REGISTER, (message) => {
console.log('FAILED_TO_REGISTER:', message);
});
Push.addMessageEventListener(NativeEvent.NOTIFICATION_OPENED, (message) => {
console.log('NOTIFICATION_OPENED:', message);
});
Push.addMessageEventListener(
NativeEvent.FOREGROUND_MESSAGE_RECEIVED,
(message) => {
console.log('FOREGROUND_MESSAGE_RECEIVED:', message);
}
);
Push.addMessageEventListener(
NativeEvent.LAUNCH_NOTIFICATION_OPENED,
(message) => {
console.log('LAUNCH_NOTIFICATION_OPENED:', message);
}
);
return () => {
Push.removeListeners(NativeEvent.TOKEN_RECEIVED);
Push.removeListeners(NativeEvent.BACKGROUND_MESSAGE_RECEIVED);
Push.removeListeners(NativeEvent.NOTIFICATION_OPENED);
Push.removeListeners(NativeEvent.FOREGROUND_MESSAGE_RECEIVED);
Push.removeListeners(NativeEvent.LAUNCH_NOTIFICATION_OPENED);
};
}, []);
```

## Testing
Expand All @@ -123,6 +140,30 @@ This will use the [payload.json](./example/payload.json) file to send a push not

Apple also has a new [console](https://developer.apple.com/notifications/push-notifications-console/) to test push notifications. If you print out the token from `deviceTokenReceived` listener, you can use it to send a push notification from the console.

## SNS

If you're using AWS SNS, you can use the following code to send a push notification

```
const message = // apns
os === 'ios' ? JSON.stringify({ APNS: JSON.stringify(payload) })
: // fcm
JSON.stringify({
GCM: JSON.stringify({
data: {
title: title,
body: body,
custom: customData,
data: customData,
priority: '1',
imageUrl:
'https://logo.png',
targetClass: 'com.yourapp.candle.MainActivity',
},
})
})
```

## Contributing

We are open to contributions. Please read our [Contributing Guide](CONTRIBUTING.md) for more information.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,15 @@ class FirebaseMessagingService : FirebaseMessagingService() {
HeadlessJsTaskService.acquireWakeLockNow(baseContext)
} else {
Log.e(TAG, "Failed to start headless task")
PushNotificationEventManager.sendEvent(
PushNotificationEventType.BACKGROUND_MESSAGE_RECEIVED, payload.toWritableMap()
)
}
} catch (exception: Exception) {
Log.e(TAG, "Something went wrong while starting headless task: ${exception.message}")
PushNotificationEventManager.sendEvent(
PushNotificationEventType.BACKGROUND_MESSAGE_RECEIVED, payload.toWritableMap()
)
}
}
}
Expand Down
21 changes: 18 additions & 3 deletions android/src/main/java/com/candlefinance/push/NotificationUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class PushNotificationsUtils(
val notificationContent = payload.rawData

notificationBuilder
.setSmallIcon(getResourceIdByName("ic_default_notification", "drawable"))
.setSmallIcon(getResourceIdByName("ic_default_notification_foreground", "drawable"))
.setContentTitle(notificationContent[PushNotificationsConstants.TITLE])
.setContentText(notificationContent[PushNotificationsConstants.BODY])
.setSubText(notificationContent[PushNotificationsConstants.SUBTITLE])
Expand All @@ -128,6 +128,7 @@ class PushNotificationsUtils(
putExtra(PushNotificationsConstants.OPENAPP, true)
val json = JSONObject()
notificationContent.forEach { (key, value) -> json.put(key, value) }
Log.d(Tag, "SAVE to intent rawData: $json")
putExtra("rawData", json.toString())
}

Expand Down Expand Up @@ -232,9 +233,22 @@ open class NotificationPayload(

fun toWritableMap(): WritableMap {
val map = Arguments.createMap()
map.putMap("rawData", Arguments.makeNativeMap(rawData))
if (rawData.containsKey("rawData")) {
val existingRawData = rawData["rawData"]?.let {
JSONObject(it)
}
val toMap = existingRawData?.let {
it.keys().asSequence().associateWith { key -> it.get(key).toString() }
}
toMap?.forEach { (key, value) -> map.putString(key, value) }
map.putMap("rawData", Arguments.makeNativeMap(toMap))
Log.d(Tag, "TO READABLE existing: $map")
} else {
map.putMap("rawData", Arguments.makeNativeMap(rawData))
rawData.forEach { (key, value) -> map.putString(key, value) }
Log.d(Tag, "TO READABLE new: $map")
}
map.putString("channelId", channelId)
rawData.forEach { (key, value) -> map.putString(key, value) }
return map
}

Expand All @@ -252,6 +266,7 @@ open class NotificationPayload(
return intent?.extras?.let {
val toMap: Map<String, String?> = it.keySet().associateWith { key -> it.get(key)?.toString() }
val contentProvider = NotificationContentProvider.FCM(toMap.filterValues { it != null } as Map<String, String>)
Log.d(Tag, "READING: Notification payload from intent: $toMap")
NotificationPayload(contentProvider)
}
}
Expand Down
42 changes: 12 additions & 30 deletions android/src/main/java/com/candlefinance/push/PushModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import android.content.Context.MODE_PRIVATE
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
Expand Down Expand Up @@ -57,7 +59,6 @@ class PushModule(
const val NAME = "Push"
}

private var isAppLaunch: Boolean = true
private var launchNotification: WritableMap? = null
private val sharedPreferences = reactContext.getSharedPreferences(PREF_FILE_KEY, MODE_PRIVATE)
private val scope = CoroutineScope(dispatcher)
Expand Down Expand Up @@ -181,46 +182,27 @@ class PushModule(
*/
override fun onHostResume() {
Log.d(TAG, "App resumed")
if (isAppLaunch) {
isAppLaunch = false
PushNotificationEventManager.init(reactApplicationContext)
val firebaseInstance = FirebaseMessaging.getInstance()
firebaseInstance.token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
Log.w(TAG, "Fetching FCM registration token failed")
return@OnCompleteListener
}
val params = Arguments.createMap().apply {
putString("token", task.result)
}
Log.d(TAG, "Send device token event")
PushNotificationEventManager.sendEvent(
PushNotificationEventType.TOKEN_RECEIVED,
params
)
})
currentActivity?.intent?.let {
PushNotificationEventManager.init(reactApplicationContext)
currentActivity?.intent?.let {
val payload = NotificationPayload.fromIntent(it)
if (payload != null) {
Log.d(TAG, "Launch notification found in intent waiting 5 seconds")
launchNotification = payload.toWritableMap()
// Launch notification opened event is emitted for internal use only
PushNotificationEventManager.sendEvent(
PushNotificationEventType.LAUNCH_NOTIFICATION_OPENED,
payload.toWritableMap()
)
Handler(Looper.getMainLooper()).postDelayed({
PushNotificationEventManager.sendEvent(
PushNotificationEventType.LAUNCH_NOTIFICATION_OPENED,
payload.toWritableMap()
)
}, 3000)
} else {
Log.d(TAG, "No launch notification found in intent")
}
}
} else {
// Wipe the launching notification as app was re-opened by some other means
Log.d(TAG, "Wipe launching notification")
launchNotification = null
}
}

override fun onHostPause() {
// noop - only overridden as this class implements LifecycleEventListener
Log.d(TAG, "App paused")
}

override fun onHostDestroy() {
Expand Down

0 comments on commit 36103a5

Please sign in to comment.