-
-
Notifications
You must be signed in to change notification settings - Fork 669
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
android notif: Replace our NotificationIntentService with directly opening MainActivity #5146
Conversation
9990022
to
2521e1d
Compare
@@ -11,6 +11,25 @@ import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; | |||
import com.zulipmobile.notifications.* | |||
import com.zulipmobile.sharing.maybeHandleShareIntent | |||
|
|||
/* Returns true just if we did handle the intent. */ | |||
fun maybeHandleOpenNotificationIntent(intent: Intent): Boolean { |
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 think we probably want this to live somewhere else (the share-to counterpart lives in SharingHelper.kt), and probably have a better name? I haven't settled on anything definite for either yet though.
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.
Yeah. Looking at this function and now looking at the share-to version beside it, I think what I really want is to have those two bits of code next to each other -- and probably cleanest as a single when (intent.action)
, with sub-conditions as needed (like looking for EXTRA_NOTIFICATION_DATA
, though actually the cleanest thing there -- which can be a followup -- is probably to parse the outermost parts of the URL.)
IOW rather than align the name of maybeHandleIntent
to reflect where it lives, align where it lives to reflect its name: move it to be a private method on MainActivity
, and then later in the branch add the open-notification case to it.
Part of why I'd like them to be next to each other is that they're carving out two different swaths of the same space of possibilities -- if we were to have any two things like this that could overlap in what they covered, then the order we called them in would start mattering, and it'd probably be a bug. So it's helpful to have them next to each other and ideally in the same when
statement, so that it's easy to see they're mutually exclusive and keep them that way.
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.
Thanks! Glad to have this getting cleaned up. Comments below.
when (appStatus) { | ||
null, ReactAppStatus.NOT_RUNNING, ReactAppStatus.BACKGROUND -> | ||
launchMainActivity(application as Context) | ||
ReactAppStatus.FOREGROUND -> Unit |
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 didn't find any reasons given for the conditional; possibly it's
an unquestioned relic of the Wix library we used to use.
Indeed the behavior goes back to the Wix library. Looks like it's basically unchanged since 9eddca8:
android notif: Use standard upstream API for watching RN lifecycle.
This has exactly the same effect as the 80 lines of code in the wix
library's ReactAppLifecycleFacade.
with the logic a bit refactored in 9b0157d and 0feb940.
I don't think having the conditional is necessarily bad, given that launching isn't going to do anything useful when already in the foreground. But with the changes upcoming in this branch, it becomes useful to do this unconditionally, so it's a good change now.
Log.d(TAG, "NotifyReact: launching main activity") | ||
Log.d(TAG, "android notif: launching main activity") |
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.
android notif: Remove false specificity from a log line
Launching MainActivity doesn't really have anything to do with
"notifying React", i.e., sending information about the opened
notification to the JS environment.
Yeah, the prefix there was really to make this log line identifiably correspond to this line of code -- so it was a reference to the filename, not a description of the substance.
I think the simplest thing is just to delete this line, if you haven't had a use for it. (That's what happens later in the branch anyway.)
(If we were to keep this, I'd drop "android" from the prefix -- this is already going to an Android-specific log system.)
override fun onNewIntent(intent: Intent?) { | ||
override fun onNewIntent(intent: Intent) { |
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.
android notif [nfc]: Make MainActivity.onNewIntent's `intent` arg required
ReactActivity's `.onNewIntent`, which we're overriding, requires `intent`.
I think it doesn't. Here's the declaration in ReactActivity.java
:
@Override
public void onNewIntent(Intent intent) {
That doesn't mention the possibility of the argument being null. But... in Java, all object types are nullable. So Java Intent
translates to Kotlin Intent?
.
Sometimes there's a Java annotation @NotNull
indicating that a given value isn't supposed to be null. But there doesn't seem to be one of those here. So if a caller passes null, no linter would complain either.
If you look in the Kotlin version of the SDK docs:
https://developer.android.com/reference/kotlin/android/app/Activity#onnewintent
this is reflected in the notation Intent!
:
protected open fun onNewIntent(intent: Intent!): Unit
The meaning of that !
notation is defined here:
https://kotlinlang.org/docs/java-interop.html#notation-for-platform-types
T!
means "T
orT?
"
Practically, the way I ended up with this version of the definition is probably that I started typing "onNewIntent" and let Android Studio autocomplete an empty override for me.
&& intent.hasExtra(EXTRA_NOTIFICATION_DATA) | ||
) { | ||
val data = intent.getBundleExtra(EXTRA_NOTIFICATION_DATA) | ||
logNotificationData("notif opened", data!!) |
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.
android notif: Move a log line into MainActivity's lifecycle methods
This commit is kind of confusing because it's rather a lot more code than just moving a log line sounds like it should be:
2 files changed, 26 insertions(+), 4 deletions(-)
The added plumbing makes sense because of the real work we have it do in the next commit. I think the clearest thing is probably to squash that one into this. The result is only slightly bigger than this already is:
2 files changed, 26 insertions(+), 6 deletions(-)
and it's easier to understand as changing where we do the real work, with the log line coming along for the ride.
|
||
class NotificationIntentService : IntentService("NotificationIntentService") { | ||
override fun onHandleIntent(intent: Intent?) { | ||
val context = applicationContext as? MainApplication ?: return | ||
if (Intent.ACTION_VIEW == intent!!.action) { | ||
val data = intent.getBundleExtra(EXTRA_NOTIFICATION_DATA) | ||
onOpened(context, data!!) | ||
Log.d(TAG, "android notif: launching main activity") |
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.
This commit and the next move this code around twice:
6fd1a19 android notif [nfc]: Inline NotificationUiManager.onOpened
2 files changed, 23 insertions(+), 26 deletions(-)
2521e1d android notif: Remove NotificationIntentService
2 files changed, 21 insertions(+), 50 deletions(-)
If squashed, the resulting diff is just slightly bigger than one of those:
2 files changed, 21 insertions(+), 53 deletions(-)
That also keeps the move within one file, rather than moving to a different file and back. So I think it's clearest to squash those two together.
With that, the preceding commit:
3cf3564 android notif [nfc]: Rename variables to ease next refactor
2 files changed, 6 insertions(+), 6 deletions(-)
also becomes unnecessary.
(Also just replied to the initial thread above.) |
…ally The intent used to start the main activity has the FLAG_ACTIVITY_NEW_TASK flag. The doc on that flag says this [1]: > When using this flag, if a task is already running for the > activity you are now starting, then a new activity will not be > started; instead, the current task will simply be brought to the > front of the screen with the state it was last in. That should mean that there's no hiccup or loss of state if we run our code to launch the main activity from the main activity when it's already foregrounded. Empirically that seems to be the case. I didn't find any history that says the conditional is necessary; looks like it dates back to the old Wix library we used to use here [2]. [1] https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK [2] zulip#5146 (comment)
2521e1d
to
c03ffe8
Compare
Thanks for the review! Revision pushed. |
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.
Thanks for the revision! Looks good -- just small comments below.
|
||
@JvmField | ||
val TAG = "ShareToZulip" | ||
|
||
/* Returns true just if we did handle the intent. */ | ||
fun maybeHandleIntent( |
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.
android notif [nfc]: Make maybeHandleIntent a helper method on MainActivity
nit: s/ notif//
Similarly for the next commit:
android notif [nfc]: Simplify interface of MainActivity's maybeHandleIntent
if (!intent.hasExtra(EXTRA_NOTIFICATION_DATA)) { | ||
return false | ||
} | ||
|
||
val data = intent.getBundleExtra(EXTRA_NOTIFICATION_DATA) | ||
logNotificationData("notif opened", data!!) |
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.
This can be cleaned up slightly by putting the getBundleExtra
first, and then checking if it's null instead of calling hasExtra
. Then the !!
becomes unnecessary.
That also means that if somehow we get an intent with an EXTRA_NOTIFICATION_DATA
extra that isn't a Bundle, then we don't crash at the !!
and instead just ignore it here as unhandled.
// TODO deduplicate this with handleSend in SharingHelper.kt. | ||
// Until then, keep in sync when changing. |
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 could deduplicate all the way down to `appStatus`, except
Yeah. There's still quite a bit of trickiness here that I'd like to deduplicate: sometimes we set a static/global variable, and sometimes we emit an event, and we need to pick the right one or we're likely to drop or duplicate an event. My ideal version of this and handleSend
is that they'd each say: here's a WritableMap
, and here's a little value identifying where the event should go, and then all that logic like the when (appStatus)
would be in a common function they both invoke.
This change is a helpful step! But let's keep these TODO comments here.
} | ||
|
||
|
||
val host = (application as ReactApplication).reactNativeHost |
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.
nit: just one blank line
/* Returns true just if we did handle the intent. */ | ||
private fun maybeHandleIntent( | ||
intent: Intent?, | ||
contentResolver: ContentResolver |
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.
This parameter can be simplified out in much the same way as application: ReactApplication
: the call sites just get it from a getter on this
, so this function can do the same thing.
val host = (application as ReactApplication).reactNativeHost | ||
val reactContext = host.tryGetReactInstanceManager()?.currentReactContext |
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.
Hmm, actually I don't love having these lines run here, above the when (intent.action)
.
The thing is that that means they run even if they're not needed -- if the intent is something else. They look like they could be cheap and side-effect-free… or could not be. I think there isn't any live case where it'd matter even if they are -- the only intents we handle other than the two cases below are the web-auth intents, where we'll want a React instance/context anyway -- but I'd rather not have to worry about that possibility coming up in the future.
Maybe simplest is I'll just add a commit at the end to address this.
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.
Maybe simplest is I'll just add a commit at the end to address this.
OK, SGTM, thanks!
…ally The intent used to start the main activity has the FLAG_ACTIVITY_NEW_TASK flag. The doc on that flag says this [1]: > When using this flag, if a task is already running for the > activity you are now starting, then a new activity will not be > started; instead, the current task will simply be brought to the > front of the screen with the state it was last in. That should mean that there's no hiccup or loss of state if we run our code to launch the main activity from the main activity when it's already foregrounded. Empirically that seems to be the case. I didn't find any history that says the conditional is necessary; looks like it dates back to the old Wix library we used to use here [2]. [1] https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK [2] zulip#5146 (comment)
c03ffe8
to
dc7d4a7
Compare
Thanks! Revision pushed. |
Ah, and is there a followup from #5146 (comment) :
I'm not really sure what you mean there. |
Most of them were already formatted correctly, and they'll still be formatted correctly at the end of this series.
For the reason given in the TODO, but also because we want to try removing the me.leolin.shortcutbadger.ShortcutBadger dependency and seeing if things still work OK. We leave the `connectGlobal` call because the component still uses the `dispatch` prop.
…n open I tested this on the office Android device (a Samsung Galaxy S9 running Android 9), and badge counts still work as I expect, including decreasing as messages are read, and disappearing if it's gone down to zero.
We stopped calling one of the methods from JS in a recent commit.
…ally The intent used to start the main activity has the FLAG_ACTIVITY_NEW_TASK flag. The doc on that flag says this [1]: > When using this flag, if a task is already running for the > activity you are now starting, then a new activity will not be > started; instead, the current task will simply be brought to the > front of the screen with the state it was last in. That should mean that there's no hiccup or loss of state if we run our code to launch the main activity from the main activity when it's already foregrounded. Empirically that seems to be the case. I didn't find any history that says the conditional is necessary; looks like it dates back to the old Wix library we used to use here [2]. [1] https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK [2] zulip#5146 (comment)
…eact` This should be fine and NFC because `launchMainActivity` is just starting an asynchronous task, without waiting for it to complete.
I think we haven't had any use for this line.
And, while we're at it, move launchMainActivity's definition, too, since it has nothing to do with "notifying React", i.e., sending information about the opened notification to the JS environment.
To plumb the notification data to the new location, we attach the data to the launch-main-activity intent as an extra, copied from the present intent (the one created when the notification was received, that we're handling with NotificationIntentService).
And replace it with directly opening MainActivity. When we set targetSdkVersion to 31, we will no longer be allowed to use a Service as a "notification trampoline", for performance reasons: https://developer.android.com/about/versions/12/behavior-changes-12#notification-trampolines So, go with their recommended fix, and have the PendingIntent open MainActivity directly. In the preceding commits, we moved everything out of NotificationIntentService except the code that starts MainActivity, to make this commit as small as possible. (The really important bit was the `notifyReact` call, which lets the JS environment know about the notification the user opened.) Fixes: zulip#5145
And link to a comment from Greg on further work toward deduplicating.
Thanks! Looks good -- merging. I made one tweak to cut the I have a draft for that followup and this one: #5146 (comment), and I'll send those as a separate PR because they're nontrivial refactors. |
dc7d4a7
to
fdec74f
Compare
Great, thanks very much! |
…ally The intent used to start the main activity has the FLAG_ACTIVITY_NEW_TASK flag. The doc on that flag says this [1]: > When using this flag, if a task is already running for the > activity you are now starting, then a new activity will not be > started; instead, the current task will simply be brought to the > front of the screen with the state it was last in. That should mean that there's no hiccup or loss of state if we run our code to launch the main activity from the main activity when it's already foregrounded. Empirically that seems to be the case. I didn't find any history that says the conditional is necessary; looks like it dates back to the old Wix library we used to use here [2]. [1] https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_NEW_TASK [2] zulip#5146 (comment)
Fixes: #5145