Interact with React app in Android WebView - javascript

Here is my simplified setup
//--- sample.com app ---//
async fetchFromServer() {
// return promise..
}
function App() {
const [sampleState, setSampleState] = React.useState(null)
React.useEffect(() => {
fetchFromServer().then((data) => {
setSampleState(data)
})
}, [])
return (
<p>{JSON.stringify(sampleState)}</p>
);
}
//--- android app ---//
public class SampleWebActivity extends Activity {
SamleWebInterface mWebInterface;
WebView mWebView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mWebView = new WebView(this);
setContentView(mWebView);
mWebInterface = new SamleWebInterface();
mWebView.addJavascriptInterface(mWebInterface, "Native");
//...
mWebView.loadUrl("https://sample.com");
}
}
// Somewhere in another activity
private void showWebScreen() {
Intent intent = new Intent(this, SampleWebActivity.class);
// use existing activity if present
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(intent);
}
//--- Question ---//
How can I re-fetch sampleState from server every time SampleWebActivity is shown without reloading the whole page?
I want to aviod reloading because the actual web app is much bigger than the sample. Also I don't know the exact state of the web app so it's not clear which url to load.
I want to show whetever whas shown before the activity was switched but with updated data.
I'm aware of WebView.evaluateJavascript() but don't know how to interact with the react app after it's compiled into vanilla js.

Using the Webview (browser) itself:
You could just use the webview's built in event system, i.e. When the webview returns from being backgrounded for instance you can leverage the document.visibilityState API (https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilityState):
async fetchFromServer() {
// return promise..
}
function App() {
const [sampleState, setSampleState] = React.useState(null)
React.useEffect(() => {
const fetchData = () => {
if (document.visibilityState !== "hidden") {
fetchFromServer().then((data) => {
setSampleState(data)
})
}
}
fetchData();
document.addEventListener("visibilitychange", fetchData);
return () => document.removeEventListener("visibilitychange", fetchData);
}, [])
return (
<p>{JSON.stringify(sampleState)}</p>
);
}
Injecting/Calling from Java:
async fetchFromServer() {
// return promise..
}
function App() {
const [sampleState, setSampleState] = React.useState(null)
React.useEffect(() => {
const fetchData = () => {
fetchFromServer().then((data) => {
setSampleState(data)
})
}
/**
* Make some globals
*/
window.__triggerRefetch__ = fetchData;
window.__setState__ = setSampleState;
fetchData();
return () => {
/**
* Remove the globals
*/
delete window.__triggerRefetch__;
delete window.__setState__;
}
}, [])
return (
<p>{JSON.stringify(sampleState)}</p>
);
}
You trigger a refetch with WebView.evaluateJavascript("window.__triggerRefetch__()") or you can inject data with something like WebView.evaluateJavascript("window.__setState__({foo: "bar"})")

Related

inconsistent execution of functions in react

i'm trying to get data from the server and write it to a state, but the functions are not firing sequentially
I have a file core.js with with fuctions set
export var LoginAPI = {
signIn: (signInData) => {
request.useQuery(signInData, 'Autorize', 'LogIn').then(Request => {
if (Request.flag) {
return true;
}
else {
GeneralAPI.showNotificationError("Ошибка входа", Request.answer)
return false;
}
})
}
}
request is simple fetch to sigin method into .net core identity
and i have a some page where i need to use it
`class NormalLoginForm extends Component {
//somecode
onFinish = (e) => {
var LoginDatas = {
Login: this.formRef.current.getFieldValue("loginFormItem"),
Password: this.formRef.current.getFieldValue("passWordFormItem")
}
var signIn = core.LoginAPI.signIn(LoginDatas)
this.props.store.isAuthorizeChange(signIn)
console.log("result")
}
render() {
return (
<Form >
<somefFormComponents>
</Form>
);
}
}`
And i have a problem
this function is executed first is this.props.store.isAuthorizeChange(signIn) and only the second one makes a request to the server.
how can this be fixed?
I tried to wrap
var signIn = core.LoginAPI.signIn(LoginDatas)
into Promise but all the same, first changes were called in the storage and only then the resolve method in the promise

web3js intercepting when metamask connects manually

