React-native: fontWeight behavior changed on Android between <0.60.0 and >= 0.60.0 - now high font weights can default to low ones

Created on 17 Jul 2019  路  23Comments  路  Source: facebook/react-native

Prior to 0.60.0 font weights that were not supported but were high would default to the nearest 'bold' font. Now they just default to a non-bold font.

React Native version:

Steps To Reproduce

1a. react-native init AwesomeProject (at time of writing defaults to version 0.60.3)
2a. Use this code in App.js


import React, {Fragment} from 'react';
import { Text, SafeAreaView } from 'react-native';

const App = () => {
  return (
      <SafeAreaView>
        <Text style={{fontWeight: '900'}}>FONT WEIGHT 900</Text>
        <Text style={{fontWeight: '800'}}>FONT WEIGHT 800</Text>
        <Text style={{fontWeight: '700'}}>FONT WEIGHT 700</Text>
        <Text style={{fontWeight: '600'}}>FONT WEIGHT 600</Text>
        <Text style={{fontWeight: '500'}}>FONT WEIGHT 500</Text>
        <Text style={{fontWeight: '400'}}>FONT WEIGHT 400</Text>
        <Text style={{fontWeight: '300'}}>FONT WEIGHT 300</Text>
        <Text style={{fontWeight: '200'}}>FONT WEIGHT 200</Text>
        <Text style={{fontWeight: '100'}}>FONT WEIGHT 100</Text>
      </SafeAreaView>
  );
};

export default App;

3a. Run the app in an emulator or on device
4a. Output: https://imgur.com/VEpgKzC


react-native info

info Fetching system and libraries information...
System:
OS: macOS 10.14.5
CPU: (8) x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
Memory: 1.28 GB / 16.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 12.2.0 - /usr/local/bin/node
Yarn: 1.15.2 - /usr/local/bin/yarn
npm: 6.10.0 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 12.2, macOS 10.14, tvOS 12.2, watchOS 5.2
Android SDK:
API Levels: 23, 28
Build Tools: 28.0.3
System Images: android-28 | Intel x86 Atom_64, android-28 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom
IDEs:
Android Studio: 3.4 AI-183.6156.11.34.5692245
Xcode: 10.2.1/10E1001 - /usr/bin/xcodebuild
npmPackages:
react: 16.8.6 => 16.8.6
react-native: 0.60.3 => 0.60.3
npmGlobalPackages:
create-react-native-app: 1.0.0
react-native-cli: 2.0.1

THEN

1b. react-native init AwesomeProject2 --version 0.59.9
2b. Use code from above in App.js
3b. Run the app in an emulator or on device
4b. Output: https://imgur.com/ZW9hZGU


react-native info

info
React Native Environment Info:
System:
OS: macOS 10.14.5
CPU: (8) x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
Memory: 1.34 GB / 16.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 12.2.0 - /usr/local/bin/node
Yarn: 1.15.2 - /usr/local/bin/yarn
npm: 6.10.0 - /usr/local/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
SDKs:
iOS SDK:
Platforms: iOS 12.2, macOS 10.14, tvOS 12.2, watchOS 5.2
Android SDK:
API Levels: 23, 28
Build Tools: 28.0.3
System Images: android-28 | Intel x86 Atom_64, android-28 | Google APIs Intel x86 Atom, android-28 | Google Play Intel x86 Atom
IDEs:
Android Studio: 3.4 AI-183.6156.11.34.5692245
Xcode: 10.2.1/10E1001 - /usr/bin/xcodebuild
npmPackages:
react: 16.8.3 => 16.8.3
react-native: 0.59.9 => 0.59.9
npmGlobalPackages:
create-react-native-app: 1.0.0
react-native-cli: 2.0.1

Describe what you expected to happen:

When an unsupported font weight is higher than a supported, non standard font weight, the default should be picked as the fatter one not the skinny one.

Snack, code example, or link to a repository:

Bug Android

Most helpful comment

Same issue. RN 0.62.0 not fixed.

All 23 comments

It looks like this originates from this commit https://github.com/facebook/react-native/commit/3915c0fa6178fbb8aa9b8bd54881cfdbde8fde70#diff-d4141c42bc6789637b6fdc7754ec1dd9

In particular, the commit message says:

This change might break texts where fontWeight use improperly, because this PR removes conversion of values above 500 to BOLD and below 500 to normal.

I don't really know if this is an issue or not then - this is the code i am using to 'fix' the fonts in my code base, this does not apply if you are using custom fonts and it is not using the roboto default which does not seem to support very many font weights out of the box.

iOS:
ios

Android:
android

const oldRender = Text.render;

