How do I handle scaling issues in a React Native app between tvOS and Android TV? - react-native

Native resolution for Apple TV seems to be 1920x1080 (as expected) but for Android TV / Fire TV it seems to be 961.5022957581195x540.8450413639423 (according to Dimensions.get('window')).
So, when I run my app on Apple TV everything looks fine. But when I run it on an Android TV nothing fits on the screen.
Is there a way to force the Android TV to shrink everything? Or do I have to create two different style sheets for the different devices to change font sizes and dimensions of all my components?

We use a different approach, on the base Application class for tv we add this
class TvApplication extends Application {
#Override
protected void attachBaseContext(Context base) {
Configuration configuration = new Configuration(base.getResources().getConfiguration());
configuration.densityDpi = configuration.densityDpi / 2;
Context newContext = base.createConfigurationContext(configuration);
super.attachBaseContext(newContext);
}
}
with this, we have consistent width & height when using dimensions, and we can use the same styling values on all platforms without doing any manipulation on the JS side.
it's not perfect, but it's more convenient when building for multiple platforms

Use Platform.OS to check for platform and use margin property in styles to get the content right on screen in android. This is normal behavior in android tv.

You have PixelRatio and Dimensions for this purpose in React Native. Along with this you need to use a RN module react-native-pixel-perfect, this keeps your app pixel perfect across all devices, quickly and easily
import {PixelRatio, Dimensions} from 'react-native';
import {create} from 'react-native-pixel-perfect';
let displayProps = {
width: PixelRatio.roundToNearestPixel(
Dimensions.get('window').width * PixelRatio.get(),
),
height: PixelRatio.roundToNearestPixel(
Dimensions.get('window').height * PixelRatio.get(),
),
};
let perfectSize = create(displayProps);
Now always pass your size in pixels to this method to get original device pixels based on the devices.
const styles = StyleSheet.create({
container: {
width: perfectSize(500),
height: perfectSize(300)
}
});
Your container will adapt to the devices correctly. Based on their screen resolution.
In case if you have a minimum height x width to support but some devices are less than the minimum screen resolution and you want to still achieve the same results in those devices. Then you can set the minimum screen resolution on this function like below.
let displayProps = {
width: PixelRatio.roundToNearestPixel(
Math.max(1920, Dimensions.get('window').width * PixelRatio.get()),
),
height: PixelRatio.roundToNearestPixel(
Math.max(1080, Dimensions.get('window').height * PixelRatio.get()),
),
};
So in my case if the screen resolution is less than 1920x1080 lets say 720p devices then this will help rendering the UI in 1920x1080.

Related

React native responsive units

I have been watching Rect Native tutorials on youtube and everyone uses units like padding:20 or fontSize:18 etc
Is that how you do in a professional app too? What is the ideal practice? Is this automatically responsive?
No, this is not automatically responsive. If we set padding:20, then the padding of that component stays on 20 no matter what phone we use. The same holds for font sizes, even though they are scaled depending on what pixel density we are dealing with.
A responsive design must be implemented by hand which could be done by using Dimensions as follows.
import { Dimensions } from 'react-native'
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
We can now use a different padding depending on the width of our device.
padding: width > 320 ? 20 : 15
When it comes to general layouting, then flexbox takes care of responsiveness, e.g. imagine that we have three elements on the screen with flexDirection: 'row' and we set flex:1 for all of them, then the available space will be divided equally between them no matter what our screen width is. The same applies for height.
It might be advised to create a separate Fonts file which holds font sizes and similar constants in a common place, so we only need to change them once.
import { Dimensions } from 'react-native'
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
export Fonts = {
h1: width > 320 ? 18 : 15,
h2: width > 320 ? 16 : 14,
...
}
The above takes care of responsive font sizes and we can use it in all other screens, e.g.
<Text style={{fontSize: Fonts.h1}}>I am a header</Text>

React Native view scaling

