Description
Issue
The library can't seem to handle cases where Activity.onDestroy()
is called when authenticating. Authentication is performed in a WebView separate from the Android Activity that is running RN, so that means when we move to it, the RN Activity is put on the backstack, and Android is now free to have the Activity.onDestroy()
called on the Activity.
When Activity.onDestroy()
is called on the RN Activity, and once authentication succeeds, the library attempts to launch the previous activity again (but it was destroyed so it needs to be recreated). So we end up with a new Activity/Component that is completely unrelated to the authorize call we originally made.
Some time after the new activity is done loading, our original call to authorize()
will get a response from the token endpoint. However, the Component is detached, and unable to handle rendering. This means that if you want to navigate to a new Component, such as the rest of your app, you can not. The only way I can imagine being able to handle this is, first being aware that this situation can arise, when it does arise, and then to actually know that the state has changed on the new Component, you would have to use a reactive structure like a RxJS Subject that will emit to the new Component to allow it to handle this case, when it occurs.
You can recreate this scenario easily by using the "Don't Keep Activities" setting in your android Device's Developer Settings, or by using a low performance/low memory Android device.
Example code
import React from 'react'
import {Button, Text} from 'native-base'
import {Image, ImageStyle, StyleSheet, View, ViewStyle} from 'react-native'
import {NavigationParams, NavigationScreenProps} from 'react-navigation'
import {authorize} from 'react-native-app-auth'
import Config from 'react-native-config'
type State = {
showLogin: Boolean,
}
export default class LoginComponent extends React.Component<NavigationScreenProps, State> {
constructor(props: Readonly<NavigationScreenProps<NavigationParams, State>>) {
super(props)
this.state = {
showLogin: true,
}
}
async authWithBackend() {
this.setState({
showLogin: false,
})
// use the client to make the auth request and receive the authState
try {
const authorizationResponse: any = await authorize({
clientId: 'f3d259ddd3ed8ff3843839b',
clientSecret: '4c7f6f8fa93d59c45502c0ae8c4a95b',
redirectUrl: 'io.viamo.clipboard://',
scopes: [],
serviceConfiguration: {
authorizationEndpoint: Config.OAUTH_ISSUER_URL,
tokenEndpoint: Config.OAUTH_TOKEN_URL,
},
usePKCE: false,
})
this.props.navigation.navigate('MainComponent')
} catch (error) {
console.error(error)
}
}
private loginButton() {
if (this.state.showLogin) {
return (
<Button style={styles.signInButton} onPress={() => this.authWithBackend()}>
<Text>
Sign In
</Text>
</Button>
)
} else {
return null
}
}
render() {
return (
<View style={styles.root}>
<Image source={{uri: 'viamo_logo'}} style={styles.logo}/>
{this.loginButton()}
</View>
)
}
// noinspection JSUnusedGlobalSymbols Used by NavigationController
static navigationOptions = {
header: null,
}
}
interface IStyles {
root: ViewStyle,
signInButton: ViewStyle,
logo: ImageStyle;
}
const styles = StyleSheet.create<IStyles>({
root: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-evenly',
},
signInButton: {
alignSelf: 'center',
paddingLeft: 32,
paddingRight: 32,
},
logo: {
width: '85%',
// Without height undefined it won't work :( (https://stackoverflow.com/a/53482563)
height: undefined,
// figure out your image aspect ratio
aspectRatio: 488 / 171,
},
})
Environment
- Your Identity Provider: `e.g. Custom/Viamo.io
- Platform that you're experiencing the issue on:
Android
- Are you using Expo?: No