const settings = [
// we use this empty object for when there is no weight specified
  {},
  {
    fontFamily: 'sans-serif-thin',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif-light',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif-medium',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif',
    fontWeight: 'bold',
  }, {
    fontFamily: 'sans-serif-medium',
    fontWeight: 'bold',
  },
];

const defaultIndex = 0;

Text.render = (...args) => {
  const origin = oldRender.call(this, ...args);

  if (Platform.OS === 'android') {
    let useIndex = defaultIndex;

    if (typeof origin.props.style !== 'undefined' && typeof origin.props.style.fontWeight !== 'undefined') {
      const { fontWeight } = origin.props.style;

      if (fontWeight === '100' || fontWeight === '200' || fontWeight === '300') {
        useIndex = 1;
      } else if (fontWeight === '400') {
        useIndex = 2;
      } else if (fontWeight === '500' || fontWeight === 'normal') {
        useIndex = 3;
      } else if (fontWeight === '600') {
        useIndex = 4;
      } else if (fontWeight === '700' || fontWeight === 'bold') {
        useIndex = 5;
      } else if (fontWeight === '800' || fontWeight === '900') {
        useIndex = 6;
      }
    }

    return React.cloneElement(origin, {
      style: [settings[defaultIndex], Platform.OS === 'android' ? { fontFamily: 'Roboto' } : {}, origin.props.style, settings[useIndex]],
    });
  }

  return origin;
};

Shouldn't this issue be open? It's definitely not desired behaviour if a fontWeight of '900' defaults to Roboto Regular on Android. This essentially means I have to either use the hack mentioned by @iamacup or use a custom font that includes all the font weights I need.

I don't really know if this is an issue or not then - this is the code i am using to 'fix' the fonts in my code base, this does not apply if you are using custom fonts and it is not using the roboto default which does not seem to support very many font weights out of the box.

iOS:
ios

Android:
android

const oldRender = Text.render;

const settings = [{
    fontFamily: 'sans-serif-thin',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif-light',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif-medium',
    fontWeight: 'normal',
  }, {
    fontFamily: 'sans-serif',
    fontWeight: 'bold',
  }, {
    fontFamily: 'sans-serif-medium',
    fontWeight: 'bold',
  }
];

const defaultIndex = 2;

Text.render = (...args) => {
  const origin = oldRender.call(this, ...args);

  if (Platform.OS === 'android') {
    let useIndex = defaultIndex;

    if (typeof origin.props.style !== 'undefined' && typeof origin.props.style.fontWeight !== 'undefined') {
      const { fontWeight } = origin.props.style;

      if (fontWeight === '100' || fontWeight === '200' || fontWeight === '300') {
        useIndex = 0;
      } else if (fontWeight === '400') {
        useIndex = 1;
      } else if (fontWeight === '500' || fontWeight === 'normal') {
        useIndex = 2;
      } else if (fontWeight === '600') {
        useIndex = 3;
      } else if (fontWeight === '700' || fontWeight === 'bold') {
        useIndex = 4;
      } else if (fontWeight === '800' || fontWeight === '900') {
        useIndex = 5;
      }
    }

    return React.cloneElement(origin, {
      style: [settings[defaultIndex], origin.props.style, settings[useIndex]],
    });
  }

  return origin;
};

@iamacup
Could you explain a bit more about your solution?

+1

Shouldn't this issue be open? It's definitely not desired behaviour if a fontWeight of '900' defaults to Roboto Regular on Android. This essentially means I have to either use the hack mentioned by @iamacup or use a custom font that includes all the font weights I need.

@iamacup Could you re-open this issue?

Done :)

@jorgemasta His solution is simulating the previous font weight rendering on android. I ended up using his solution to do something similar in our code base for our custom text component.

// @flow
import * as React from 'react';
import { StyleSheet, Text as RNText, Platform } from 'react-native';

type Props = {|
    ...
    style: {} | number | $ReadOnlyArray<?{} | ?number | null>,
    title: ?boolean,
    medium: ?boolean,
    italic?: ?boolean,
|};

const styles = StyleSheet.create({
    ...
    medium: { fontWeight: '500' },
    androidMedium: {
        fontFamily: 'Roboto-Medium',
        fontWeight: '500',
    },
    roboto100: {
        fontFamily: 'Roboto-Thin',
    },
    roboto100Italic: {
        fontFamily: 'Roboto-ThinItalic',
    },
    roboto300: {
        fontFamily: 'Roboto-Light',
    },
    roboto300Italic: {
        fontFamily: 'Roboto-LightItalic',
    },
    roboto400: {
        fontFamily: 'Roboto-Regular',
    },
    roboto400Italic: {
        fontFamily: 'Roboto-Italic',
    },
    roboto500: {
        fontFamily: 'Roboto-Medium',
    },
    roboto500Italic: {
        fontFamily: 'Roboto-MediumItalic',
    },
    roboto700: {
        fontFamily: 'Roboto-Bold',
    },
    roboto700Italic: {
        fontFamily: 'Roboto-BoldItalic',
    },
    roboto900: {
        fontFamily: 'Roboto-Black',
    },
    roboto900Italic: {
        fontFamily: 'Roboto-BlackItalic',
    },
});