So I'm developing a cross platform React Native app, the app is using allot of images as buttons as per design requirements that need to be given an initial height and width so that their aspect ratios are correct. From there I've built components that use these image buttons and then placed those components on the main screen. I can get things to look perfect on one screen by using tops and lefts/ rights to get the components positioned according to the design requirements that I've been given.
The problem I'm running into is now scaling this main screen for different screen sizes. I'm basically scaling the x and y via the transform property on the parent most view as such. transform: [{ scaleX: .8 }, { scaleY: .8 }] After writing a scaling function that accounts for a base height and current height this approach works for the actual size of things but my positioning is all screwy.
I know I'm going about this wrong and am starting to think that i need to rethink my approach but am stumped on how to get these components positioned correctly on each screen without having to hard code it.
Is there any way to position a view using tops and lefts/rights, lock that in place, then scale it more like an image?
First of all, try using flex as far as you can. Then when you need extra scaling for inner parts for example, you can use scale functions. I have been using a scale function based on the screen size and the pixel density, and works almost flawless so far.
import { Dimensions } from "react-native";
const { width, height } = Dimensions.get("window");
//Guideline sizes are based on standard ~5" screen mobile device
const guidelineBaseWidth = 350;
const guidelineBaseHeight = 680;
const screenSize = Math.sqrt(width * height) / 100;
const scale = size => (width / guidelineBaseWidth) * size;
const verticalScale = size => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5) =>
size + (scale(size) - size) * factor;
export { scale, verticalScale, moderateScale, screenSize };
Then you can import these and use in your components. There are different types of scales, you can try and see the best one for your components.Hope that helps.
I ended up going through each view and converting everything that was using a hard coded height and width pixel to setting the width and then using the property aspectRatio and giving that the hard coded height and widths. That along with implementing a scaling function that gave me a fraction of the largest view, so say .9, and then scaling the main view using transform. People arent kidding when they say this responsive ui stuff is tough.
2022 update -
I resolved this problem on my next app by using flex everywhere & a function called rem that I use everywhere that needs a fixed pixel count. With this I can set the width on an image and define an aspect ratio based on the images original dimensions and get an image that scales to the screen size, it's been super reliable.
static width = Dimensions.get("window").width;
static height = Dimensions.get("window").height;
static orientation = 'PORTRAIT';
static maxWidth = 428;
static rem = size => {
let divisor = window.lockedToPortrait || Styles.orientation === 'PORTRAIT' ? Styles.width : Styles.height;
return Math.floor(size * (divisor / Styles.maxWidth))
};
The maxWidth is a predefined value from the largest device I could find to simulate which was probably an iPhone max.

Image orientation issue in android react native

