Home > Net >  React Native Android app's production build fails to start up
React Native Android app's production build fails to start up

Time:10-05

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.

  • Related