Currently I have the following code to connect the metamask wallet in the browser (using reactjs and web3js):
function App() {
const [contract, setContract] = useState();
const [account, setAccount] = useState();
const[web3Obj, setWeb3Obj]=useState();
useEffect(()=>{
async function load(){
const web3 = new Web3(Web3.givenProvider || 'http://http://localhost:7545');
setWeb3Obj(web3);
const accounts = await web3.eth.requestAccounts();
console.log(accounts[0] + " is the account");
setAccount(accounts[0]);
$.get( "getcontractaddress")
.done( function( data ) {
const _contract = new web3.eth.Contract(abi, data);
_contract.address = data;
console.log(_contract.address + " is the contract");
setContract(_contract);
});
}
load();
},[])
return(
);
}
I deleted the return(); part from the snippet because its irrelevant to the question.
Whenever I switch to a different account in metamask, the "account" object is not being updated. How can I intercept the event of metamask switching accounts to automatically reset the account object to the new account?
Your component does not know when you switch accounts in Metamask. To detect account changes, Metamask provides the accountsChanged event.
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
You can add the event to your load function or make another useEffect. You can also remove the event when your component unmounts using ethereum.removeListener.
useEffect(() => {
const { ethereum } = window;
if (ethereum && ethereum.on) {
const handleAccountsChanged = (accounts) => {
console.log("accountsChanged", accounts);
setAccount(accounts[0])
};
ethereum.on('connect', handleConnect);
return () => {
if (ethereum.removeListener) {
ethereum.removeListener('accountsChanged', handleAccountsChanged);
}
};
}
}, []);

Javascript: Fetch data while executing code

I am building an app that relies on some data I need to fetch from my server. Right now my app starts with fetching the data and then passing down the data through several steps which are separated by classes. I simplify the code in the following.
Class1:
export default class Class1 {
constructor(props) {
this.props = props
}
async init() {
const data = await this.fetchData()
new Screen({ data }).init()
}
async fetchData() {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open("POST", 'http://localhost:8888/getData', true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.onload = () => resolve(JSON.parse(xhr.responseText))
xhr.onerror = reject
xhr.send(JSON.stringify({}))
})
}
}
Class2:
export default class Screen {
constructor(props) {
this.props = props
}
async init() {
let ScreenType
if (this.offscreenIsSupported()) ScreenType = Offscreen
else ScreenType = Onscreen
new ScreenType(this.props).init()
}
offscreenIsSupported() {
return "OffscreenCanvas" in window && "transferControlToOffscreen" in this.props.canvas
}
}
OnscreenClass:
export default class Onscreen {
constructor(props) {
this.props = props
}
async init() {
new Handler(this.props}).init()
}
}
OffscreenClass:
export default class Offscreen {
constructor(props) {
this.props = props
}
async init() {
this.worker = new Worker(new URL('./offscreen.worker.js', import.meta.url))
const offscreen = this.props.canvas.transferControlToOffscreen()
this.worker.postMessage({
type: "init",
canvas: offscreen
}, [offscreen])
}
}
offscreen.worker.js
self.onmessage = msg => {
switch (msg.data.type) {
case 'init':
init(msg.data)
break
}
}
async function init(data) {
self.vgHandler = new Handler({ data })
await self.vgHandler.init()
}
Class3:
export default class Handler {
constructor(props) {
this.props = props
}
async init() {
this.setup(this.props.data)
}
}
As you can see the main code called in Class3 will be the same. This Class needs the fetched data. Class2 is just necessary to determine if the browser should use offscreen or onscreen canvas. Since checking the ability of using offscreen canvas as well as creating the web worker is not dependent to the fetched data it is not very smart to await the fetching process.
How am I able to fetch the data in background, run my code of Class2 simultaneously and use the fetched data in Class3?
I want to make this process as fast as possible.
Best

How to implement a paging solution using RxJs?

What is the best way to implement a paging solution in RxJs?
If want to have an observable that emits new data based on an event (could be a click, or a JavaScript function call). For instance if I have an Observable retrieving data from a Web API, and I don't want it to just keep on hammering away HTTP requests, I only want that to happen on an event triggered by the subscriber. Scenarios could be infinite scrolls (event triggered by scrolling), classic paging (event triggered by user clicking on next page) etc.
This is the solution I came up with based on the comments and using RxJs 6.3.3
export default class Test extends React.Component<any, any> {
private subscription: Subscription;
public componentDidMount() {
const client = new MyClient();
let source = client.get('/data');
const buttonNotifier = defer(() => {
console.log("waiting");
return fromEventPattern(
(handler) => { this.next = handler; }
).pipe(tap(() =>
console.log("sending more data")
));
});
const pagedData: Observable<{Value:any, NextLink:string}> = source.pipe(
expand(({ NextLink }) => NextLink ?
buttonNotifier.pipe(take(1), concatMap(() => client.get(NextLink))) :
empty()));
this.subscription = pagedData.subscribe(
result => {
this.setState({
Value: result.Value,
});
},
error => {
this.setState({
Error: `ERROR: ${error}`
});
},
() => {
this.setState({
Done: `DONE`
});
}
);
}
public componentWillUnmount() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
private next: Function;
public render(): React.ReactElement<any> {
return (<button onClick={()=> {this.next();}}>Next</button>);
}
}

