I built a small React Native app, which has a WebView
component and notification feature. All other features are implemented in Web App and this React Native app only loads this Web App via WebView
.
This app works perfectly fine when I run this app in an Android emulator or a real Android machine connected via USB. However, the production build in internal test of Google Play store fails to start up without any message. Even a splash screen is not shown. Launching app fails immediately. (By the way, it works well in iOS, both Simulator and real iPhone.)
I'm very frustrated because I don't know from where I start to investigate. First, I suspect that react-native-unimodules
has problems but it is not. I tested my app after removing react-native-unimodules
and the same problem occurred.
My app is super simple and it just has a single file, which contains a WebView
component. My full code is below:
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Alert, BackHandler, SafeAreaView, Platform, TouchableOpacity } from 'react-native'
import { WebView, WebViewMessageEvent } from 'react-native-webview'
import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions'
import * as Notifications from 'expo-notifications'
import { Constants } from 'react-native-unimodules'
const INJECTED = `
(function() {
function wrap(fn) {
return function wrapper() {
var res = fn.apply(this, arguments);
window.ReactNativeWebView.postMessage(JSON.stringify({type: 'navigation'}));
return res;
}
}
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', function() {
window.ReactNativeWebView.postMessage(JSON.stringify({type: 'navigation'}));
});
})();
true;
`
interface PostMessageData {
type: 'navigation' | 'userID'
payload?: { chainID: string; userID: string }
}
function MainView(): JSX.Element {
const webviewRef = useRef<WebView>(null)
const [canGoBack, setCanGoBack] = useState(false)
const backAction = useCallback(() => {
if (canGoBack) {
webviewRef.current?.goBack()
} else {
Alert.alert('Exit', 'Do you want to exit the app?', [
{
text: 'Cancel',
onPress: () => null,
style: 'cancel',
},
{ text: 'Exit', onPress: () => BackHandler.exitApp() },
])
}
return true
}, [canGoBack])
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', backAction)
return () => BackHandler.removeEventListener('hardwareBackPress', backAction)
}, [backAction])
useEffect(() => {
if (Constants.isDevice) {
const _setNotification = async () => {
const result = await checkIfNotificationPossible()
if (result) {
try {
const experienceId = '...'
const { data } = await Notifications.getExpoPushTokenAsync({ experienceId })
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'default',
importance: Notifications.AndroidImportance.MAX,
vibrationPattern: [0, 250, 250, 250],
lightColor: '#FF231F7C',
})
}
} catch (err) {
console.error(err)
}
}
}
_setNotification()
}
}, [])
const messageHandler = (event: WebViewMessageEvent) => {
const data: PostMessageData = JSON.parse(event.nativeEvent.data)
if (data.type === 'navigation') {
setCanGoBack(event.nativeEvent.canGoBack)
}
}
return (
<SafeAreaView style={{ flex: 1 }}>
<WebView
ref={webviewRef}
originWhitelist={['*']}
source={{ uri: 'uri/to/my/webapp' }}
injectedJavaScript={INJECTED}
onMessage={messageHandler}
allowsBackForwardNavigationGestures
pullToRefreshEnabled
/>
</SafeAreaView>
)
}
async function checkIfNotificationPossible(): Promise<boolean> {
const { status: result } = await checkNotifications()
if (result === RESULTS.BLOCKED || result === RESULTS.UNAVAILABLE) {
return false
} else if (result === RESULTS.DENIED) {
const { status: requestResult } = await requestNotifications(['alert', 'badge', 'sound'])
if (requestResult === RESULTS.BLOCKED) {
return false
}
}
return true
}
export default MainView
Please let me know from where should I investigate? If you need additional information to diagnose, please let me know via comments. Thanks.
===EDIT=== I attached my gradle file.
apply plugin: "com.android.application"
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
import com.android.build.OutputFile
project.ext.react = [
enableHermes: false, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc: '
def enableHermes = project.ext.react.get("enableHermes", false);
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "..."
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 21
versionName "1.0.0"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
storeFile file('...')
storePassword '...'
keyAlias '...'
keyPassword '...'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
// Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
defaultConfig.versionCode * 1000 versionCodes.get(abi)
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native: " // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
addUnimodulesDependencies()
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath "hermes-debug.aar")
releaseImplementation files(hermesPath "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply plugin: 'com.google.gms.google-services'
CodePudding user response:
Connect your device to the pc and check the logs in logcat in android studio. You might find the reason for the crash.
If you get an error like bundler not found, try regenerating the apk. This had solved my issue.