Build responsive React Native views for any device and support orientation change

Image 1: The multiple screen sizes that a responsive design should cover

Responsive UI is definitely an important part of web and mobile development. When I started working with React Native more than a year ago, I discovered the hard way that there is no functionality out of the box to support responsiveness.

The goal of this article is to show a complete solution that scales RN UI to all sizes and types of screens (phones, tablets etc) and also supports UI scaling even when the app changes orientation.

How React Native works and what are the problems

React Native style properties accept either percentage or independent pixel (dp) values.

Percentage

Percentage is what we know from “normal” web development. The problem with it, is that not all RN style properties accept it. In example: margin, border-width, border-radius and many properties do not accept it as value.

That being said, there is no option for a developer to make their app responsive by coding everything in percentage…

Independent pixels

Independent pixels (dp) on the other hand, are not the classic screen pixels (px) that we become accustomed to as web developers. They mathematically connect to screen pixels and the screen’s scale factor through the following equation:

px = dp * scaleFactor

DP can not be used for responsive UI development as someone might think at this point. That is because scaleFactor actually depends on screen’s pixel density, meaning the number of pixels per inch (ppi). What RN can do though, is that it can scale dp values to screen of different sizes only if they have the same number of ppi. But if you think of Android phones — there are thousands out there and most of them have screens with different ppi even if they come from the same manufacturer!

For more info regarding screens and UI factors, you can have a look at Android’s guide for pixel densities here, Android’s screen compatibility overview and paintcodeapp’s guide for iPhone resolutions.

Let’s come up with a solution — let’s introduce package react-native-responsive-screen

The idea is really simple! Let’s emulate ourselves the percentage effect, and provide the “correct” dp value for every different screen dynamically. We coded that small and easy solution into a package called react-native-responsive-screen.`

UI responsiveness

In order to create responsive UIs, you need to import and use these 2 methods called widthPercentageToDP and heightPercentageToDP. The names are a bit big but they try to be indicative. That being said, both methods accept a string like percentage ('30%') as an argument and return the percentage of the screen’s actual width or height respectively in dp.

Let’s see this with an example. Samsung A5 2017 Android phone, has a width of 360 dp (this is without taking the scale factor into account); so if we code the following:

<View style={{width: widthPercentageToDP('53%')}} />

it will be translated to:

<View style={{width: 190.8} />

because 53% * 360 dp = (53/100) * 360 dp = 190.8 dp. So if you include these 2 methods within your style process, they will automatically find the correct dp values for every single device. And that happens in a performant way; the package makes sure to calculate screen’s width and height once when the app is initialized and every time the methods are used it simply calls these values to make the calculation instead of identifying them again.

Let’s see a 2nd more detailed example:

import React from 'react';
import { StyleSheet, Text, View, Dimensions } from 'react-native';
import {widthPercentageToDP as wp, heightPercentageToDP as hp} from 'react-native-responsive-screen';

export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<View style={styles.responsiveBox}>
<Text style={styles.text}>This box is always of 84.5% width and 17% height.</Text>
<Text style={styles.text}>Test it by running this example repo in phones/
emulators with screens of various dimensions and pixel per inch (ppi).</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'gray',
alignItems: 'center',
justifyContent: 'center',
},
responsiveBox: {
width: wp('84.5%'),
height: hp('17%'),
borderWidth: 2,
borderColor: 'orange',
flexDirection: 'column',
justifyContent: 'space-around'
},
text: {
color: 'white'
}
});

What happens here is that we create a simple screen with some text and a view wrapper around it. We code the wrapper’s height to always be the 70% of the screen’s width and the 80% of the screen’s height. Finally we used smaller aliases for the methods’ big names. Check the cross screen result below:

Image 2: Example of screen responsiveness across Android phones and tablets of different screen density

Let’s see a 3rd example of a real production Android app:

Images 3, 4: Profile scene of Math Warriors Android game

Guidelines on how to use these methods

  1. After the package has installed and when the application loads, it detects the screen’s width and height. I.e. for Samsung A5 2017 phone, it detects width: 360DP and height: 640DP (these are the values without taking into account the device's scale factor).
  2. Methods widthPercentageToDP and heightPercentageToDP can be used for any style (CSS) property that accepts DP as value. Properties with DP values are the ones of type number over the props mentioned in RN docs: View style props, Text style props, Image style props, Layout props and Shadow props. Use the exposed methods for all of the type numberproperties used in your app in order to make your app fully responsive for all screen sizes.
  3. You can also provide decimal values to these 2 methods, i.e. font-size: widthPercentageToDP('3.75%'). The package methods can be used with or without flex depending on what you want to do and how you choose to implement it.
  4. The suggested approach is to start developing from larger screens (i.e. tablets). That way you are less prone to forget adding responsive values for all properties of type number. In any case, when your screen development is done, you should test it over a big range of different screens as shown below in the How do I know it works for all devices ? section.

Orientation change support

If you want for your application to detect orientation change and adapt the UI itself, you simply need to add an extra listener . This translates into an additional 2 methods: listenOrientationChange that adds the event listener for detecting device orientation change and removeOrientationListener that removes the listener in order not to add new listeners in case the screen is re-mounted (that could lead to performance issues and potential app crashes). To see how to use them, check the example below.

Let’s see how we can add orientation support to our previous example:

import React from 'react';
import { StyleSheet, Text, View, Dimensions } from 'react-native';
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
listenOrientationChange as loc,
removeOrientationListener as rol
} from 'react-native-responsive-screen';

export default class App extends React.Component {
componentDidMount() {
loc(this);
}

componentWillUnMount() {
rol();
}
render() {
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'gray',
alignItems: 'center',
justifyContent: 'center',
},
responsiveBox: {
width: wp('84.5%'),
height: hp('17%'),
borderWidth: 2,
borderColor: 'orange',
flexDirection: 'column',
justifyContent: 'space-around'
},
text: {
color: 'white'
}
});

return (
<View style={styles.container}>
<View style={styles.responsiveBox}>
<Text style={styles.text}>This box is always of 84.5% width and 17% height.</Text>
<Text style={styles.text}>Test it by running this example repo in phones/
emulators with screens of various dimensions and pixel per inch (ppi).</Text>
</View>
</View>
);
}
}

What happened now?
1. We added the orientation change listener in componentDidMount lifecycle method. 
2. We added the orientation change listener remover in componentWillUnmount lifecycle method. This is a crucial step, because if we don’t do it, a new listener will be registered every time the component re-mounts and can lead to performance issues or even application crash.
3. styles object is created inside the render lifecycle method. That is because every time the listener detects the orientation change it triggers a re-render and thus the styles will be re-created.

And the result looks like that:

Image 5: Gif demonstrating how UI adapts its responsiveness to orientation change

The code behind the package

If you want to check the source code of these methods and how they work have a look below. The code is actually pretty small and that’s what triggered me to create a package out of them; a small, easy to use package for responsiveness.

What do you think?

What do you think about this solution? Feel free to offer your perspective and ideas to the comments section below.

If you enjoyed this article, feel free to hit that clap button 👏 to help others find it.

About me

About me

Hi, I’m Tasos, a software engineer that loves web and currently works a lot with React Native and React. I take over project contracts and do consulting. You can have a look over my portfolio here. If you want to work with me or just say hi, contact me an email: tasos.maroudas@codedlines.com