Issue
Trying to connect to my local development server using react-native-webview
on Android.
You can use http://10.0.0.2
as the default gateway to connect to your local machine on the emulator. Alternatively, I ran the build on my phone and connected to the IPV4 address of my machine.
I need the Crypto API and that's only available on HTTPS, which meant I needed to connect to https://10.0.0.2
.
I didn't get the self signed certificate to work.
Quick & dirty solution
Call handler.proceed()
on the first line of the onReceivedSslError
method of the com.reactnativecommunity.webview
package.
Although this might work, it's not the preferred way because it's not under source control and needs to be removed each time a release build is made or the project is freshly installed.
Solution
See answers below
Create a custom webview on development builds to circumvent the SSL error (or any other webview method).
My solution
1. Create a debug only class CustomWebviewManager.java
We don't want our custom webview manager ending up in release builds.
package nl.myapp;
import android.webkit.WebView;
import android.webkit.SslErrorHandler;
import android.net.http.SslError;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.reactnativecommunity.webview.RNCWebViewManager;
@ReactModule(name = CustomWebViewManager.REACT_CLASS)
public class CustomWebViewManager extends RNCWebViewManager {
protected static final String REACT_CLASS = "RNCCustomWebView"; // React native import name
protected static class CustomWebviewClient extends RNCWebViewClient {
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error){
// Prevent SSL errors
handler.proceed();
}
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
// Set our custom client as webview client
view.setWebViewClient(new CustomWebviewClient());
}
}
2. Create a new class to register React Packages MyappAppPackage.java
We should register our debug class only on debug mode and as a ViewManager
.
React Native will call createViewManagers
automatically after registering our package in the next step
Use BuildConfig.DEBUG
to only execute in debug builds.
We cannot instantiate our class directly, as it will cause errors in productions builds. That's why we use Class.forName
package nl.myapp;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyappAppPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = new ArrayList<>();
if (BuildConfig.DEBUG) {
// Add custom webview manager to circumvent SSL errors
try {
Class<ViewManager> c = (Class<ViewManager>) Class.forName("nl.myapp.CustomWebViewManager");
viewManagers.add(c.newInstance());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
return viewManagers;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
3. Register our package
package nl.myapp;
import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import java.util.List;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new MyappAppPackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
...
}
4. And finally use our custom webview while developing
Put it in separate file, otherwise hot-reloading won't work because requireNativeComponent
would try to register the ViewManager
again
src/components/native/RNCCustomWebView.js
import {requireNativeComponent} from 'react-native';
module.exports = requireNativeComponent('RNCCustomWebView');
src/components/webview.js
import React from 'react';
import { WebView } from 'react-native-webview';
import RNCCustomWebView from './native/RNCCustomWebView';
// ...
const renderWebView = () => {
// ...
const nativeConfig = {};
if (__DEV__) { // __DEV__ is set by RN on debug builds
// Set custom component to circumvent SSL errors
nativeConfig.component = RNCCustomWebView;
}
return (
<WebView
nativeConfig={nativeConfig}
source="https://10.0.0.1"
/>
);
};