-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
[Nullability Annotations to Java Classes] Add Missing Nullability Annotations to All SDK Override Methods for Activities #18997
Conversation
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
Warning Message: "Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in const fields" Explanation: "Avoid the usage of resource IDs where constant expressions are required. A future version of the Android Gradle Plugin will generate R classes with non-constant IDs in order to improve the performance of incremental compilation." ------------------------------------------------------------------------ This Lint warning is suppressed, that is, instead of it being resolved, since a resolution would require a proper investigation. As such, it might be best to ignore this as out of scope, for now, and so as to not introduce any breaking changes with this upgrade overall. Related Commit: f8bc363
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
Warning Message: "Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in const fields" Explanation: "Avoid the usage of resource IDs where constant expressions are required. A future version of the Android Gradle Plugin will generate R classes with non-constant IDs in order to improve the performance of incremental compilation." ------------------------------------------------------------------------ This Lint warning is suppressed, that is, instead of it being resolved, since a resolution would require a proper investigation. As such, it might be best to ignore this as out of scope, for now, and so as to not introduce any breaking changes with this upgrade overall. Related Commit: f8bc363
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
FYI: 'n-a' stands for 'nullability annotations'.
This is done so as to have the 'onActivityResult(...)' method match the super method signature.
This is done so as to have the 'onQueryTextSubmit(...)' method match the super method signature.
This is done so as to have the 'onQueryTextChange(...)' method match the super method signature.
This is done so as to have the 'onCreateActionMode(...)' method match the super method signature.
This is done so as to have the 'onPrepareActionMode(...)' method match the super method signature.
This is done so as to have the 'onActionItemClicked(...)' method match the super method signature.
This is done so as to have the 'onActionItemClicked(...)' method match the super method signature.
This is done so as to have the 'onDestroyActionMode(...)' method match the super method signature.
This is done so as to have the 'onBindViewHolder(...)' method match the super method signature.
This is done so as to have the 'onNewIntent(...)' function for Kotlin files match the 'onNewIntent(...)' method for Java files. Otherwsie, half of those files were using 'intent' as nullable and the other half were using 'intent' as non null, which creates confusion.
This is done so as to have the 'onClick(...)' method match the super method signature.
📲 You can test the changes from this Pull Request in WordPress by scanning the QR code below to install the corresponding build.
|
📲 You can test the changes from this Pull Request in Jetpack by scanning the QR code below to install the corresponding build.
|
… into analysis/add-missing-nullability-annotations-to-all-sdk-override-methods-for-activities # Conflicts: # WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java
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.
Hi Petros 👋 😄, thank you for all the work you've done to analyze and catalog the nullness through our Java code pertaining to Activity
methods!
I left a few comments where I think we should be a bit careful about assumptions. In particular, I think we should try to avoid refactoring to a "fail-fast" mode when the existing code already has some robust guard for the @Nullable
case. This is because, I would rather avoid introducing any new crashes with this work.
That said, much of the work in this PR is actually primarily concerned with compile time correctness, and it'd be ideal, imo, to separate these kinds of changes from those which can affect the behavior of the running app. This would have several benefits:
- It will allow us to make smaller PRs, reducing the barrier to contribution by reviewers
- It enables some PRs to be reviewable and testable without smoke tests (e.g. when the changes only affect compile time)
- It isolates changes that have potential to introduce crashes or bugs (when nullness is ambiguous from un-annotated framework or library methods)
Also, the additional effort to refactor various nearby parts of the code is commendable! But, I wonder, too, if this should be contributed as part of a separate PR, since it seems that many of these improvements will not affect behavior of the running app. If it is the case that some crash is introduced by making a wrong guess (e.g. annotating @NotNull
when it should have been @Nullable
, etc.), it would be great to be able to revert the PR that introduced it without losing all of the other improvements.
Fwiw, I see that you have very neatly organized the commits, so, in theory, such a revert could still be applied on a commit by commit basis. However, whatever discussion that may surround such cases may be deserving of it's own PR.
In summary, I propose to split this (and similar) PRs by the following three categories:
- Analysis PRs which only affect compile time 👈 these are the low-hanging fruit, imo
- Analysis PRs which can affect behavior of the running app (more scrutiny needed)
- Refactor PRs which address various formatting issues, other lint rule violations, etc.
Wdyt?
WordPress/src/main/java/org/wordpress/android/ui/AddQuickPressShortcutActivity.java
Show resolved
Hide resolved
super.onCreateOptionsMenu(menu); | ||
MenuInflater inflater = getMenuInflater(); | ||
inflater.inflate(R.menu.app_log_viewer_menu, menu); | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean onOptionsItemSelected(final MenuItem item) { |
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 don't feel strongly about this, but typically, final
is used to prevent reassignment of the variable within the method. In this case, the method is rather simple, and item
is only even used in the default call to the super method. But final
parameters have no effect at the call site, so I'd generally be ok with keeping it - but in this case, since the method is very simple anyway, I'm also fine with removing it. 👍
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.
The only reason I removed this final
keyword is because I wanted every such method across the codebase to be consistent with each other.
Thus, and because I found very few occurrences of such final
usage on such onOptionsItemSelected(...)
methods across the whole codebase, that it is better to remove it here as well, rather than adding it everywhere else. No matter, I think it is better for us to use a consistent signature for such methods. Wdyt? 🤔
PS: Also, and since we don't have a documented way of using final
, and since I rarely seen it being used, I think it is better to avoid adding it unnecessarily everywhere. Thus, I decided it might be better to remove it instead, whenever found on those @Override
methods.
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 it is better for us to use a consistent signature for such methods
I agree with this, but my point above is that final
is not really part of the method signature. That's what I meant by "no effect at the call site".
Since, in this instance, the usage is for such a simple method, I think it's ok to remove it. Generally speaking, though, the intention for using this is entirely compatible with keeping signatures consistent. final
on a parameter only pertains to the usage of that reference within the method body, and is sometimes used to prevent reassignment of the variable. This can be a useful restriction in long / complex methods.
On this same topic, there are many parameter renaming changes, in which the commit message mentions this was done for the sake of consistent method signatures, but parameter names are not part of a methods signature in Java. I didn't see any issue with the renamings that you did, but was curious to know your motivation. Does it relate somehow to Kotlin?
To play devils advocate, I could envision deviating from the overriden super class parameter names for clarity's sake - to I don't think we should have a rule that all parameter names must match that of the super class. Especially because some super methods have inappropriately terse parameter names.
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 agree with this, but my point above is that final is not really part of the method signature. That's what I meant by "no effect at the call site".
👍
Generally speaking, though, the intention for using this is entirely compatible with keeping signatures consistent.
I agree with all your points @mkevins and apologies for the confusion here. What I meant with keeping signatures consistent wan't related to super vs implementing signature, but rather project wise, that is, so that when one is searching/copy-pasting such methods, that they are not seeing a mixture of both, some of them using a final
, while others are not. Now, I agree that the usage of final
is a case-by-case scenario, but on those specific cases I doubt that it was done like that (an assumption from my side). Instead, I think this was initial done purely because it is a best practice to use final
as much and as widely as possible, and then, that was blindly copy-pasted thereafter. However, since us using final
vs not using it at all hasn't been discussed, or agreed upon, and since me seeing and experiencing us not using final
during our day-to-day work with Java, that is, unless the compiler complains or warns us about it, I was thinking it is better to remove those usages as well. This way, IMHO, the next person to copy-paste such method would not unnecessarily copy-paste the final
as well, even if that means safer code... 🤷
The other way around for me would have been to add the final
to every such @Override
method. 🤔
Having said all the above, it is easier for me to plain ignore such changes if this will make it easier for you to review. I am only doing those to increase the consistency and make sure that we are using such @Override
method consistently across the codebase, just to have make us that bit more consistent. But, I can stop doing that if that makes you uncomfortable as removing final
is indeed less safe that having it there, or adding it everywhere missing, even if someone just copy-pasted that there from a previous such existing activity. 🤷
@@ -98,8 +99,8 @@ public void handleOnBackPressed() { | |||
|
|||
loadComment(getIntent()); | |||
|
|||
if (icicle != null) { | |||
if (icicle.getBoolean(ARG_CANCEL_EDITING_COMMENT_DIALOG_VISIBLE, false)) { |
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 had to look this up 😅 .. this is a throwback to pre Android 1.0 🤯 , when onSavedInstanceState
was called onFreeze
(release notes). That said, I'm fine with renaming this. 🫡 farewell, icicle
.
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.
Yeaaa... 🤣 Bye, bye icicle
and thanks for all the fish! 🎣
boolean hasError = (editContent.getError() != null); | ||
boolean hasText = (s != null && s.length() > 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.
I agree with your estimation that this probably shouldn't ever be null, but this line makes me wonder...
also, there are a few cases with null guards in the TextView source.. so maybe it's safer to assume nullable? Wdyt?
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 agree with your estimation that this probably shouldn't ever be null, but this line makes me wonder...
I think so too... 🤞
also, there are a few cases with null guards in the TextView source.. so maybe it's safer to assume nullable?
Can you help me connect the dots of the null guards you shared in the TextView source and this @NonNull Editable s
, I honestly fail to find a quick connection, but, I am also a bit rusty with everything TextView
, EditText
and TextWatcher
related, so please forgive me... 😊
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 help me connect the dots of the null guards you shared
I didn't connect any dots myself, just noted that there were several guards throughout that file where the underlying text (among other things) could be null. That said, it might be the case that it is safe to consider it @NonNull
, (considering things like this, for example). It is unfortunate that this was left unannotated in the docs, but my feeling is that it is probably safe. Still, I'd prefer that if we remove a guard, we do it in a separate PR so that we can attach an explicit crash monitoring task, to be proactive in observing the effect in production.
@@ -31,11 +31,9 @@ class ScanActivity : AppCompatActivity(), ScrollableViewInitializedListener { | |||
|
|||
private var binding: ScanActivityBinding? = null | |||
|
|||
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.
This is another case where there is a suppression on UnknownNullness
in the framework source 🤷♂️ . I wonder if it is safer to leave it as @Nullable
? Also, strangely, I'm seeing this error:
Overriding method should call `super.onNewIntent`
but, I'm not sure if it is related with the signature 🤔
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 is another case where there is a suppression on UnknownNullness in the framework source 🤷♂️ .
Yea, spot-on, I also researched the codebase and saw half of those intent
being used as nullable, while other half as non-null (Java and Kotlin files included). Them being already used as @NonNull
made me take the risky
decision to mark them all as @NonNull
, making this signature consistent across the whole codebase. 🤞
I wonder if it is safer to leave it as @nullable?
Obviously it is safer to leave it and change everything else as @Nullable
, but I wonder if it is better to "fail fast" or "fail silently"... I am usually on the "fail fast" side. 🤷
Also, strangely, I'm seeing this error:
Overriding method should call `super.onNewIntent`
but, I'm not sure if it is related with the signature 🤔
About this warning, I've also noticed it, but this is unrelated to the signature problem as for the life of me I could get rid of it no matter what I tried. PS: I am also founding this to happen occasionally on other such methods as well. 🤔
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 wonder if it is better to "fail fast" or "fail silently"... I am usually on the "fail fast" side.
I'm usually on the "fail faster" side, which, to me, means failing at compile time. I don't think that assigning @Nullable
will be "fail silently", since the compiler would fail to even build the binary.
That said, the wide usage (half, as you mentioned) of this as @NonNull
is a good indication that this is likely safe.. but as I've mentioned before, I think changes that could introduce a crash should be on a separate PR so that we could also have an associated crash monitoring task. Then, the other improvements (that could not possibly introduce a crash) would be unblocked.
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 agree on having a separate PR @mkevins ! 👍
PS: With "fail silently" here I didn't mean at compile time, or even at runtime. What I meant is that we would have branches of code that are doing nothing, and then, us developers thinking that those branches are active, we would unnecessary maintain and develop on top of those. I am sorry to use "fail silently" and "fail fast" to mean various things, and all at once, I just didn't want to introduce "something else" to avoid making it harder to follow this discussion.
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 explanation. 👍 I agree that if we wrongly annotate something as @Nullable
and then add (or even keep) guards in place, that those conditional branches might be (and have always been) dead code.
@@ -1311,7 +1311,7 @@ private void setSite(Intent data) { | |||
|
|||
@Override | |||
@SuppressWarnings("deprecation") | |||
public void onActivityResult(int requestCode, int resultCode, Intent data) { | |||
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent 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 should be fine, especially if the compiler does not complain. I'm wondering how this came to be, since typically when you autocomplete an override, it will use the same access modifier as the super method 🤔 . Reseting it makes sense, since if there was a need for this to be public, the build would fail. 👍
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.
Reseting it makes sense, since if there was a need for this to be public, the build would fail. 👍
Exactly, and again, I am going for consistency here and making this safe minor changes seems easy enough for the better they produce, not having us to worry again about why the protected
was/is used. 👍
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { | ||
if (mScrollRange == -1) { | ||
public void onOffsetChanged(@Nullable AppBarLayout appBarLayout, int verticalOffset) { | ||
if (mScrollRange == -1 && appBarLayout != null) { |
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.
Just curious, should anything be added to an else
block when appBarLayout
is null? I wonder, since, if appBarLayout
is null
, and it is the container for collapsingToolbar
, would that also imply that collapsingToolbar
is null
, or perhaps in some invalid state? 🤔
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 question and my short answer here is; I am not sure. I didn't want to invest more time into going deeper than than. However, I think this question of yours about an invalid state makes a case about being in favor of the "fail fast" case. Otherwise, going the "fail silently" road, we might introduce invalid states that are going to be much harder to identify going forward. 🤷 🤔
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 the only "fail silently" part about this is the addition of the appBarLayout != null
, which is a quick way to make the compile error go away, but not necessarily a "valid" thing to do. To me, I think of @Nullable
as failing at compile time, and @NonNull
as failing at runtime, and thus @Nullable
is failing faster, since it fails before even reaching users.
I think the conflation here is that additional changes are being introduced, alongside the application of the @Nullable
annotation, to satisfy the compiler (or make the red things go away in the IDE 😄). But, in many cases, significant effort is required to do this correctly, as well as a deeper understanding of the implementation in question.
This is another reason I think these kinds of changes should be postponed to a different PR. The effort to review these may vary greatly from case to case, and it would be ideal to have input from folks more familiar with a given implementation (especially regarding the correct handing of the @Nullable
cases).
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.
To me, I think of @nullable as failing at compile time, and @nonnull as failing at runtime, and thus @nullable is failing faster, since it fails before even reaching users.
Hmmm... yea @mkevins , I get that but let me provide an extra thing that @Nullable
is to me. In addition to it failing at compile time, and thus failing "faster", if used incorrectly, it is now actually failing "silently" as now we are introducing a whole application flow and could never be valid, or an application state that is ignored, which, in its turn will propagate this downstream everywhere else. Plus, doing that, we are now giving the wrong example for others to follow.
Thus, to me, using @NonNull
is already falling "faster" vs using @Nullable
as I consider that you could never fail with that anyway, you could only "fail" during compile time, but I personally don't consider that failing, I only consider failing a runtime thing. 🤔
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.
It seems I should clarify what I mean by @Nullable
failing. I don't mean that it is failing in the sense that we add dead code unnecessarily. I mean that the build will fail, because the compiler will not let us dereference a pointer to memory that is annotated this way.
I only consider failing a runtime thing
To me, I have a strong preference that as many failures (or errors, etc.) are surfaced at compile time as possible. The preference of where a "failure" should be surfaced is: [runtime] < [compile time] < [lint].
if used incorrectly, it is now actually failing "silently" as now we are introducing a whole application flow and could never be valid, or an application state that is ignored, which, in its turn will propagate this downstream everywhere else. Plus, doing that, we are now giving the wrong example for others to follow.
I don't think having wrongly applied @Nullable
annotations would lead to an application state which is ignored or invalid, but rather that it may make the compile complain until non-existent cases are handled (i.e. the compile will force the writing of dead code). I'm also not worried for the propagation of this because we have things like !!
and Object.requireNotNull
(in Kotlin and Java, respectively), which give us the power to decide how far along the codepaths we want to provide null-safety for an uncertain nullness. We do not have an equivalent mechanism for @NonNull
annotations. Once annotated as such, the NPE will occur anywhere this wrong assumption leads. I.e., user crashes that are trickier to track down, because the originally incorrect annotation is not the site of the crash.
Another point to consider is that "propagation" is going to happen with either of the annotations. For example, annotating a return type as @Nullable
requires any instances, fields, other return types, etc. that consume such a method be annotated similarly, while annotating a parameter with @NonNull
similarly requires at the call site that any arguments passed are non-null. In either case, in order for the code to remain safe and valid, the developer would need to provide some handling of the potentially null case before usage, with the choices being primarily conditionally / gracefully failing, or asserting and crashing. It is, of course, easier to just crash, but users tend not to like that 😅 . But handling these cases gracefully requires an understanding of the domain - a scope far to large to handle in bulk, imo.
public boolean onQueryTextChange(@NonNull String newText) { | ||
mViewModel.setSearchQuery(newText); |
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.
Similar to the comment about afterTextChanged
, I wonder whether this is safe to do? I'm especially curious because the query != null
ternary guard falls back to an empty string 🤔 - if this has survived a decade in production, do we risk introducing a "fail-fast crash" to replace the empty string fallback? I.e. is the existing implementation already more robust?
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.
Yea, I know, me looking at the change (05f5add) that introduced this didn't give me enough evidence of its need, thus me being more bold on that, and again me basing this change on all other usages of it across the whole codebase (Java and Kotlin included, and especially Kotlin, where it is solely used as non-null, without the extra ?
). 🤞
Having said that, and as per all the discussion we are having on this, "fail fast" vs "being safe", I wonder what is best, to keep having something there that might never be true, thus us needed to create a fallback, but then risking having every other client code that using this onQueryTextChange(...)
think it being potentially falsely @Nullable
, or risk a potential very low chance of a quickly revertible crash, especially since testing verified that this is indeed an empty string (""
) instead of it being null
when the search result is empty... 🤷
PS: I personally think that we can just quickly smoke test this and all those cases and be better of having a @NonNull
annotation, then spread that across the codebase, than have a @Nullable
one and spread that. No matter, we should either should one or the other, I wouldn't be comfortable having a mix of those and thus ending up having an inconsistent signature for other to replicate inconsistently as well. Wdyt? 🤔
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.
The evidence you mention is compelling, imo, i.e. that this is already being treated as @NonNull
throughout the codebase and without issue. So, I agree with your rationale for consistently annotating it as such. That said, like the others, I think this could be on another PR with dedicated crash monitoring. My biggest concern is that we introduce a new crash and negatively affect the user experience. If we are very confident we can avoid that (from the recognition of wide usage already, along with scrutiny during beta testing), I think it's ok to annotate as @NonNull
.
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 agree on having a separate PR @mkevins ! 👍
@@ -279,6 +281,7 @@ public void restoreState(Parcelable state, ClassLoader loader) { | |||
} | |||
} | |||
|
|||
@NonNull |
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.
Since newInstance
is analyzed and confirmed to be @NotNull
, I wonder if the annotations should be applied there as well. Wdyt?
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.
Ah yea, I can do that, nice catch, but then again, I wonder how deep I should go with that? 🤔
FYI: I have arbitrarily (usually) stopped at the first level, when working on this PR, with the plan being that this annotations at this level will then help with other follow-up PRs where will be focusing on a case-by-case scenario, like adding missing annotations to ReaderPhotoViewerFragment.java
or any other strategy we chose to follow going forward.
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 wonder how deep I should go with that?
I had assumed (perhaps wrongly so 😅), that you already did analyze the return value of this static factory method. It reads like a constructor, but unfortunately, unlike a constructor, does not have any guarantees about the return type being @NonNull
. But, a quick look at the method suggests so, and I thought that is how you came to annotate this as such.
But reflecting on this now, I see that it is coming from the other way around, that you annotated this return type as @NotNull
, to match the super type signature. I think this is fine, since we already know it was working (and we also know it can't be null). When I reviewed this, I checked the return type of the newInstance
call to verify it was appropriately @NonNull
, so in this case it would also be good to annotate that as well, but feel free to defer to another PR for that.
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.
Yea @mkevins , these kind of PRs and work, unless being discussed and agreed ahead of time, it is hard to make assumptions on how one would approach such work, or how was something that is already completed have been built to completion and why. 🤷
When I reviewed this, I checked the return type of the newInstance call to verify it was appropriately @nonnull, so in this case it would also be good to annotate that as well, but feel free to defer to another PR for that.
As explained above, I will indeed prefer to defer that to another PR just to have this or anything other such super type signature method matching PR progress faster, both author and review wise. 👍
👋 @mkevins and thank you so much for the detailed comment and for sharing your thoughts and suggestions, this is very much appreciated! 🥇 💯 🙇
I was thinking about it too, and after some thinking, plus me usually being more on the "fail-fast" side, I decided to move forward with being more risky than staying on the safe side and having an unnecessary The reason why I made this decision is the fact that this change will not end-up being a one-of change for a specific method, but will then apply to every such usage across the whole codebase. Thus, if we end-up taking the safe side, you are risking writing unnecessary guard code going forward everywhere, people will start rightfully copy-pasting it, which would then propagate on other places too. My bet is that even if we end-up being wrong on a Us trying to avoid introducing any new crashed with this work might be efficient in terms of increasing the crash free rate of JP/WPAndroid, but, would not be the most efficient in terms of adding the most appropriate nullability annotation possible. I am thinking about it this way, us being on the safe side and thus making this the most efficient NPE project, we might as well add
I agree with this idea and I like it, having PRs that are Having said that I am still on the more risky side of creating such PRs, even if they have the potential of introducing crashes or bugs. Else, I would suggest we just ignore creating any PRs, that is, instead creating PRs that are adding a
Please let me respectfully disagree on that, I consider every refactor commit a safe commit and I would like to keep adding those to increase the consistency and coherence of our codebase when I see fit, that is, without the need for a separate PR, IMHO a separate commit should suffice. Thus, when I create such change, I usually separate them from other commits (something that admirably wasn't done on this other PR, for a different reason, mainly due to that fact that I wanted to avoid creating such extra, extremely safe, commits, example here, a separate commit renaming the
Yes, with having such commits, reverting could be applied on the commit level and not PR level. Having said that, I am not sure if you would prefer having one PR with one commit instead, such to keep the discussion specific to a single method that is wrongly annotated with The reason I didn't go for multiple PRs like that is because I fear that doing so will explore the scope of this project and it will take us a huge amount of time dealing with every singe such method (then field, etc). But, for sure we will be a bit more safe, I agree. I decide to go for a more batch approach, just to be a bit more efficient, but we can adjust accordingly... 🤷
I think I agree on (1.) + (2.), but I wouldn't go for (3.) as separate PRs, instead I would add such refactor commit as I go and have them be part of any of the major (1.) + (2.) PRs that we'll be working from now on (as explained above). Btw, why would you have (3.) as separate PRs, assuming that those changes are extra minor and safe, can you explain your reasoning for suggesting that again? 🤔 PS: I am used to add such extra Having discussed the above, would you like me to now close this PR, after replying to your other comments and then:
I also have another suggestion for you, for me to create separate PRs per Again, many thanks for opening this discussion and for sharing all your thoughts, much appreciated! 🙇 |
I understand your rationale, and I don't disagree, in principle, especially for cases where we are already treating the types as
I agree with this point, and this may be another way of looking at the split in PRs that I suggested - to separate the concerns. Changes that could (however unlikely we estimate) introduce a new crash should, imo, be reviewed / followed up on, etc. differently from those changes which cannot possibly introduce a new crash, and could only possibly improve the developer and user experience. Those in the latter category make for an easy PR review, since CI is the main testing step.
I think it might help if I clarify what I'm envisioning with regard to this. I see this effort as something that will be approached gradually, and what the lint rule will help to achieve after raising sufficient awareness of the underlying issue (that is, nullness ambiguity). To me, this ambiguity is costly, and addressing it is also costly:
As a matter of principle, I value the user experience above the developer experience, so avoiding crashes or undefined behavior is a high priority, imo. That said, I believe you have a valid point about the reduced risk of crashes when we've already been treating certain cases as I noticed that you mentioned the idea in a few places that there is a trade-off between "fail fast" and "fail silently", and that With this in mind, I still agree with your sentiments about inferring
I'm not sure what you "disagree" with, as I wasn't stating an opinion, but rather the opposite - I am unsure where these commits belong. But on thinking about it further, I think these commits can be separated in the same way as the others. If they won't cause runtime issues, or if they might. Fwiw, a quick look across all the "refactor" commits, so far this is the only one that could introduce runtime issues.
I think it's ok to batch, especially since you have very nicely organized the commits to make reverts easy.
You're right about 3, it can be split up by the same criteria and added to 1 and 2.
Maybe it'd be best to "convert" this PR into 1, and spin up another for 2? Wdyt?
I would suggest for this "1" PR that we add as many annotations as possible, including annotations which are not present on a super method signature (by inferring as you have already done), with the exception that if such annotations require you to change any other code (removing or adding a guard, etc.), those should be omitted. Essentially, the compiled output should remain unchanged by this PR.
Hm.. I'm not quite sure whether slicing the work from a different angle would help separate the cases between compile-time and runtime PRs 🤔 , but if you think it will help, I'd say go for it! I think that the "compile time" PR should be something where very little effort is required to review, and I'd expect the other changes (affecting runtime) to be more carefully scrutinized. How this breaks down along lines of override method usage, or between Activities / Fragments, etc. is not 💯 clear to me at the moment, so use your best judgement 😄 . |
👋 @mkevins !
I agree! 👍
I agree! 👍
Nicely done on the table! 💯
Totally, I am aligned with you on that, and I understand how me focusing on developer experience might make me seen as siding more on the developers side, more so than the users side. I want to take this opportunity to clarify that this is not it. Instead, the way I think about it, to avoid crashes does not always mean a better user experience. Sometime, it might mean that by avoiding crashes the user enters into an undefined behavior. This could take much more effort for the user to figure out and report, and then, it would might even more time and resources (accumulatively) for it to get prioritized and fixed, that is, comparing to fixing a NPE crash, which is reported instantly and usually doesn't require prioritzation.
👍
👍
👍
Ah, again, I think the confusion here was due to this "refactor" commit, which is actually not the same as the other such commits, thus, you were stating your opinion on that, while I was thinking that you were talking about all the other ones. 😭 Apologies for this "refactor" commit. I just couldn't figure out whether to use "analysis" or "refactor" there, from our discussion and the fact that this commit might end-up being harmful, I now seen it more clearly, I should have used the "analysis" wording there. That might have helped us avoiding having this discussion in the first place as I am now seeing we are both aligned on having "minor" refactor separate unharmful commits. This all was my bad. 😞
👍
👍
Let me start with (1.) and then have another quick individual PR for (2.) just for a specific Actually, let's do the vertical approach I suggested (one PR per Wdyt? 🤔
Yea, I like that approach too, seems the safer choice, to have a compiled output unchanged for the (1.) type of PRs. 👍 But, as suggested above, let me try to work vertically on those PRs (one PR per
Yea, I think it will, or at least make sure that we adhere to everything we have already discussed so far and see the results of that merging to
Using my best judgement it is then, wish us luck! 🤣 🍀 ⚖️ |
Thank you for your huge effort on all of this Petros!
I totally agree about this as well, so I think we are aligned, and our discussions have been productive toward illustrating the common facets of the "null problem".
Indeed, that commit in particular stands out as one of the "risky" ones, compared to the others. The other refactor commits seem to me to not even be capable of harm, whereas this one potentially could (though I think the risk is likely rather low). Thanks for separating them out! 👍 |
Thank for this constructive discussion we had @mkevins , now onwards and upwards we go, we will find you, we will kill you, you Mr. NPE! 🙇 ❤️ 🚀 |
Parent #18909
This PR adds any missing nullability annotation to all SDK override methods on
Activity
classes.Note that the nullability information wasn't available on all of the super method signatures and thus I had to make a call on making a method's parameter and/or its return value either
NonNull
orNullable
. Thus, and in order to make the best call possible, I researched each such method separately(*)
, used ChatGPT to increase my confidence, and based my call on existing usages of such methods.While doing so I also documented all those methods and grouped them per package, per library, see below:
Methods + Nullability
Methods + Nullability to Triple Check
(*)
: Nullability info not available on the super method signature and thus arbitrary added.(⚠️)
: Better to double (triple) check that this@NonNull
added won't cause us any trouble!Nullability Annotation List:
Lint Warnings Suppression List:
Refactor List:
To test:
Regression Notes
Potential unintended areas of impact
androidx.activity.ComponentActivity.java
like changes where theIntent intent
parameter got assigned a@NonNull
annotation, and thus, any safe call got removed, a null-pointer exception might occur if this nullability annotation is incorrectly set, that is, instead of using@Nullable
(see(*) + (⚠️)
above).What I did to test those areas of impact (or what existing automated tests I relied on)
To test
section above.What automated tests I added (or what prevented me from doing so)
PR submission checklist:
RELEASE-NOTES.txt
if necessary.UI Changes testing checklist: