Changing to landscape screen in Expo? - react-native

I have a problem in changing the screen to Landscape with expo-screen-orientation. I have a component that uses ImageViewer to show images. When i'm on Android it works but when i'm on iOS it doesn't. How can i fix that. What i did is i called my 2 function unlockScreenToDefault and lockScreenToPortraitOrientation:
componentDidMount() {
unlockScreenToDefault().then();
}
componentWillUnmount() {
lockScreenToPortraitOrientation().then();
}
And inside the 2 functions :
const lockScreenToPortraitOrientation = async () => {
await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT);
};
const unlockScreenToDefault = async () => {
await ScreenOrientation.unlockAsync();
};

You can just force it to Landscape
await ScreenOrientation.lockAsync(
ScreenOrientation.OrientationLock.LANDSCAPE
);

Related

How to call API inside expo SplashScreen?

I'm new in react native so I can't figure out how to add an API call inside SplashScreen in react -native app. The context - I'm building a react-native app expo, which on app load should send API GET request to the backend to get order data, and based on that data I'm either displaying screen A(delivered) or B(order on it's way). I want to add this API call inside the SplashScreen when app still loads so when app is loaded there is no delay in getting API data and displaying screen A/B.
I have a simple useEffect function to call API like this:
const [data, setData] = useState{[]}
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(url);
if (response.status.code === 200 ) {
setData (response.data) // to save data in useState
}
} else if (response.status.code != 200) {
throw new Error();
}
} catch (error) {
console.log(error);
}
};
getData();
}, []);
and then in the return:
if (data.order.delivered) {
return <ScreenA />
}
else if (!data.order.delivered) {
return <ScreenB />
else {return <ScreenC />}
The issue is that sometimes if API is slow, then after splash screen app has a white screen, or ScreenC can be seen. How can I call API in the splashscreen while app is loading and have a nicer ux?
you can make a custom hook with simple UseState and put it after you've fetched your data
const [loading, setLoading] = useState(true)
...
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(url);
if (response.status.code === 200 ) {
setData (response.data)
// When data is ready you can trigger loading to false
setLoading(false)
}
...
and After that, you can use a Simple If statement on top of your app.js file
like this
if (!loaded) {
return <LoadingScreen/>; // whetever page you want to show here ;
}
you can use expo expo-splash-screen to achieve this goal:
call this hook on mount...
import * as SplashScreen from 'expo-splash-screen';
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// Keep the splash screen visible while we fetch resources
await SplashScreen.preventAutoHideAsync();
// Pre-load fonts, make any API calls you need to do here
await Font.loadAsync(Entypo.font);
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
you can also check expo doc

navigation.getparam latest value is not accessible inside didFocus listener

I am having 2 screens in a react native app, both the screens are functional components.
A button on screen 1 leads to screen 2, I select a few checkboxes on screen 2 and click on a button to navigate to screen 1 with adding navigation params.
coming back on screen 1 runs didFocus listener, but param values are undefined, when I hit ctrl+s on in code editor, useEffect runs and values become accessible.
After this, going back to screen 1 from screen 2 runs didfocus listener (as expected) but the param values do not update.
below is useEffect code in screen 1.
useEffect(() => {
navigation.getParam('from') == 'TagFiltersScreen' ? getAllQuestions('mostInsightful', navigation.getParam('tagsFilter')) : getAllQuestions();
const listener = navigation.addListener('didFocus', () => {
navigation.getParam('from') == 'TagFiltersScreen' ? getAllQuestions('mostInsightful', navigation.getParam('tagsFilter')) : getAllQuestions();
});
return () => {
listener.remove();
}
}, []);
I faced the same issue, and here is how I am doing it.
useEffect(() => {
const isFocused = props.navigation.isFocused();
if (isFocused) {
const { params } = props.navigation.state;
navigationFocus(params);
}
const navigationFocusListener = props.navigation.addListener('willFocus', (payload) => {
const params = payload.state.params;
navigationFocus(params);
});
return () => {
navigationFocusListener.remove();
};
}, []);
const navigationFocus = (params) => {
if (params) {
}
}
I'll be curios to know if there is a better way of doing this.

orientation change listener in expo react native not firing?

i want to detect the current orientation of device in expo react native, this is my code that doesn't work:
import {
Dimensions,
} from 'react-native';
import * as ScreenOrientation from 'expo-screen-orientation';**
const App = () => {
...
useEffect(() => {
const isPortrait = () => {
const dimension = Dimensions.get('screen');
return dimension.height >= dimension.width;
};
Dimensions.addEventListener('change', () => {
const orientation = isPortrait() ? 'portrait' : 'landscape';
console.log('Dimensions orientation', orientation);
});
ScreenOrientation.addOrientationChangeListener((e) => {
console.log('e ', e);
});
}, []);
how ever when i rotate the device there is no logs so it's not firing?
This works for me:
const [orientation, setOrientation] = useState(
ScreenOrientation.Orientation.PORTRAIT_UP
);
useEffect(() => {
// set initial orientation
ScreenOrientation.getOrientationAsync().then((info) => {
setOrientation(info.orientation);
});
// subscribe to future changes
const subscription = ScreenOrientation.addOrientationChangeListener((evt) => {
setOrientation(evt.orientationInfo.orientation);
});
// return a clean up function to unsubscribe from notifications
return () => {
ScreenOrientation.removeOrientationChangeListener(subscription);
};
}, []);
You should set your orientation field as default in your app.json / app.config.js. The app is locked to the specified orientation if this field is set to another value.
Related doc is here:
https://docs.expo.dev/versions/v46.0.0/config/app/#orientation
This is the line that doesn't do anything. Broken, bugged, POS? All of the above?
ScreenOrientation.addOrientationChangeListener((e) => {
console.log(e);
});
I had this same issue. The listener function was never firing.
Adding expo-sensors to my project seems to have fixed the callback for me. I think expo-screen-orientation might depend on expo-sensors
Steps for adding:
npx expo install expo-sensors
Rebuild your expo development client. (For me that command is eas build --profile simulator, but that will depend on your eas config)
After that, the listener callback function started firing.
Here's a code snippet of where I add the listener:
useEffect(() => {
ScreenOrientation.addOrientationChangeListener((e) => {
console.log(e)
})
}, [])
You're using the wrong package.
From the expo-screen-orientation docs:
Screen Orientation is defined as the orientation in which graphics are painted on the device. ... For physical device orientation, see the orientation section of Device Motion.

React Native, how to update async-storage immediately?

I'm making a simple drinking game. When a playing card shows, it's corresponding rule shows below it. I have a settings.js file where the rules are, and the user can see and modify the rules, and they update on the game.js file. I'm using async-storage to store the rules.
I wanted to add a button in the settings.js file, which would return the original rules when pressed. The only problem is, that the original rules don't update immediately on the settings screen. When the button is pressed the original rules do update on the game, but they update on the settings screen only when the user goes back in the game and then back in the settings screen.
The code for updating the rules:
initialState = async () => {
try {
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
...
await AsyncStorage.setItem('rule13', 'theoriginalrule13')
catch(err) {
console.log(err)
}
}
I have the following line of code to update the async-storage when the screen is entered, but as said, it only works when the screen is re-entered:
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}
To answer your question here, not in a comment.
Try this :
componentDidMount() {
const { navigation } = this.props;
this.getData();
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}
I would suggest you to use ,
State driven UI
means your ui will change only when state is changed , now suppose you are changing your asyncStorage, using
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
so I would suggest your state will also update after updating your aysncStorage like.
//Initial state
this.state = { score: 0 };
async storeValues(){
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
let newScoreValue = await AsyncStorage.getItem('rule1')
this.setState({score:newScoreValue})
}
// UI will be like
render(){
return(<Text>{this.state.score}</Text>)
}

React Native, store information before app exits

Is this at all possible? I'm currently using react-native-track-player to stream audio files and I would love to be able to store the last position when my users exit the app and resume when they re-open (e.g. similar to how Spotify works)
Right now I'm tracking this info via a simple interval:
this.keepTime = setInterval(async () => {
const state = await TrackPlayer.getState()
if (state == TrackPlayer.STATE_PLAYING) {
const ID = await TrackPlayer.getCurrentTrack()
const position = await TrackPlayer.getPosition()
await AsyncStorage.setItem(ID, String(position))
}
}, 10000)
Problem is, I need to clear the interval when my app moves to the background or else it will crash. I would also much rather only need to call this code once as opposed to periodically if that is possible.
I know I could use headless JS on android but the app is cross platform, so my iOS user experience would be lesser.
Any suggestions?
I think you can use componentWillUnmount() function for this.
You could add a listener to get the App State and then log the position when it goes to background.
class App extends React.Component {
state = {
appState: AppState.currentState
}
componentDidMount() {
AppState.addEventListener('change', this.handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
this.saveTrackPosition();
}
handleAppStateChange = (nextAppState) => {
if (nextAppState.match(/inactive|background/) && this.state.appState === 'active') {
this.saveTrackPosition();
}
this.setState({appState: nextAppState});
}
saveTrackPosition = () => {
if (state == TrackPlayer.STATE_PLAYING) {
const ID = await TrackPlayer.getCurrentTrack()
const position = await TrackPlayer.getPosition()
await AsyncStorage.setItem(ID, String(position))
}
}
}