AsyncStorage.removeItem(key) not removing items unless App restarted or after several minutes - react-native

I have a logout Button that binds to the following method.
async onLogoutButtonPress() {
await AsyncStorage.removeItem('accessToken');
await AsyncStorage.removeItem('mobileNumber');
await AsyncStorage.removeItem("businessId");
await AsyncStorage.clear();
this.props.navigation.navigate('Login');
}
I understand that AsyncStorage.removeItem is an asynchronous operation and it returns a Promise. However notice that I am using await that waits for Promise to resolve before navigating to Login.
I also tried the alternate then syntax
AsyncStorage.removeItem(('accessToken')).then(() => {console.log('resolved') },
() => { console.log('rejected') })
And all of them show as resolved in the console, but some how the items persist unless I close and restart the app. I am seeing this behavior on Android.
Why aren't the items getting removed until I restart the app ?
I am using expo for the build, if that matters.
I am using
import { AsyncStorage} from 'react-native';
Here's the react-native version.
"react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",

Related

Abort an Updates.fetchUpdateAsync() after a certain time [Expo/React native]

Expo React Native SDK Version: 46
Platforms: Android/iOS
Package concerned : Expo.Updates
Hello everyone, I want to programmatically check for new updates, without using the fallbackToCacheTimeout in app.json that will trigger the check of the new updates when the application is launched because like that I can't put a custom loading page.
So by doing this all by code as follow :
try{
const update = await Updates.checkForUpdateAsync();
if(update.isAvailable){
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}else{}
}catch(err){}
But I want to be able to abort all those calls after a certain time (thus, the user that have a bad connection can use the app without waiting a very long time).
I check the documentation and I cannot found any method that allow this.
I dont't think it's possible to cancel a Promise for now in Javascript, or maybe any connection ?
Or does the "fallbackToCacheTimeout" value in the app.json will automatically apply to the fetch updates call of the Expo API?
Do someone have any idea how to do it ? :(
First of all I am assuming you have set updates.checkautomatically field to ON_ERROR_RECOVERY in app.json or app.config.js file. If not, please check the documentation. The reason why you need this is to avoid automatic updates which can also block your app on splash screen.
Updated Solution
Because of the limitation in javascript we can't cancel any external Promise (not created by us or when its reject method is not exposed to us). Also the function fetchUpdateAsync exposed to us is not a promise but rather contains fetch promise and returns its result.
So, here we have two options:
Cancel reloading the app to update after a timeout.
But note that updates will be fetched in background and stored on
the device. Next time whenever user restarts the app, update will
be installed. I think this is just fine as this approach doesn't
block anything for user and also there is a default timeout for http
request clients like fetch and axios so, request will error out in
case of poor/no internet connection.
Here is the code:
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
const updateFetchPromise = Updates.fetchUpdateAsync();
const timeoutInMillis = 10000; // 10 seconds
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject("timedout"), timeoutInMillis))
// This will return only one Promise
Promise.race([updateFetchPromise, timeoutPromise])
.then(() => Updates.reloadAsync())
.catch((error) => {
if (error === 'timedout') {
// Here you can show some toast as well
console.log("Updates were not cancelled but reload is stopped.")
} else if (error === 'someKnownError') {
// Handle error
} else {
// Log error and/or show a toast message
}
})
} else {
// Perform some action when update is not available
}
} catch (err) {
// Handle error
}
Change the expo-updates package just for your app using a patch
Here you can return a cancel method with Updates.fetchUpdateAsync() and use it with setTimeout to cancel the fetch request. I won't be providing any code for this part but if you are curious I can definitely provide some help.
Please refer this section to understand use of fallbackToCacheTimeout in eas updates.
Old solution:
Now, for aborting or bypassing the promise i.e. Updates.fetchUpdateAsync in your case. You can basically throw an Error in setTimeout after whatever time duration you want, so that, catch block will be executed, bypassing the promises.
Here is the old code :
try{
const update = await Updates.checkForUpdateAsync();
if(update.isAvailable){
// Throw error after 10 seconds.
const timeout = setTimeout(() => { throw Error("Unable to fetch updates. Skipping..") }, 10000)
await Updates.fetchUpdateAsync();
// Just cancel the above timeout so, no error is thrown.
clearTimeout(timeout)
await Updates.reloadAsync();
}else{}
}catch(err){}

How to properly test if a Toast has been shown in react native using native base?

