React Native - Webview call React Native function - javascript

Is that possible create a function inside the WebView component, trigger React Native function?

It's possible but I'm not sure if it's the only way to do this.
Basically you can set an onNavigationStateChange event handler, and embed function call information in navigation url, here's an example of the concept.
In React Native context
render() {
return <WebView onNavigationStateChange={this._onURLChanged.bind(this)} />
}
_onURLChanged(e) {
// allow normal the natvigation
if(!e.url.startsWith('native://'))
return true
var payload = JSON.parse(e.url.replace('native://', ''))
switch(e.functionName) {
case 'toast' :
native_toast(e.data)
break
case 'camera' :
native_take_picture(e.data)
break
}
// return false to prevent webview navitate to the location of e.url
return false
}
To invoke native method, use this just trigger webview's navigation event and embed the function call information in URL.
window.location = 'native://' + JSON.stringify({
functionName : 'toast', data : 'show toast text'
})

use onMessage eventListner on <WebView/>
<WebView onMessage={onMessage} ... />
/** on message from webView -- window.ReactNativeWebView?.postMessage(data) */
const onMessage = event => {
const {
nativeEvent: {data},
} = event;
if (data === 'goBack') {
navigation.goBack();
} else if (data?.startsWith('navigate')) {
// navigate:::routeName:::stringifiedParams
try {
const [, routeName, params] = data.split(':::');
params = params ? JSON.parse(params) : {};
navigation.navigate(routeName, params);
} catch (err) {
console.log(err);
}
}
};
use this in your HTML to post message event
window.ReactNativeWebView?.postMessage("data")

You could inject a javascript function to the webview on load and then use onMessage to get response from the function you injected more info IN Here

yes it's possible , it existe a package for that react-native-webview-bridge.
I used it heavily in production and it works perfectly.

I am not sure, but my opinion is -
You can not. Webview can load only js part which we can define in Webview component. This is totally separate than other components, it is only just a viewable area.

Related

Request Body Missing in Spring while updating using PATCH API in react js

