From a86bd3f8305d01496edd4a37ac296de32cf20fcc Mon Sep 17 00:00:00 2001 From: franruiztech Date: Mon, 5 Oct 2026 10:20:23 +0200 Subject: [PATCH] fix(android): log missing generated setter at debug level, not warn ViewManagerPropertyUpdater looks up a "$$PropsSetter" class by reflection and falls back to reflection over the @ReactProp methods when it is not found. The annotation processor that generated those setters (ReactPropertyProcessor) was removed in #49811, so the fallback is now the normal path for every view manager and shadow node. Logging it at warn level prints one "Could not find generated setter for class ..." line per class on every app start, for a condition that is neither an error nor actionable. Lower it to debug. The lookup is kept, since a library can still ship a generated setter. --- .../uimanager/ViewManagerPropertyUpdater.kt | 4 +- .../ViewManagerPropertyUpdaterTest.kt | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ViewManagerPropertyUpdaterTest.kt diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt index 7a33df4ae121..0b18da7c1b84 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ViewManagerPropertyUpdater.kt @@ -135,7 +135,9 @@ public object ViewManagerPropertyUpdater { @Suppress("DEPRECATION", "UNCHECKED_CAST") return setterClass.newInstance() as T } catch (e: ClassNotFoundException) { - FLog.w(TAG, "Could not find generated setter for $cls") + // A generated setter is optional: the annotation processor that produced them was removed, + // so reaching the reflection-based fallback is the expected path, not a problem. + FLog.d(TAG, "Could not find generated setter for $cls") return null } catch (e: InstantiationException) { throw RuntimeException("Unable to instantiate methods getter for $clsName", e) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ViewManagerPropertyUpdaterTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ViewManagerPropertyUpdaterTest.kt new file mode 100644 index 000000000000..e9b8070ef233 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ViewManagerPropertyUpdaterTest.kt @@ -0,0 +1,63 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.uimanager + +import android.view.View +import com.facebook.common.logging.FLog +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.uimanager.annotations.ReactProp +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mockStatic +import org.mockito.Mockito.never +import org.mockito.kotlin.any +import org.robolectric.RobolectricTestRunner + +/** Tests for the setter lookup done by [ViewManagerPropertyUpdater]. */ +@RunWith(RobolectricTestRunner::class) +class ViewManagerPropertyUpdaterTest { + @Suppress("DEPRECATION") + private class ViewManagerWithoutGeneratedSetter : ViewManager>() { + override fun getName() = "ViewManagerWithoutGeneratedSetter" + + override fun createShadowNodeInstance(): ReactShadowNode<*> = + error("This method should not be executed as a part of this test") + + override fun getShadowNodeClass(): Class> = ReactShadowNode::class.java + + override fun createViewInstance(reactContext: ThemedReactContext): View = + error("This method should not be executed as a part of this test") + + override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? = + error("This method should not be executed as a part of this test") + + override fun updateExtraData(view: View, extraData: Any) = + error("This method should not be executed as a part of this test") + + @ReactProp(name = "someProp") fun setSomeProp(view: View?, value: Boolean) = Unit + } + + @Before + fun setUp() { + ReactNativeFeatureFlagsForTests.setUp() + // Setters are cached per class, so the lookup would be skipped if an earlier test ran it. + ViewManagerPropertyUpdater.clear() + } + + @Test + fun missingGeneratedSetterFallsBackToReflectionWithoutWarning() { + mockStatic(FLog::class.java).use { flog -> + val nativeProps = ViewManagerWithoutGeneratedSetter().nativeProps + + assertThat(nativeProps).containsKey("someProp") + flog.verify({ FLog.w(any(), any()) }, never()) + } + } +}