Thursday, March 22, 2018

Expo Icon Fonts with React Native and React Native Elements!

I was trying to use the Avatar and Icon objects from react-native-elements I kept getting the following error:
fontFamily 'MaterialIcons' is not a system font and has not been loaded through Expo.Font.loadAsync.

- If you intended to use a system font, make sure you typed the name correctly and that it is supported by your device operating system.

- If this is a custom font, be sure to load it with Expo.Font.loadAsync.

It was driving me nuts.   Googling for the answer just brought up snippets of information about what to do.   So finally I pieced together the parts to get it working.

You have to load the fonts before they are used.   It seems that if you ever blow away your node_modules and then do npm install again, you lose the built in loading.  So you have to do it manually.  Here is how!

I made sure the @expo/vector icons are loaded:

npm install --save @expo/vector-icons


Then I changed the App to load them directly:

import React from 'react';
import { View } from 'react-native';
import { Avatar } from 'react-native-elements';
import { AppLoading, Font } from 'expo';

import FontAwesome  
from './node_modules/@expo/vector-icons/fonts/FontAwesome.ttf';
import MaterialIcons  
from './node_modules/@expo/vector-icons/fonts/MaterialIcons.ttf';
export default class App extends React.Component {
state = {
fontLoaded: false
};

async componentWillMount() {
try {
await Font.loadAsync({
FontAwesome,
MaterialIcons
});
this.setState({ fontLoaded: true });
} catch (error) {
console.log('error loading icon fonts', error);
}
}
render() {
if (!this.state.fontLoaded) {
return <AppLoading />;
}

return (
<View>
<Text>My App</Text>
<Avatar
small
rounded
icon={{ name: 'add' }}
/>
</View>
);
}
}

So now the fonts load before the app is shown.   While they are loading, the AppLoading continues to render the loading screen before showing any of the app.   The fonts get loaded, then the state is set so the AppLoading component no longer renders and it continues to your app.

But why throw all that into the main App.js?    It's messy.   So I made an AppFontLoader utility that looks like this:


import React from 'react';
import { AppLoading, Font } from 'expo';

import FontAwesome 
from '../../node_modules/@expo/vector-icons/fonts/FontAwesome.ttf';
import MaterialIcons  
from '../../node_modules/@expo/vector-icons/fonts/MaterialIcons.ttf';

class AppFontLoader extends React.Component {
state = {
fontLoaded: false
};

async componentWillMount() {
try {
await Font.loadAsync({
FontAwesome,
MaterialIcons
});
this.setState({ fontLoaded: true });
} catch (error) {
console.log('error loading icon fonts', error);
}
}

render() {
if (!this.state.fontLoaded) {
return <AppLoading />;
}

return this.props.children;
}
}

export { AppFontLoader };

Now the App.js gets simplified!

import React from 'react';
import { View } from 'react-native';
import { Avatar } from 'react-native-elements';
import { AppFontLoader } from './src/utils';
export default class App extends React.Component { render() {
return ( 
<AppFontLoader>
 <View>
<Text>My App</Text>
<Avatar
small
rounded
icon={{ name: 'add' }}
/>
</View>
</AppFontLoader>
);
}
}

I hope this helps you.   It shouldn't take 4 hours to figure this out!

11 comments:

  1. hey there. Thanks for sharing.
    I am wondering, is it the right solution to fix? i can run the app without any problem in ios expo but android is giving this error? is it something to do with android itself?

    ReplyDelete
  2. I don't have experience with IOS yet but many cases seem to cause this error (at least in Android). This code ensures the fonts get loaded before you try and use them.

    ReplyDelete
  3. Hello, I've used this method but ran into some issues I've got the following

    this.setState({fontLoaded:true});
    console.log('fonts are now loaded');
    } catch (error) {
    console.log(error);
    }

    Console is returning fonts are now loaded, no errors. Just stuck on white AppLoading screen. Any ideas?

    ReplyDelete
    Replies
    1. From you code... return this.props.children;

      Replace 'this.props.children;' with the main router in my case ; and all is working!!!

      Thank you!

      Delete
  4. It worked for me! Thanks a lot! I dont have much experience in react native and you solution saved my life

    ReplyDelete
  5. awesome thanks I was dying because of it.

    ReplyDelete
  6. thanks.. worked
    for me

    ReplyDelete
  7. Nice, thanks for this. This error was killing me

    ReplyDelete
  8. I have copied the code completley but when I use the component I am getting this error: Export 'AppFontLoader' is not defined.

    ReplyDelete
  9. My problem was in SimpleLineIcons. I added this code and it worked.

    await Expo.Font.loadAsync({
    SimpleLineIcons: require("native-base/Fonts/SimpleLineIcons.ttf")
    });

    ReplyDelete
  10. You saved me ! Thanks !

    ReplyDelete