Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[expo-notifications] Handling notifications (#6796)
# Why Next `expo-notifications` feature. # How - `NotificationsHandlerModule` registers at singleton for new notifications/messages - for each message it _starts up_ a task which emits an event to JS - in response to the JS event, delegate responds with the appropriate behavior (eg. `shouldShowAlert: true`) - the behavior is pushed to native side using `NotificationsHandler.handleNotificationAsync` call - which directs it to the appropriate task - task handles the behavior (on iOS calls `completionHandler`, on Android it will show the notification once implemented) and finishes - if for whatever reason delegate didn't respond in 3 seconds, `onTimeout` is called on task, which emits another event to JS (for debugging purposes) and the task finishes ![excalidraw-202012311929-7](https://user-images.githubusercontent.com/1151041/73078318-3ca14180-3ec2-11ea-9220-c7a2f3c1e558.png) # Test Plan Tested manually by sending notifications and logging messages that the scheme works both when the delegate responds and when it does not.
- Loading branch information
Showing
28 changed files
with
1,109 additions
and
118 deletions.
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
apps/bare-expo/ios/Pods/.project_cache/installation_cache.yaml
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
255 changes: 140 additions & 115 deletions
255
apps/bare-expo/ios/Pods/EXNotifications.xcodeproj/project.pbxproj
Large diffs are not rendered by default.
Oops, something went wrong.
1 change: 1 addition & 0 deletions
1
apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXNotificationsHandlerModule.h
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
1 change: 1 addition & 0 deletions
1
apps/bare-expo/ios/Pods/Headers/Private/EXNotifications/EXSingleNotificationHandlerTask.h
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
1 change: 1 addition & 0 deletions
1
apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXNotificationsHandlerModule.h
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
1 change: 1 addition & 0 deletions
1
apps/bare-expo/ios/Pods/Headers/Public/EXNotifications/EXSingleNotificationHandlerTask.h
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
...src/main/java/expo/modules/notifications/notifications/handling/NotificationsHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
package expo.modules.notifications.notifications.handling; | ||
|
||
import android.content.Context; | ||
|
||
import com.google.firebase.messaging.RemoteMessage; | ||
|
||
import org.unimodules.core.ExportedModule; | ||
import org.unimodules.core.ModuleRegistry; | ||
import org.unimodules.core.Promise; | ||
import org.unimodules.core.arguments.ReadableArguments; | ||
import org.unimodules.core.interfaces.ExpoMethod; | ||
|
||
import java.util.Collection; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import expo.modules.notifications.notifications.emitting.NotificationsEmitter; | ||
import expo.modules.notifications.notifications.interfaces.NotificationBehavior; | ||
import expo.modules.notifications.notifications.interfaces.NotificationListener; | ||
import expo.modules.notifications.notifications.interfaces.NotificationManager; | ||
|
||
/** | ||
* {@link NotificationListener} responsible for managing app's reaction to incoming | ||
* notification. | ||
* <p> | ||
* It is responsible for managing lifecycles of {@link SingleNotificationHandlerTask}s | ||
* which are responsible: one for each notification. This module serves as holder | ||
* for all of them and a proxy through which app responds with the behavior. | ||
*/ | ||
public class NotificationsHandler extends ExportedModule implements NotificationListener { | ||
private final static String EXPORTED_NAME = "ExpoNotificationsHandlerModule"; | ||
|
||
private NotificationManager mNotificationManager; | ||
private ModuleRegistry mModuleRegistry; | ||
|
||
private Map<String, SingleNotificationHandlerTask> mTasksMap = new HashMap<>(); | ||
|
||
public NotificationsHandler(Context context) { | ||
super(context); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return EXPORTED_NAME; | ||
} | ||
|
||
@Override | ||
public void onCreate(ModuleRegistry moduleRegistry) { | ||
mModuleRegistry = moduleRegistry; | ||
|
||
// Register the module as a listener in NotificationManager singleton module. | ||
// Deregistration happens in onDestroy callback. | ||
mNotificationManager = moduleRegistry.getSingletonModule("NotificationManager", NotificationManager.class); | ||
mNotificationManager.addListener(this); | ||
} | ||
|
||
@Override | ||
public void onDestroy() { | ||
mNotificationManager.removeListener(this); | ||
Collection<SingleNotificationHandlerTask> tasks = mTasksMap.values(); | ||
for (SingleNotificationHandlerTask task : tasks) { | ||
task.stop(); | ||
} | ||
} | ||
|
||
/** | ||
* Called by the app with {@link ReadableArguments} representing requested behavior | ||
* that should be applied to the notification. | ||
* | ||
* @param identifier Identifier of the task which asked for behavior. | ||
* @param behavior Behavior to apply to the notification. | ||
* @param promise Promise to resolve once the notification is successfully presented | ||
* or fails to be presented. | ||
*/ | ||
@ExpoMethod | ||
public void handleNotificationAsync(String identifier, final ReadableArguments behavior, Promise promise) { | ||
SingleNotificationHandlerTask task = mTasksMap.get(identifier); | ||
if (task == null) { | ||
String message = String.format("Failed to handle notification %s, it has already been handled.", identifier); | ||
promise.reject("ERR_NOTIFICATION_HANDLED", message); | ||
return; | ||
} | ||
task.handleResponse(new ArgumentsNotificationBehavior(behavior), promise); | ||
} | ||
|
||
/** | ||
* Callback called by {@link NotificationManager} to inform its listeners of new messages. | ||
* Starts up a new {@link SingleNotificationHandlerTask} which will take it on from here. | ||
* | ||
* @param message Received message | ||
*/ | ||
@Override | ||
public void onMessage(RemoteMessage message) { | ||
SingleNotificationHandlerTask task = new SingleNotificationHandlerTask(mModuleRegistry, message, this); | ||
mTasksMap.put(task.getIdentifier(), task); | ||
task.start(); | ||
} | ||
|
||
/** | ||
* Callback called by {@link NotificationManager} to inform that some push notifications | ||
* haven't been delivered to the app. It doesn't make sense to react to this event in this class. | ||
* Apps get notified of this event by {@link NotificationsEmitter}. | ||
*/ | ||
@Override | ||
public void onDeletedMessages() { | ||
// do nothing | ||
} | ||
|
||
/** | ||
* Callback called once {@link SingleNotificationHandlerTask} finishes. | ||
* A cue for removal of the task. | ||
* | ||
* @param task Task that just fulfilled its responsibility. | ||
*/ | ||
void onTaskFinished(SingleNotificationHandlerTask task) { | ||
mTasksMap.remove(task.getIdentifier()); | ||
} | ||
|
||
/** | ||
* An implementation of {@link NotificationBehavior} capable of | ||
* "deserialization" of behavior objects with which the app responds. | ||
* <p> | ||
* Used in {@link #handleNotificationAsync(String, ReadableArguments, Promise)} | ||
* to pass the behavior to {@link SingleNotificationHandlerTask}. | ||
*/ | ||
class ArgumentsNotificationBehavior extends NotificationBehavior { | ||
private static final String SHOULD_SHOW_ALERT_KEY = "shouldShowAlert"; | ||
private static final String SHOULD_PLAY_SOUND_KEY = "shouldPlaySound"; | ||
private static final String SHOULD_SET_BADGE_KEY = "shouldSetBadge"; | ||
|
||
private ReadableArguments mArguments; | ||
|
||
ArgumentsNotificationBehavior(ReadableArguments arguments) { | ||
mArguments = arguments; | ||
} | ||
|
||
@Override | ||
public boolean shouldShowAlert() { | ||
return mArguments.getBoolean(SHOULD_SHOW_ALERT_KEY); | ||
} | ||
|
||
@Override | ||
public boolean shouldPlaySound() { | ||
return mArguments.getBoolean(SHOULD_PLAY_SOUND_KEY); | ||
} | ||
|
||
@Override | ||
public boolean shouldSetBadge() { | ||
return mArguments.getBoolean(SHOULD_SET_BADGE_KEY); | ||
} | ||
} | ||
} |
Oops, something went wrong.