i'm using CameraRoll.getPhotos for get photos from gallery.
import {
CameraRoll,
} from 'react-native';
CameraRoll.getPhotos(fetchParams).then((data) => {
});
I have a problem with the orientation of the images on some android phones. I have been able to detect what happens in most SAMSUNGs.
It has an orientation depending on the way you take the picture.
portrait with home button in bottom position: 90º
landscape with home button left position: 180º
portrait with home button in top position: 270º
landscape with home button right position: 0º
Related with this issue
Base on CameraRoll Documentation the orientation value is not returned by getPhotos.
I'm try with:
transform: [{ rotateX: '0deg' }, { rotateY: '0deg' }] or rotate: '0deg'
Using react-native-media-meta for get metadata info (not works)
Use another image component instead native Image
but notting works. :(
The problem is that the images appear rotated in my application, but in the native gallery ignores the degree of rotation they have and shows them correctly with 0º.
Example:
In this case the image appear with 90º and it's showed perfectly by native gallery but bad in my app.
- Is there any way to detect the current rotation of the image?
- Can the <Image> component be forced in some way to place the orientation at 0º?
UPDATE:
i found a library this module and its possible get the orientation of image and with that can fix this problem. But this process is very delayed and affect considerably the perfomance.

Slower performance on iOS release than development

I've built a React Native iOS app which is fairly basic; it's a few screens which the user can click through to from a 'Home' component, and each one consists of basic components comprising solely Text/View/Image components.
In simulator the app is responsive and there aren't any JS console warnings, however when I do a release to my iPad (Air 2), there's a noticable lag between the home screen and certain other screens. These are notably the screens which have more images on.
I'm wondering if it's because I'm using larger images (the app was designed for the iPad Pro 2) and scaling the images down for use where I want them. The worst offender is a page which has a masonry-style grid of images. There's still only about 30 in a ScrollView there though. Once the component has been shown once the app is much more responsive.
I've already taken steps to optimise my components in terms of using extending PureComponent or using stateless functions wherever possible, and console logging shows me that the touchables are responding immediately, so the delay is definitely at the render time of the component.
N.B. All images are local (loaded via require('./path/to/file')), nothing is being loaded over the network.
Here's an example of the code that populates an array of items for display inside the ScrollView:
...
const items = mediaItems.map((item, index) => {
// scale desired width (1044 for video, 520 for images) based on designed width and device aspect ratio.
const imageWidth = item.video ? (1044/2732) * deviceWidth : (520/2732) * deviceWidth
return (
<TouchableHighlight key={index} onPress={() => onOpen(item)}>
<View style={[styles.gridImageView, { width: imageWidth }]}>
<Image style={styles.gridImage} source={item.image} />
</View>
</TouchableHighlight>
)
});
...
and the styles for the gridImageView and gridImage are as follows:
...
gridImageView: {
height: (460/2732) * width,
margin: (2/2732) * width,
position: 'relative'
},
gridImage: {
resizeMode: 'cover',
width: null,
height: null,
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
...
So my question is kind of multi-layered:
What is the best practice with regards to ensuring the component appears quickly?
Should I not be setting a width/height on the image itself at all?
Should I be doing some kind of pre-loading of the images in the sizes I want before I let the user begin to navigate around the app?
Should I be using JPG instead of PNG images?
Is there another trick I'm missing?
Should I not be setting a width/height on the image itself at all
You have to set a width and height. If you don‘t do so, your image won‘t display
Should I be doing some kind of pre-loading of the images in the sizes I want before I let the user begin to navigate around the app?
It is a good idea to downlod images beforehand. Huge images need a lot of performance. Probably your issues are gone, if you resize your images before displaying them. Therefore you could use react native image resizer
Should I be using JPG instead of PNG images?
You should use JPGs, because they provide a higher compression rate.

How to apply multiple device layouts in React Native?

I'm a newbie to React Native so if I'm asking a very dumb question. Please forgive me for wasting your time.
I need to apply multiple devices layout in my React Native app. Let's say my application screens have completely different appearances but the same business processes on mobile and tablet devices.
How do I achieve that in React Native? Where do I start digging?
EDIT 2020:
Ok, I was a newbie...! Sorry!
Like #Hariks says, you could try to use something like this module
and put something like:
import Device from 'react-native-device-detection';
// Mobile Styles
let imageSize = 60;
// Tablet Styles
if(Device.isTablet) {
imageSize = 120;
}
Old answer: (if you want to detect OS)
I'm newbie too, and, from what I've understood, and extracted from here, there are two methods:
By naming files (recommended) Platform specific files can be named as “[filename].android.js” and “[filename].ios.js” for Android
and iOS respectively. If we import or require [filename], it picks up
the file depending on the host platform.
By adding conditionals in the source code For example, if you want the background of the navbar in different colors for iOS and
Android we can write the following code:
Code: backgroundColor: (Platform.OS === ‘ios’ ) ? ‘gray’ : ‘blue’
Of course, you should take a look at the official documentation.
If you are styling based on the OS, you could use Platform as mentioned by #anfuca. If you need to style based on devices ie tabs and phone, there is a handy module react-native-device-detection
You could do something like this in your style file
import Device from 'react-native-device-detection';
// Mobile Styles
let imageSize = 60;
// Tablet Styles
if(Device.isTablet) {
imageSize = 120;
}
Also you could create a global style file where you could define fontsizes and all based on the device/pixel ratios.
commonstyle.js
import Device from 'react-native-device-detection';
let h1 = 20;
let h2 = 18;
let h3 = 16;
if(Device.isTablet) {
h1 = 25;
h2 = 22;
h3 = 20;
}
module.exports = {
h1,
h2,
h3
};
You can use device detection for detecting mobile or tablet and use separate styling for mobile and tablet accordingly
https://www.npmjs.com/package/react-native-device-detection