-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement handling of Android actions in background #1074
Implement handling of Android actions in background #1074
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dluksza Thanks so much for submitting this PR, it's greatly appreciated. I've added a few comments with some suggested renames and a couple of cases which I think need to be considered before we can get this merged.
} | ||
} | ||
|
||
private boolean isAppOnForeground(Context context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change this to use the method from the io.invertase.firebase.Utils
class rather than duplicating?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
import java.util.List; | ||
|
||
public class RNFirebaseBackgroundNotificationReceiver extends BroadcastReceiver { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this to RNFirebaseBackgroundNotificationActionReceiver
to make it clear that it handles actions and not notifications themselves
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
serviceIntent.putExtras(intent.getExtras()); | ||
context.startService(serviceIntent); | ||
|
||
if (!isAppOnForeground((context))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the app is in the foreground when the Snooze
action is hit?
I think it will currently just get lost - instead we need to make sure that the action gets passed through into the standard onNotificationOpened
handler so that it can be handled normally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to test this. I'm not sure if going through standard onNotificationOpened
way is the best solution, since this would require code duplication (the same handler function should be called from two places). Would rather handle everything in background.
There is a small drawback for the always background approach. When background action handler is changing the UI state this should be achieved using some kind of event or listener approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it would be better to handle things separately in the foreground and background as it gives more flexibility to the developer if they're like their app to act differently. You've already touched on UI state which might need to be updated in the foreground.
The functionality that's shared can easily be wrapped inside a method which can be called from both the background and foreground handler to prevent code duplication.
|
||
import io.invertase.firebase.messaging.MessagingSerializer; | ||
|
||
public class RNFirebaseBackgroundNotificationsService extends HeadlessJsTaskService { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, can this be renamed to RNFirebaseBackgroundNotificationActionsService
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
if (extras != null) { | ||
WritableMap notificationMap = Arguments.makeNativeMap(intent.getExtras()); | ||
return new HeadlessJsTaskConfig( | ||
"RNFirebaseBackgroundNotification", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we change this to RNFirebaseBackgroundNotificationAction
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) { | ||
Bundle extras = intent.getExtras(); | ||
if (extras != null) { | ||
WritableMap notificationMap = Arguments.makeNativeMap(intent.getExtras()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to make sure that the object being passed to the JS side conforms to the NotificationOpen
structure. At the moment it doesn't allow for a snooze
action with a remote input specified.
Take a look at the parseIntentForLocalNotification
method in RNFirebaseNotifications
to see how this is done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There are some cases when local notification action should be handled in background eg. snoozing the reminder. In case of it launching app UI is not necessary and would be confusing for the end user. Therefore there should be a way to handle local notification action in background. For this reason new property 'runInBackground' was added to the AndroidAction class and TypeScript type. Also new broadcast receiver and service were implemented to handle properly background actions. In order to run particular action in background API consumer need to set its 'runInBackground' property to 'true', eg: ... .android.addAction(new firebase.notifications.Android.Action("snooze", "ic_snooze", "Snooze").setRunInBackground(true)) ... Then, there are two cases that API consumer needs to handle. First when app is in the foreground, standard notification and notification action code path will be executed. This mean, that: * onNotification() listener will be called (which should call displayNotification(), in order to show it to the user), * onNotificationOpen() listener will be called after the action is tapped by the user Secondly, when application is in background or it is not running new 'RNFirebaseBackgroundNotificationAction' handler will be called. To properly handle this case API consumer should create a background asynchronous handler: const handleAsyncTask = async (notificationOpen: NotifficationOpen) => { if (notificationOpen && notificationOpen.notification) { const action = notificationOpen.action; const notificationId = notificationOpen.notification.notificationId; if (action === "snooze") { console.log("Reschedule notification for later time", notificationId); } else { console.log("unsupported action", action); } // hide the notification firebase.notifications().removeDeliveredNotification(notificationId); } } Next hander should be registered to headless handler: AppRegistry.registerHeadlessTask('RNFirebaseBackgroundNotificationAction', () => handleAsyncTask); Finally AndroidManifest.xml file must be modified, to include receiver and service definition: <receiver android:name="io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationActionReceiver" android:exported="true"> <intent-filter> <action android:name="io.invertase.firebase.notifications.BackgroundAction"/> </intent-filter> </receiver> <service android:name="io.invertase.firebase.notifications.RNFirebaseBackgroundNotificationActionsService"/> Now when ever 'Snooze' action is pressed it will launch 'handleAsyncTask' function in the background or onNotificationOpen() when app is in foreground. And reschedule the notification for the later time.
fea93ed
to
17f7f39
Compare
Here is the updated version of previous PR. I've addressed all the comments, updated the commit message and rebased it on current master. |
return; | ||
} | ||
|
||
if (Utils.isAppInForeground(context)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you been able to test that this works in the foreground?
Generally the broadcast receiver runs outside the react context, so I'd be surprised if this did work as it is currently. The way we work around this in other areas is to use a local broadcast receiver which the react application listens to directly. I'm more than happy to merge and then make these changes myself, but wanted to understand if you'd managed to get it working this way before I do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just retested it with HabitChallenge app in three scenarios:
- app in foreground
- app in background
- app not running
In all of them everything worked as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW. Test were done with react-native
0.55.4 and android simulator running Android 8.1.0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! In which case, I will get this merged and we can keep an eye on it.
If we get reports of issues in the foreground then I can easily switch it over to use the local broadcast.
Thank you again for such a comprehensive PR! I'll look to get some documentation added and we'll include this as part of a release today or tomorrow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool! Looking forward for the release! :D
There are some cases when local notification action should be handled in
background eg. snoozing the reminder. In case of it launching app UI is
not necessary and would be confusing for the end user.
Therefore there should be a way to handle local notification action in
background.
For this reason new property 'runInBackground' was added to the
AndroidAction class and TypeScript type.
Also new broadcast receiver and service were implemented to handle
properly background actions.
In order to run particular action in background user need to set its
'runInBackground' property to 'true', eg:
Then create a background asynchronous handler:
Next hander should be registered to an headless handler:
Finally AndroidManifest.xml file must be modified, to include receiver
and service definition:
Now when ever 'Snooze' action is pressed it will launch
'handleAsyncTask' function in the background and reschedule the
notification for the later time.