...

const androidGetFontWeightStyles = ({
    title,
    medium,
    italic,
    style,
}: Props): {} => {
    if (title || medium) {
        return styles.androidMedium;
    }

    // $FlowFixMe
    if (style && style.fontWeight !== undefined) {
        // $FlowFixMe
        const isItalic = italic || style.fontStyle === 'italic';
        const fontFamilyStyle =
            styles[`roboto${style.fontWeight}${isItalic ? 'Italic' : ''}`];
        if (fontFamilyStyle) {
            return fontFamilyStyle;
        }
    }

    return {};
};

const getFontWeightStyles = (props: Props): {} => {
    if (Platform.OS === 'android') {
        return androidGetFontWeightStyles(props);
    }

    if (props.title || props.medium) {
        return styles.medium;
    }

    return {};
};

export default function Text(props: Props) {
    ...

    const textStyles = [
        ...
        getFontWeightStyles(props),
    ];

    ...

    return (
        <RNText style={textStyles}>
            ...
        </RNText>
    );
}

...

I introduced the change to add support for other font weights natively and fix parity with iOS. Custom font weight support wasn't available when RN emerged, but added later in SDK 25 or 26.

I wrote an npm package to handle mapping of custom fonts (eg. Roboto-Light, Roboto-Black) to fontWeights heavily modified from jacobcabantomski-ct's code.

react-native-font-weight (alpha)

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

?

Has it been fixed in RN 0.62.0?

Still an issue in 0.62.0

Same issue. RN 0.62.0 not fixed.

I quote https://github.com/facebook/react-native/pull/25341

I found that on Android we only support 2 fontWeight options, either normal or bold, even developer can set any numeric value. But iOS supports all possible numeric values. This PR tries to add support for all possible numeric values on Android, even if it's supported only on Android P(28) and above.

I will write a Pull Request to re-enable to old behaviour on Android API < 28 in ReactBaseTextShadowNode

  @ReactProp(name = ViewProps.FONT_WEIGHT)
  public void setFontWeight(@Nullable String fontWeightString) {
    int fontWeightNumeric =
        fontWeightString != null ? parseNumericFontWeight(fontWeightString) : -1;
    int fontWeight = UNSET;
    if (fontWeightNumeric >= 500 || "bold".equals(fontWeightString)) {
      fontWeight = Typeface.BOLD;
    } else if ("normal".equals(fontWeightString)
        || (fontWeightNumeric != -1 && fontWeightNumeric < 500)) {
      fontWeight = Typeface.NORMAL;
    }

as you can see in https://github.com/facebook/react-native/pull/25341 https://github.com/react-navigation/react-navigation/pull/7720 will have to update their logic to use this changes...

Additionally this is the result of https://github.com/facebook/react-native/pull/25341 with API 28

I am little bit confused, but I will write this pull request as I invested considerable amount of time reviewing all this discussions.

@iamacup

I don't understand your sentence below. Do you believe that I can present a pull request with such a change in behaviour?

Do you expect ReactNative to solve some of the limitations imposed by the Android Platform? Or you want me to do the change I described in https://github.com/facebook/react-native/issues/25696#issuecomment-642537998

Describe what you expected to happen:
When an unsupported font weight is higher than a supported, non standard font weight, the default should be picked as the fatter one not the skinny one.

I think you should close this issue in favour of https://github.com/facebook/react-native/issues/28854. Your requirement is not clear.

I quote issue https://github.com/facebook/react-native/issues/28854

Expected Results
font weight values should change font effect in android sdk 28 and above.
Expected result should look like this

I quote https://github.com/facebook/react-native/issues/26193#issuecomment-525028689

It's known issue due to Android limitations. Only way to use custom font weights is to create a custom font.
Android added support changing font weights programmatically, but won't work on older versions.

My pull request should fix this behaviour for Android Pie API 28.

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

Not fixed

Still not fixed

React native is great but its things like this that just makes it feel unfinished. 2 years has passed and it still isn't fixed. The problem is that it worked with older RN versions and now it's broken.

Why would developer depend or specify non-existing font weight. I would rather offer consistent UX by specifying font weights explicitly

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Findiglay picture Findiglay  路  239Comments

PhillippOhlandt picture PhillippOhlandt  路  321Comments

srijak picture srijak  路  134Comments

cpojer picture cpojer  路  196Comments

michalraska picture michalraska  路  223Comments