From 786a267d2f15566dfd18e6fa0aeab32c44c4c003 Mon Sep 17 00:00:00 2001 From: Neha Gupta Date: Mon, 16 Feb 2026 08:43:54 -0800 Subject: [PATCH] Add @Nullsafe(Nullsafe.Mode.LOCAL) to clean interfaces and classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Add Nullsafe(Nullsafe.Mode.LOCAL) annotation to 10 un-annotated Java interfaces and classes across imagepipeline-base, drawee, and drawee-backends modules. These files require no NULLSAFE_FIXME suppressions — all null contracts are already correct. This is a compile-time annotation for Infer static analysis with zero runtime effect. Files: QualityInfo, HasImageMetadata, ImageInfo (imagepipeline-base); DraweeHierarchy, SettableDraweeHierarchy, DraweeController, SimpleDraweeControllerBuilder, ControllerViewportVisibilityListener, GenericDraweeHierarchy (drawee); ImageOriginListener (drawee-backends). Also adds infer-annotations dep to drawee/interfaces BUCK. **Context** What is Nullsafe? Nullsafe is a Meta annotation that enables static null checking via https://fbinfer.com/, Meta's static analysis tool. When you annotate a class with Nullsafe, Infer analyzes the code at compile time (not runtime) and flags any place where a null value could sneak in and cause a crash. Without Nullsafe, Infer ignores null issues in that class. With it, every field, parameter, and return value is assumed non-null by default unless explicitly marked Nullable. What does Nullsafe.Mode.LOCAL mean? There are different strictness levels: ┌────────┬─────────────────────────────────────────────────────────────────────────────────────────────┐ │ Mode │ What it checks │ ├────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ LOCAL │ Only checks code within this class. Trusts that other classes return correct values. │ ├────────┼─────────────────────────────────────────────────────────────────────────────────────────────┤ │ STRICT │ Checks this class AND requires all dependencies to also be Nullsafe. Much harder to adopt. │ └────────┴─────────────────────────────────────────────────────────────────────────────────────────────┘ LOCAL is the safe starting point — it catches bugs in the annotated class without requiring the entire dependency tree to be annotated first. **Java-Kotlin Interop and Platform Types** When Kotlin calls unannotated Java code, Kotlin treats return types as platform types (shown as Type!). This means Kotlin doesn't know if it's nullable or not, and lets you treat it either way: // Java: public String getName() { ... } (no annotation) // Kotlin sees: String! (platform type — could be null or non-null) val name: String = controller.name // Kotlin allows this (treats as non-null) val name: String? = controller.name // Kotlin also allows this (treats as nullable) Once we add Nullsafe to the Java class, unannotated types become strictly non-null, and Nullable-annotated types become strictly nullable: Safety These changes are compile-time only. Nullsafe and Nullable are annotation-only — they produce zero bytecode changes, zero runtime overhead, and don't change any actual behavior. They just make the compiler and static analysis catch null bugs earlier. Reviewed By: cortinico Differential Revision: D93118745 --- .../com/facebook/react/views/image/ReactImageView.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt index ecbcc9b3083b..3378e2886ec4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageView.kt @@ -414,14 +414,16 @@ public class ReactImageView( // We store this in a local variable as it's coming from super.getHierarchy() val hierarchy = this.hierarchy - hierarchy.actualImageScaleType = scaleType + hierarchy.setActualImageScaleType(scaleType) - if (defaultImageDrawable != null) { - hierarchy.setPlaceholderImage(defaultImageDrawable, scaleType) + val defaultDrawable = defaultImageDrawable + if (defaultDrawable != null) { + hierarchy.setPlaceholderImage(defaultDrawable, scaleType) } - if (loadingImageDrawable != null) { - hierarchy.setPlaceholderImage(loadingImageDrawable, ScalingUtils.ScaleType.CENTER) + val loadingDrawable = loadingImageDrawable + if (loadingDrawable != null) { + hierarchy.setPlaceholderImage(loadingDrawable, ScalingUtils.ScaleType.CENTER) } val roundingParams = hierarchy.roundingParams