I am trying to write a test that checks if the screen is showing a Toast with an error message. The test passes, but there is a warning:
console.error
Warning: You called act(async () => ...) without await.
This could lead to unexpected testing behaviour, interleaving multiple act calls
and mixing their scopes. You should - await act(async () => ...);
The screen is working fine, I am just learning how to write tests. This is my test:
it('shows error correctly', async () => {
mockAxios.get.mockRejectedValueOnce(new Error('Async error'))
const { queryByText } = renderWithRedux(<DiscoverScreen />)
await waitFor(() => {
expect(queryByText(ErrorMessages.GeneralErrorToast)).not.toBeNull()
})
await waitForElementToBeRemoved(() => queryByText(ErrorMessages.GeneralErrorToast), { timeout: 5000 })
})
What am I not doing right? Definitely there is an issue with react native testing, because there are problems for certain async querying, especially when you have several of them. I found that here: https://github.com/callstack/react-native-testing-library/issues/379#issuecomment-720734366
I am using native base for showing the Toast, which is using Animated I think. Should I use jest.useFakeTimers() and how?
After researching how the Toast in native base works (this could be done when you open the source code in github - https://github.com/GeekyAnts/NativeBase/blob/master/src/basic/ToastContainer.js), I found that it uses Animated.timing.
So I had to find out how to deal with react native animations in tests. That article had a solution that worked for me: https://medium.com/#joncardasis/react-native-how-to-test-components-implementing-animated-with-jest-8cabb5fc2730
After I added the code in my jest setup file, this is how my test looks:
global.withAnimatedTimeTravelEnabled(async () => {
const { queryByText } = renderedComponent
await waitFor(() => {
expect(queryByText(ErrorMessages.GeneralErrorToast)).not.toBeNull()
})
global.timeTravel(Constants.ErrorToastDelay)
expect(queryByText(ErrorMessages.GeneralErrorToast)).toBeNull()
})
It works and now the test passes with no warnings!
One little adjustment in my jest configuration was also missing. I had to add this:
"testEnvironment": "jsdom"
I hope this could help someone else, too!

Restarting expo app on logout without ejecting

I would like to reload the entire expo application when I click on the logout function and I want to do it without ejecting the application, is there a workaround for this? Any help would be appreciated. I have researched on the react-native-restart library but it requires me to eject my application.
These are my current codes
logOut() {
Auth.currentAuthenticatedUser({
bypassCache: false
}).
then(
user => user.signOut()
)
.catch(err => console.log(err))
}
You can use
import { Updates } from 'expo';
Updates.reload()
It's generally used to reload apps when new update is available, but should also work in your case
After expo remove
Updates.reload()
then you can use
await Updates.reloadAsync()
to reload application instead.

How to force users to update the app using react native

I have updated my app on app and play store and I want to force my app users to update the new version of app in App store and playstore.
You can check for the App Store / Play Store version of your app by using this library
react-native-appstore-version-checker.
In expo app you can get the current bundle version using Constants.nativeAppVersion. docs.
Now in your root react native component, you can add an event listener to detect app state change. Every time the app transitions from background to foreground, you can run your logic to determine the current version and the latest version and prompt the user to update the app.
import { AppState } from 'react-native';
class Root extends Component {
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextState) => {
if (nextState === 'active') {
/**
Add code to check for the remote app version.
Compare it with the local version. If they differ, i.e.,
(remote version) !== (local version), then you can show a screen,
with some UI asking for the user to update. (You can probably show
a button, which on press takes the user directly to the store)
*/
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
}
import VersionCheck from 'react-native-version-check';
i have used version check lib for this purpose and approach i used is below. if version is lower i'm opening a modal on which an update button appears, and that button redirects to app store/google play
componentDidMount() {
this.checkAppUpdate();
}
checkAppUpdate() {
VersionCheck.needUpdate().then(res => {
if (res.isNeeded) {
setTimeout(() => {
this.setState({openModal: true});
});
}
});
}
updateApp = () => {
VersionCheck.getStoreUrl({
appID: 'com.showassist.showassist',
appName,
})
.then(url => {
Linking.canOpenURL(url)
.then(supported => {
if (!supported) {
} else {
return Linking.openURL(url);
}
})
.catch(err => console.error('An error occurred', err));
})
.catch(err => {
console.log(`error is: ${err}`);
});
};
For future readers.
If you are using Expo managed workflow, install this package react-native-version-check-expo using yarn add react-native-version-check-expo or npm install react-native-version-check-expo.
Consult the package documentation on Github for usage guidelines.
I'm using react-native-version-check-expo library to achieve this. Working fine for me.
if you are looking for an easy to integrate built in solution. You can use App Upgrade https://appupgrade.dev/ service to force update your mobile apps.
Create new version entry for your app version that you want to update in the app upgrade service and select whether you want to force it or just want to let users know that new version is available.
Integrate your app with App Upgrade using available SDK. Official SDK are available for React Native, Flutter, Expo, Android and iOS(Swift).
The SDK will take care of the rest.
Whenever you want to force upgrade a version just create a version entry in app upgrade dashboard.
You can also integrate using API. Just call the appupgrade api from your app with the required details such as your app version, platform, environment and app name.
The API will return you the details.. that this app needs to be updated or not.
Based on the response you can show popup in your app.You can call this API when app starts or periodically to check for the update. You can even provide a custom message.
API response:
See the response has force update true. So handle in the app by showing popup.
You can find the complete user documentation here. https://appupgrade.dev/docs
Thanks.

Dispatch an action in background app refresh with react native

I'm using react-native-background-fetch to receive app refresh events and have been struggling to dispatch an action (that fetches data) when it's triggered. I'm able to do this outside of redux but not when I dispatch the action.
BackgroundFetch.configure({
stopOnTerminate: false
}, async () => {
await store.dispatch(getItemsAction);
BackgroundFetch.finish();
});
Action:
export function getItemsAction() {
// <-- Reaches here
return async (dispatch, getState) => {
// <-- But not here
const items = await findAll();
dispatch(itemsRetrieved(items));
}
}
If not a solution, I'd like to get some insight into what's happening here.
First of all you need to call action creator
await store.dispatch(getItemsAction());
Then you'll need a middleware to handle functions as actions. I assume you are aware of redux-thunk.
If it's a headless task running in the background, It does not have access to the redux store from what I experienced.
You will want to use something like AsyncStorage (https://github.com/react-native-community/async-storage) when running the task as headless js, which is what happens when the app is running background events.