Spring shows - Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.cg.bookstore.entities.OrderDetails com.cg.bookstore.controller.OrderDetailsController.updateDeliveryStatus(int,java.lang.String)]
Console shows - Uncaught (in promise) Error: Request failed with status code 400
class UpdateOrder extends Component {
state = {
deliveryStatus:""
}
handleChange = (event) => {
const deliveryStatus = { ...this.state.deliveryStatus };
this.setState({ deliveryStatus: event.target.value });
};
handleSubmit = (event) => {
// Prevents default behaviour of submit button
event.preventDefault();
console.log(this.state.deliveryStatus)
console.log()
OrderService.updateDeliveryStatus(this.props.match.params.orderDetailsId,this.state.deliveryStatus)
.then((res) => {
this.props.history.push("/admin/orders");
});
};
In OrderService I call the updateDeliveryStatus
async updateDeliveryStatus(orderId,deliveryStatus){
return await axios.patch(BASE_URL+"/"+orderId,deliveryStatus)
}
The updateDeliveryStatus service in spring
#Override
public OrderDetails updateDeliveryStatus(int orderId, String deliveryStatus)
{
Optional<OrderDetails> opt = orderDetailsRepo.findById(orderId);
OrderDetails od;
if (opt.isPresent())
{
od = opt.get();
od.setDeliveryStatus(deliveryStatus);
orderDetailsRepo.save(od);
} else
{
throw new OrderDetailsNotFoundException("Order is not found");
}
return od;
}
While I was testing backend in POSTMAN , I pass the input as plain string and it works fine. Is it because the input in not in form of json the issue? How to fix this ?
Usually, when using #PutMethod and wanting to update a resource you need to provide both ID of the resource you want to update and the new body, which in this case I presume is 'OrderDetails', error suggests is missing there.
Without java controller code it's only assumptions though.

Using Google One Tap in Angular

I'd like to use Google One Tap in my Angular 11 app. Following the documentation I added <script async defer src="https://accounts.google.com/gsi/client"></script> to my html and then used the following code in my app.component.html:
<div id="g_id_onload"
data-client_id="MY_GOOGLE_CLIENT_ID"
data-callback="handleCredentialResponse",
data-cancel_on_tap_outside="false">
</div>
The popup works fine, though I can't seem to log in. If I create a function handleCredentialResponse in app.component.ts, I get the following error: [GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored.
If I instead try to use the JavaScript API, Typescript throws the following error: Property 'accounts' does not exist on type 'typeof google'
What should I do to be able to using Google One Tap in Angular?
I had a similar problem when I used the HTML API approach, so I ended up using the JavaScript API instead.
Here's what I did:
First, make sure to install the #types/google-one-tap package.
As you mentioned, I'm also importing the script in my index.html file, like so:
<body>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<app-root></app-root>
</body>
Now, moving on to your main component which in my case is app.component.ts, import the following first:
import { CredentialResponse, PromptMomentNotification } from 'google-one-tap';
Then, you can add this on the ngOnInit(). Make sure to read the documentation to get more details on the onGoogleLibraryLoad event:
// #ts-ignore
window.onGoogleLibraryLoad = () => {
console.log('Google\'s One-tap sign in script loaded!');
// #ts-ignore
google.accounts.id.initialize({
// Ref: https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
client_id: 'XXXXXXXX',
callback: this.handleCredentialResponse.bind(this), // Whatever function you want to trigger...
auto_select: true,
cancel_on_tap_outside: false
});
// OPTIONAL: In my case I want to redirect the user to an specific path.
// #ts-ignore
google.accounts.id.prompt((notification: PromptMomentNotification) => {
console.log('Google prompt event triggered...');
if (notification.getDismissedReason() === 'credential_returned') {
this.ngZone.run(() => {
this.router.navigate(['myapp/somewhere'], { replaceUrl: true });
console.log('Welcome back!');
});
}
});
};
Then, the handleCredentialResponse function is where you handle the actual response with the user's credential. In my case, I wanted to decode it first. Check this out to get more details on how the credential looks once it has been decoded: https://developers.google.com/identity/gsi/web/reference/js-reference#credential
handleCredentialResponse(response: CredentialResponse) {
// Decoding JWT token...
let decodedToken: any | null = null;
try {
decodedToken = JSON.parse(atob(response?.credential.split('.')[1]));
} catch (e) {
console.error('Error while trying to decode token', e);
}
console.log('decodedToken', decodedToken);
}
I too had the same problem in adding the function to the angular component.
Then i found a solution by adding JS function in appComponent like this:
(window as any).handleCredentialResponse = (response) => {
/* your code here for handling response.credential */
}
Hope this help!
set the div in template to be rendered in ngOnInit
`<div id="loginBtn" > </div>`
dynamically inject script tag in your login.ts as follows
constructor(private _renderer2: Renderer2, #Inject(DOCUMENT) private _document: Document){}
ngAfterViewInit() {
const script1 = this._renderer2.createElement('script');
script1.src = `https://accounts.google.com/gsi/client`;
script1.async = `true`;
script1.defer = `true`;
this._renderer2.appendChild(this._document.body, script1);
}
ngOnInit(): void {
// #ts-ignore
window.onGoogleLibraryLoad = () => {
// #ts-ignore
google.accounts.id.initialize({
client_id: '335422918527-fd2d9vpim8fpvbcgbv19aiv98hjmo7c5.apps.googleusercontent.com',
callback: this.googleResponse.bind(this),
auto_select: false,
cancel_on_tap_outside: true,
})
// #ts-ignore
google.accounts!.id.renderButton( document!.getElementById('loginBtn')!, { theme: 'outline', size: 'large', width: 200 } )
// #ts-ignore
google.accounts.id.prompt();
}
}
async googleResponse(response: google.CredentialResponse) {
// your logic goes here
}
Google One Tap js library tries to find callback in the global scope and can't find it, because your callback function is scoped somewhere inside of your app, so you can attach your callback to window, like window.callback = function(data) {...}.
Also, since you are attaching it to window, it's better to give the function a less generic name.

React Native: This is not a function handling custom URL scheme

I have followed a tutorial on how to handle a custom URL scheme. This is the way I have it set up.
componentDidMount() {
Linking.addEventListener('url', this.handleOpenURL);
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleOpenURL);
}
handleOpenURL(event) {
console.log(event.url);
this.abc()
}
abc() {
console.log("Hello World");
}
handleOpenUrl function is being called, but function abc isn't. I click on a today widget button which opens my app with a custom URL from background to foreground. I am getting the error message "this.abc is not a function" on my iPhone simulator. I am new to react native and not sure why this is. I think maybe the script hasn't loaded or something when I go from background to foreground in my app.
You have to bind handleOpenURL to your component.
Replace
handleOpenURL(event) {
console.log(event.url);
this.abc()
}
with
handleOpenURL = (event) => {
console.log(event.url);
this.abc()
}

Detect if the user is connected to the internet?

I'd like to route the user to a certain screen, in case he is not connected to the internet.
I just can't detect if he is connected or not.
I tried this code, but did not work:
async componentWillMount()
{
if (!await NetInfo.isConnected)
{
this.props.navigation.navigate('Saved');
}
}
Any tested solution to suggest?
Try await NetInfo.isConnected.fetch()
ref : https://facebook.github.io/react-native/docs/netinfo.html#isconnected
You can check using NetInfo .
for that you have to add connectionChange event listener like this
componentDidMount() {
NetInfo.isConnected.addEventListener('connectionChange', this.handleConnectionChange.bind(this));
NetInfo.isConnected.fetch().done(
(isConnected) => { this.setState({ isConnected: isConnected }); }
);
and then remove the event listener in componentWillUnmount
componentWillUnmount() {
NetInfo.isConnected.removeEventListener('connectionChange', this.handleConnectionChange);
}
And finally the handler method for connection change. I am storing the status in device local storage you can do whatever you want.
handleConnectionChange = (isConnected) => {
if (isConnected) {
//ToastAndroid.show('Data sync in process...', ToastAndroid.SHORT);
AsyncStorage.getItem('offlineData')
.then((json) => JSON.parse(json))
.then((data) => {
console.log(JSON.stringify(data));
});
}
else { ToastAndroid.show('You are offline.', ToastAndroid.SHORT); }
this.setState({ isConnected: isConnected });
}
Don't forget to add NetInfo from react-native :)
Another solution to your case (one without using isConnected property) is to use the object returned from the event handler directly like that:
componentDidMount() {
NetInfo.addEventListener('connectionChange', this.handleNetworkChange);
}
componentWillUnmount() {
NetInfo.removeEventListener('connectionChange', this.handleNetworkChange);
}
handleNetworkChange = (info) => {
if (info.type === 'none') {
this.props.navigation.navigate('Saved');
}
};
According to NetInfo documentation:
connectionChange event fires when the network status changes. The argument to the event handler is an object with keys:
type: A ConnectionType (listed above)
effectiveType: An EffectiveConnectionType (listed above)
The connection type can be one of the following : none, wifi, cellular, unknown.
Ideally you can store this information to your redux store and the listener to a root component.
We had a weird bug when using isConnected similar to the one you mentioned #Gabriel Bleu but for us, the NetInfo.isConnected.fetch() returned false only when the Android device was awake after some period of inactivity.We used it to display offline warning for users, so the warning never left. I found this solution on a Spencer Carli's course and it seems to work better but depending on your needs, you might want to use isConnected combined with the above code.
This is a great example to check online or offline and even you can have connection change information too. Source
NetInfo.isConnected.fetch().then(isConnected => {
console.log('First, is ' + (isConnected ? 'online' : 'offline'));
});
function handleFirstConnectivityChange(isConnected) {
console.log('Then, is ' + (isConnected ? 'online' : 'offline'));
NetInfo.isConnected.removeEventListener(
'connectionChange',
handleFirstConnectivityChange
);
}
NetInfo.isConnected.addEventListener(
'connectionChange',
handleFirstConnectivityChange
);
There are two issues with your code currently.
In newer versions of react life-cycle method componentWillMount is deprecated.
Newer versions of react-native have extracted the NetInfo Module out of the core. Use #react-native-community/netinfo instead.
In order to achieve the desired behavior you should do something like this.
import NetInfo from "#react-native-community/netinfo";
class CheckConnection extends Component {
componentDidMount() {
NetInfo.fetch().then(state => {
handleConnectionState(state)
});
}
handleConnectionState(state) {
console.log("Connection type", state.type);
console.log("Is connected?", state.isConnected);
... your code to handle the lack of connection
}
}

Jest: Trying to mock nested functions and redirects in jest, but no avail

We are using a javascript framework(Not react) to render the ui.
main.js
function logout(){
someObj.lock($('#container'), 'Logging out', true);
document.location = app.context + `/${appName}/signout.action?name=${appName}`;
}
function action(event){
switch(event.target){
case 'user:logout':
logout();
break;
case 'user:application':
document.location = app.context + "/";
break;
}
}
module.exports = {
action: action,
logout: logout
}
main.js along with another js file renders a navbar and a dropdown. My intention is to check whether title, dropdown in the navbar is rendered. Also I
am testing whether the browser redirect takes place in the right way.
action method takes an event object and based on its type, either performs signout('user:logout') or redirects to application page('user:application').
tests/main.js
import main from '../main';
describe("some title", () => {
it("some behavior", () => {
let event = {
target: 'user:logout'
}
let app = {
context: ''
}
let appName = 'Some app';
main.logout = jest.fn();
someObj = jest.fn();
someObj.lock = jest.fn();
document.location.assign = jest.fn();
main.action(event);
expect(main.logout).toHaveBeenCalled();
expect(document.location.assign).toBeCalledWith(app.context + `/${appName}/signout.action?name=${appName}`);
})
});
In the test file, I am trying to mock logout function. However it is executing someObj.lock function. someObj is not availabe to tests/main.js
and I am mocking it as well. I'm not sure whether I have to use spyOn instead. I'm using document.location.assign to test for browser redirects.
This test is not working and the terminal is displaying TypeError: Could not parse "/application name/signout.action?name=application name" as URL.
I have spent an entire day testing this feature but to no avail. I need some advice on the best way to test this feature.
Links explored: Intercept navigation change with jest.js (or how to override and restore location.href)
jest documentation

Categories

Resources