[Android] Fix ReactEditText Typeface Inconsistencies for Hint and Typing with Custom Fonts
Summary:
This PR ensures that the typeface applied to ReactEditText is also correctly applied to its hint text and fixes an issue where the custom font was not consistently applied when typing.
Fixes: https://github.com/facebook/react-native/issues/50137
Issues Fixed:
-
Hint text typeface inconsistency: Previously, the hint text did not inherit the custom typeface, leading to visual inconsistencies. A new
MetricAffectingSpanis introduced to apply the correct typeface to the hint. -
Font not applied correctly when typing: In some cases, especially on devices where the system font is changed (e.g., Samsung devices allowing users to set a custom system font), the typeface would not be applied correctly when entering text. This is fixed by forcing the typeface to be set in
onDraw.
Changelog:
[ANDROID] [FIXED] - Ensure ReactEditText hint text inherits the correct typeface and fix an issue where custom fonts were not applied correctly when typing.
Test Plan:
- Set a custom font on
ReactEditTextand verify that the hint text also adopts the correct typeface. - Type using a custom font and ensure the typeface remains consistent.
- Test on Samsung devices where the system font can be changed, ensuring that
ReactEditTextcorrectly maintains the specified font instead of falling back to the system default. - Verify behavior on various Android versions and devices.
Hi @lorenc-tomasz!
Thank you for your pull request and welcome to our community.
Action Required
In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.
Process
In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.
Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.
If you have received this in error or have any questions, please contact us at [email protected]. Thanks!
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!
We also measure EditText content using TextLayoutManager, against the Spannable created from text content, which ends up setting the final dimensions of the EditText. If we measure a Spannable that is inconsistent with the version we apply to the EditText, we could truncate content.
I think we would need to make sure that the Spannable being measured, before hitting the EditText, has consistent Typeface. Where is hint we are setting coming from, and how does it get measured by TextLayoutManager in normal path?
@NickGerleman Thanks for feedback :) Still learning how it works there :) I have found simpler fix for the issue.
Bump the PR
@Hardanish-Singh @NickGerleman @cortinico Updated to Koltin.
Updated with recent main.
Updated with recent main branch.
Updated with recent main branch
Hi @Hardanish-Singh :)
could you please look into this PR again, please? I have updated the PR to use Kotlin and to up to date with recent main branch.
Kind regards, Tomasz
We also measure
EditTextcontent usingTextLayoutManager, against theSpannablecreated from text content, which ends up setting the final dimensions of theEditText. If we measure aSpannablethat is inconsistent with the version we apply to theEditText, we could truncate content.I think we would need to make sure that the
Spannablebeing measured, before hitting theEditText, has consistent Typeface. Where is hint we are setting coming from, and how does it get measured byTextLayoutManagerin normal path?
I took a look back at this, and as of recent, we do have code, so that the placeholder we use for measurement, inherits the base attributes set https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputShadowNode.cpp#L225
Is this only for when the user customizes the system font?
The thing I mentioned above, I don't think that when we measure the text, on the Fabric side (see https://github.com/facebook/react-native/blob/e3047db0dc9caf06075554cc7abc0beb4f361aba/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManager.kt#L464 ) we ever consider this as being changed.
I am wondering, if TextInput may read some attribute to apply a font, that isn't considered during measurement?
When we forcibly update the typeface, is it overriding this default font? Or, does the logic for creating typeface resolve it, using the context?
@NickGerleman It seems that fix stopped working with 0.80.0. I was trying to find more durable and proper fix in C++ part but without luck.
You can try to reproduce the issue: https://github.com/lorenc-tomasz/react-native-text-input-placeholder-font-issue-reproducer/tree/android-custom-font-issue-0.80.0
I close this PR unless I will be able to find other fix. At the moment I have two working workaround with 0.80.0. One is to set autoCapitalize={'none'}. It seems it forces placeholder to rerender.
And second one using setNativeProps e.g.
const setRef = useCallback(
(node: TextInput | null) => {
if (node) {
inputRef.current = node;
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
const mutableRef = ref as React.MutableRefObject<TextInput | null>;
mutableRef.current = node;
}
// Workaround for Android to apply style before setting placeholder and before setting the styles by component
// to apply font family correctly
// TODO: Remove this workaround when RN fixes it: https://github.com/facebook/react-native/issues/50137
if (isAndroid) {
const flattenedStyle = StyleSheet.flatten([Typography.FONT_REGULAR, props.style ?? {}]);
const onlyFontStyle = Object.fromEntries(
Object.entries(flattenedStyle).filter(([key]) => fontStyleProps.includes(key)),
);
node.setNativeProps({
style: onlyFontStyle,
});
}
}
},
[ref],
);
Both workarounds available in above repo. Maybe you will figure it out.