React-native open link in browser and return to app

I've developing an app in react-native that should communicate with a gateway for payments, after finishing the payment process (success or failure) I need to show an alert to user. For this purpose, I open a link in WebView and after that I get return's url with onNavigationStateChange and show success or failure message.
But, this flow for security issues must be done in a default device browser.
Current Code:
const BASEURL = 'https://gatewayURL/?ID=';
let Token = null;
let paymentAccepted = null;
let paymentFactorId = null;
class Gateway extends PureComponent {
static propTypes = {
dispatch: PropTypes.func,
navigation: PropTypes.any,
}
componentWillMount() {
this.props.dispatch(getPaymentStatus());
}
_onLoad(webViewState) {
let url = webViewState.url.toString();
let isResponseValid = url.includes('backFromGateway');
if(isResponseValid){
if(this.props.checkedPaymentStatus != 'checked' ){
setTimeout(() => {
this.props.dispatch(setPaymentStatus('checked'));
let splitedURL = url.split("/");
paymentFactorId = splitedURL[splitedURL.length -2];
if(splitedURL[splitedURL.length - 1] === '0'){
paymentAccepted = true;
this.props.dispatch(setGatewayResponse('done', paymentFactorId));
}
else {
paymentAccepted = false;
this.props.dispatch(setGatewayResponse('rejected', paymentFactorId));
}
this.props.navigation.navigate('BackFromGateway', { title: '' })
}, 1000);
}
}
}
render() {
const { addNewOrderGatewayToken, checkedPaymentStatus } = this.props;
token = addNewOrderGatewayToken;
let view = null;
if(checkedPaymentStatus !== 'checked'){
view = <WebView onNavigationStateChange={this._onLoad.bind(this)} style={styles.container} source={{ uri: `${BASEURL}${token}` }}/>
}
else{
view = <View></View>
}
return (
<View style={styles.container}>
{view}
</View>
);
}
}
Any idea?
Thanks
If you can make callbacks from the gateway website, then I recommend to use deep linking to handle flow between app and browser. Basically, your app will open the gateway website for payment, and depending on payment result, the website will make a callback to the app using its deep link. App then will listen to the link, take out necessary information and continue to proceed.
What you need to do is:
Set up deep linking in your app. You should follow the guide from official website (here) to enable it. Let pick a random URL here for linking, e.g. gatewaylistener
Set the necessary callbacks from gateway to your app. In your case, since you need to handle successful payment and failed payment, you can add 2 callbacks, e.g. gatewaylistener://success?id={paymentId} and gatewaylistener://error?id={paymentId}
Finally, you need to listen to web browser from the app. One way to do that is add listener right inside the component opening the gateway.
// setup
componentDidMount() {
Linking.getInitialURL().then((url) => {
if (url) {
this.handleOpenURL(url)
}
}).catch(err => {})
Linking.addEventListener('url', this.handleOpenURL)
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL)
}
// open your gateway
async openGateWay = () => {
const { addNewOrderGatewayToken } = this.props
const url = `${BASEURL}${addNewOrderGatewayToken}`
const canOpen = await Linking.canOpenURL(url)
if (canOpen) {
this.props.dispatch(setPaymentStatus('checked'))
Linking.openURL(url)
}
}
// handle gateway callbacks
handleOpenURL = (url) => {
if (isSucceedPayment(url)) { // your condition
// handle success payment
} else {
// handle failure
}
}
For authentication purposes, using a deep linking redirection for example, you can use an embedded browser with Chrome Custom Tabs from Android and SafariViewController from iOS, check the InAppBrowser component to support both platforms with the same code (Linking is already used internally to detect the deep link redirection).
As you can see from the example folder, you can use a custom deep link configured from your app (AndroidManifest for Android and Info.plist for iOS):
getDeepLink (path = '') {
const scheme = 'my-demo'
const prefix = Platform.OS === 'android' ? `${scheme}://demo/` : `${scheme}://`
return prefix + path
}
async tryDeepLinking () {
const redirectToURL = `https://proyecto26.github.io/react-native-inappbrowser/`
const redirectUrl = this.getDeepLink('home')
const url = `${redirectToURL}?redirect_url=${encodeURIComponent(redirectUrl)}`
try {
if (await InAppBrowser.isAvailable()) {
const result = await InAppBrowser.openAuth(url, redirectUrl)
await this.sleep(800)
Alert.alert('Response', JSON.stringify(result))
} else {
// You can use Linking directly for iOS < 9
}
} catch (error) {
Alert.alert('Something’s wrong with the app :(')
}
}

Categories

Resources