See the Summary section.
- Background
- Ejecting my project
- Post-Eject "Additional Steps" From Output of
expo eject
- Let's get compiling
- Debugging in Android Studio
- Summary
I have an Expo app, built with the Managed Workflow. It runs great on iOS and mostly fine on Android...except that the app has a tendency to red-screen in the Android emulator (and crash/restart on a physical device).
The stack trace indicates: com.facebook.react.bridge.ReadableNativeMap cannot be cast to java.lang.String
Googling around didn't help much. Speaking with Very Helpful folks on the Expo Slack server, it was recommended to try debugging this at the Java level. I know Java development!
- (...enterprise Java development...)
- (...with Eclipse...)
My app uses Google Firebase for Authentication, Firestore, and Cloud Storage.
The nature of the crashes was less-than-predictable. Sometimes it would crash within minutes of the app starting and me clicking around. Sometimes if I just started the app and logged in, then let the app sit...and sit...and sit... it might crash after an hour.
The one consistent thing was the stack trace. It was always the above exception message with the fuller stack trace being:
09-29 17:21:00.008 876 876 D KeyguardClockSwitch: Updating clock: 5ξΈ21
09-29 17:21:14.498 470 470 E netmgr : Failed to open QEMU pipe 'qemud:network': Invalid argument
09-29 17:21:15.490 485 485 E wifi_forwarder: RemoteConnection failed to initialize: RemoteConnection failed to open pipe
09-29 17:21:30.779 19667 8549 W st.exp.exponen: Accessing hidden method Lcom/android/org/conscrypt/ConscryptEngineSocket;->setUseSessionTickets(Z)V (blacklist,core-platform-api, reflection, denied)
09-29 17:21:30.780 19667 8549 W st.exp.exponen: Accessing hidden method Lcom/android/org/conscrypt/ConscryptEngineSocket;->setHostname(Ljava/lang/String;)V (blacklist,core-platform-api, reflection, denied)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: Exception in native call
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: java.lang.ClassCastException: abi38_0_0.com.facebook.react.bridge.ReadableNativeMap cannot be cast to java.lang.String
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.ReadableNativeArray.getString(ReadableNativeArray.java:1)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.JavaMethodWrapper$5.extractArgument(JavaMethodWrapper.java:2)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.JavaMethodWrapper$5.extractArgument(JavaMethodWrapper.java:1)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:16)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:2)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.queue.NativeRunnable.run(Native Method)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at android.os.Handler.handleCallback(Handler.java:938)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at android.os.Handler.dispatchMessage(Handler.java:99)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:1)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at android.os.Looper.loop(Looper.java:223)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at abi38_0_0.com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:8)
09-29 17:21:30.951 19667 7454 E unknown:ReactNative: at java.lang.Thread.run(Thread.java:923)
09-29 17:21:30.975 19667 19667 D ReactNative: CatalystInstanceImpl.destroy() start
09-29 17:21:30.975 19667 19667 D ReactNative: CatalystInstanceImpl.destroyV1() start
09-29 17:21:30.976 19667 7454 I ReactNative: [GESTURE HANDLER] Tearing down gesture handler registered for root view abi38_0_0.host.exp.exponent.ReactUnthemedRootView{7a989b8 V.E...... ........ 0,0-1440,2792 #f1}
Having avoided platform-specific coding (that's why I went with Expo/React-Native), I was at the mercy of others. But following the advice from the Slack channel, I was empowering myself! (damn...)
Here is my experience getting from an Expo Managed Workflow running blistfully-platform-unaware with simple commands like
expo start
, to a Bare Workflow running with a native platform debugger via Android Studio π¬.
Cedric from the Expo team pointed me to the starting docs for Expo Eject (the "bare workflow"). I followed the steps, but ran into problems (that I detail, and work out(!!), below):
-
cd ~/work/
-
cp -a rn_tick8s fred
<<-- my project's name is 'rn_tick8s' -
cd fred
<<-- folder we'll do the eject work in -
expo eject
β expo eject Your git working tree is clean To revert the changes after this command completes, you can run the following: git clean --force && git reset --hard π Android package Learn more. ? What would you like your Android package name to be? com.modeldriventhinking.tick8s.roman_stage ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ π iOS Bundle Identifier Learn more. ? What would you like your iOS bundle identifier to be? com.modeldriventhinking.tick8s.roman-stage ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ β Created native projects β Added Metro bundler configuration. β Updated package.json and added index.js entry point for iOS and Android. π§Ά Using Yarn to install packages. You can pass --npm to use npm instead. β Cleaned JavaScript dependencies. β Installed JavaScript dependencies. β Ή iOS config syncing Using node to generate images. This is much slower than using native packages. βΊ Optionally you can stop the process and try again after successfully running `npm install -g sharp-cli`. β οΈ iOS config synced with warnings that should be fixed: - splash: Unable to automatically configure splash screen. Please refer to the expo-splash-screen README for more information: https://github.com/expo/expo/tree/ master/packages/expo-splash-screen β οΈ Android config synced with warnings that should be fixed: - splash: Unable to automatically configure splash screen. Please refer to the expo-splash-screen README for more information: https://github.com/expo/expo/tree/ master/packages/expo-splash-screen β Installed pods and initialized Xcode workspace. β οΈ Your app includes 3 packages that require additional setup in order to run: - expo-camera: https://github.com/expo/expo/tree/master/packages/expo-camera - expo-constants: Constants.manifest is not available in the bare workflow. You should replace it with Updates.manifest. Learn more. - expo-image-picker: https://github.com/expo/expo/tree/master/packages/expo-image-picker β‘οΈ Next steps - π‘ You may want to run npx @react-native-community/cli doctor to help install any tools that your app may need to run your native projects. - π Download your Android keystore (if you're not sure if you need to, just run the command and see): expo fetch:android:keystore - π The property assetBundlePatterns does not have the same effect in the bare workflow. Learn more. βοΈ When you are ready to run your project To compile and run your project in development, execute one of the following commands: - yarn ios - yarn android - yarn web
-
Added the expo-camera settings to
android/build.gradle
- added
maven { // expo-camera bundles a custom com.google.android:cameraview url "$rootDir/../node_modules/expo-camera/android/maven" }
-
Added the expo-camera settings to
android/app/src/main/AndroidManifest.xml
-
Removed the reference to
expo-constants
frompackage.json
(it wasn't being used, I had already replaced all of its instances withUpdates.manifest
fromexpo-updates
) -
Added the
<activity>
entries forexpo-image-picker
toAndroidManifest.xml
- added
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity" android:theme="@style/Base.Theme.AppCompat"> </activity>
-
Run
npx @react-native-community/cli doctor
β npx @react-native-community/cli doctor Common β Node.js β yarn β Watchman - Used for watching changes in the filesystem when in development mode Android β JDK β Android Studio - Required for building and installing your app on Android β Android SDK - Required for building and installing your app on Android β ANDROID_HOME iOS β Xcode - Required for building and installing your app on iOS β CocoaPods - Required for installing iOS dependencies β ios-deploy - Required for installing your app on a physical device with the CLI Errors: 0 Warnings: 1 Usage βΊ Press f to try to fix issues. βΊ Press e to try to fix errors. βΊ Press w to try to fix warnings. βΊ Press Enter to exit.
I pressed ENTER because I don't care (right now) about building for iOS, just trying to debug Android.
-
Run
expo fetch:android:keystore
β expo fetch:android:keystore Accessing credentials for greg.fenton in project ******** A file already exists at "/Users/greg/nosync/fred/********.jks" Renaming the existing file to OLD_1_tick8s-roman-stage.jks Saving Keystore to /Users/greg/nosync/fred/tick8s-roman-stage.jks Keystore credentials Keystore password: 9******************************c Key alias: Q****************************************Q== Key password: 7******************************0 Path to Keystore: /Users/greg/nosync/fred/********.jks
-
FYI: not sure what to do about
assetBundlePatterns
inapp.json
. Current value is:"assetBundlePatterns": [ "**/*" ],
so am leaving it as-is for now.
-
yarn android
β yarn android yarn run v1.22.4 $ react-native run-android info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag. Jetifier found 1793 file(s) to forward-jetify. Using 12 workers... info Starting JS server... info Launching emulator... info Successfully launched emulator. info Installing the app... Starting a Gradle Daemon (subsequent builds will be faster) java.lang.NoClassDefFoundError: Could not initialize class org.codehaus.groovy.vmplugin.v7.Java7 at org.codehaus.groovy.vmplugin.VMPluginFactory.<clinit>(VMPluginFactory.java:43) at org.codehaus.groovy.reflection.GroovyClassValueFactory.<clinit>(GroovyClassValueFactory.java:35) at org.codehaus.groovy.reflection.ClassInfo.<clinit>(ClassInfo.java:107) at org.codehaus.groovy.reflection.ReflectionCache.getCachedClass(ReflectionCache.java:95) // // ... // at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) at java.base/java.lang.Thread.run(Thread.java:832) FAILURE: Build failed with an exception. * What went wrong: Could not initialize class org.codehaus.groovy.reflection.ReflectionCache * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 2s error Failed to install the app. Make sure you have the Android development environment set up: https://reactnative.dev/docs/environment-setup. Run CLI with --verbose flag for more details. Error: Command failed: ./gradlew app:installDebug -PreactNativeDevServerPort=8081 java.lang.NoClassDefFoundError: Could not initialize class org.codehaus.groovy.vmplugin.v7.Java7 error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
- more input from @bycedric on Slack (thanks Cedric!) - dev environment requirements are available from the
React Native team: https://reactnative.dev/docs/environment-setup
- NOTE: on that page, you want to click the correct "tabs" (not necessarily intuitive):
React Native CLI Quickstart
(notExpo CLI Quickstart
)macOS
(orWindows
, orLinux
)Android
(I'm trying to debug Android; don't care aboutiOS
at this time)
- NOTE: on that page, you want to click the correct "tabs" (not necessarily intuitive):
- I followed the instructions above.
- Found that I had Android SDK 9 installed, but not SDK 10 (Q).
- Ensure to install the correct SDK, and SDK Tools (all as per the instructions)
- Ensure you update your shell (bash, zsh, etc.) correctly, and that the shell you are running in has the enviroment
variables (
ANDROID_HOME
andPATH
) set correctly.
- more input from @bycedric on Slack (thanks Cedric!) - dev environment requirements are available from the
React Native team: https://reactnative.dev/docs/environment-setup
-
In the shell running the Metro server (started with the previous
yarn android
command), pressctrl-c
to exit the server and pressENTER
to close the terminal window -
yarn android
$ yarn android yarn run v1.22.4 $ react-native run-android info Running jetifier to migrate libraries to AndroidX. You can disable it using "--no-jetifier" flag. Jetifier found 1793 file(s) to forward-jetify. Using 12 workers... info Starting JS server... info Installing the app... FAILURE: Build failed with an exception. * What went wrong: Could not initialize class org.codehaus.groovy.runtime.InvokerHelper * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 522ms error Failed to install the app. Make sure you have the Android development environment set up: https://reactnative.dev/docs/environment-setup. Run CLI with --verbose flag for more details. Error: Command failed: ./gradlew app:installDebug -PreactNativeDevServerPort=8081 error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
Googling I found these (the SO article points to the GH issue):
- https://stackoverflow.com/questions/60844245/how-solve-could-not-initialize-class-org-codehaus-groovy-reflection-reflectionc
- gradle/gradle#10248 Following one of the most recent entries on the GH issue:
- Edit android > gradle / wrapper > gradle-wrapper.properties
- update
distributionUrl
todistributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
- update
-
In the shell running the Metro server (started with the previous
yarn android
command), pressctrl-c
to exit the server and pressENTER
to close the terminal window -
Run
yarn android
(third time is the charm! π) (it is building!!!!! . . . . . .) -
Metro server is up. App splash screen (the default one, not my custom one...will fix later) loads. But then Metro has stack trace:
[Thu Oct 01 2020 14:53:20.834] BUNDLE ./index.js error: Error: Unable to resolve module `./App` from `index.js`: None of these files exist: * App(.native|.android.js|.native.js|.js|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx) * App/index(.native|.android.js|.native.js|.js|.android.json|.native.json|.json|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx) at ModuleResolver.resolveDependency (/Users/greg/nosync/fred/node_modules/metro/src/node-haste/DependencyGraph/ModuleResolution.js:163:15) at ResolutionRequest.resolveDependency (/Users/greg/nosync/fred/node_modules/metro/src/node-haste/DependencyGraph/ResolutionRequest.js:52:18) at DependencyGraph.resolveDependency (/Users/greg/nosync/fred/node_modules/metro/src/node-haste/DependencyGraph.js:287:16) at Object.resolve (/Users/greg/nosync/fred/node_modules/metro/src/lib/transformHelpers.js:267:42) at /Users/greg/nosync/fred/node_modules/metro/src/DeltaBundler/traverseDependencies.js:434:31 at Array.map (<anonymous>) at resolveDependencies (/Users/greg/nosync/fred/node_modules/metro/src/DeltaBundler/traverseDependencies.js:431:18) at /Users/greg/nosync/fred/node_modules/metro/src/DeltaBundler/traverseDependencies.js:275:33 at Generator.next (<anonymous>) at asyncGeneratorStep (/Users/greg/nosync/fred/node_modules/metro/src/DeltaBundler/traverseDependencies.js:87:24)
-
in my
<PROJECT_ROOT>/package.json
I have:"main": "src/app/layout/App.js",
-
in my file
<PROJECT_ROOT>/index.js
I have:import {registerRootComponent} from 'expo'; import App from './App'; // registerRootComponent calls AppRegistry.registerComponent('main', () => App); // It also ensures that whether you load the app in the Expo client or in a native build, // the environment is set up appropriately registerRootComponent(App);
Though this worked under an Expo Managed Workflow, it isn't working in this Bare Workflow.
-
edit line #2 of
<PROJECT_ROOT>/index.js
:import {registerRootComponent} from 'expo'; import App from './src/app/layout/App'; // registerRootComponent calls AppRegistry.registerComponent('main', () => App); // It also ensures that whether you load the app in the Expo client or in a native build, // the environment is set up appropriately registerRootComponent(App);
-
If you kept Metro running, in its window press
r
to reload the JS code. If you had shut it down, then runyarn android
.- WE HAVE GREAT SUCCESS!
- app running in emulator!
- log in, data shows. GREAT SUCCESS! (/insert happy dance here)
-
Shut down Metro with
ctrl-c
- Start Android Studio
- In its startup dialog, choose Open Existing Project.
- browse to
<PROJECT_ROOT>/android
and click the Open button
- browse to
- (~ 2 minutes) Wait....wait a while. Studio opened for me showing just a couple of nodes in the Project (left-hand panel). But it was busy in the background building and inspecting the project. Let it do its thing. Eventually it finished and it was clearly aware of the project, the emulator. Lots of nodes listed in the project It was ready to run!
- I enabled all Java breakpoints: Run Β» View Breakpoints... Β» Java Exception Breakpoints Β» Any exception
then enable Enabled and Suspend (Thread). For my purposes, I also enabled Class filters and added
two classes I am interested in:
- com.facebook.react.bridge.ReadableNativeMap
- com.facebook.react.bridge.ReadableNativeArray
- the app apparently still needs to connect to Metro to connect to. So I started it with
yarn start
. - I ran my app for a while (approx 10 minutes), and then Android Studio caught the exception(!!!)
- ends up that the object attempted to convert to a string was:
and having that data confirmed that these crashes are likely the result of expo issue #7371, which itself references this comment on{ "NativeMap": { "toString": null, "b": { "toString": null, "set": null, "C": null, "K": null, "add": null, "f": false, "c": null, "b": 7, "a": { "forEach": null, "set": null, "get": null, "K": null, "C": null, "c": 7, "a": ["database", "VER", "gsessionid", "SID", "RID", "TYPE", "zx"], "b": { "zx": ["8**********8"], "TYPE": ["terminate"], "RID": [5*****7], "SID": ["8********************w"], "gsessionid": ["v******************************I"], "VER": [8], "database": ["projects/<MY_PROJECT_ID>/databases/(default)"] } }, "get": null, "forEach": null }, "a": false, "i": "", "g": "/google.firestore.v1.Firestore/Write/channel", "h": null, "c": "firestore.googleapis.com", "j": "", "f": "https" } }
expo/browser-polyfill
issue #35 that shows the exact same data structure as the JSON string above.
- ends up that the object attempted to convert to a string was:
Essentially, it looks like expo/browser-polyfill
changes the React Native environment such that Firebase believes it is
running in a web browser. And with that change, I think, what is happening is that Firebase mimics fetching an image
as a form of "keep-alive" (or "session ping"). But the "browser" doesn't really behave like one and unexpected
interruptions cause React Native's bridge to get "bad data" (...I'm sure I could dig for more specifics, but I
have what I need for now: ditch expo-pixi
/expo/browser-polyfill
since I am not giving up on Firebase).
My app had been running on Android fine for quite some time. I have been doing a lot of tweaks to existing functionality in
the past month - mainly UI tweaks and styling consistency and some code cleanup. I noticed the crashing a couple of weeks
ago in development but ignored them to get a new beta-release out (and things were running fine on iOS). The one major feature that I did add in the past few weeks (why didn't it occur to me earlier??) was Signature Capture using expo-pixi
.
For more reading on specifics, see:
I captured all of the above steps to detail out to:
- expo eject a managed workflow project into a bare workflow
- configure a Android Studio development environment
- run the bare workflow from the command line
- run the bare workflow from Android Studio
- debug the bare workflow from Android Studio, capturing exceptions without terminating the app so that you can inspect the conditions around the exception
Conclusion: At this time (expo SDK 38 & SDK 39, Firebase v7.9.0) expo-pixi
(or, more precisely, expo/browser-polyfill
) and the Firebase SDK do not play well together.