Expo 55: Manual Setup

iOS

Supported out of the box from version [email protected] and onwards.

Android

Android

This is a temporary patch until the Expo Team adds support and release a new version of Expo Updates.

Step 1:

In expo-updates/android/build.gradle :

     packagingOptions {
-      pickFirst "**/libfbjni.so"
-      pickFirst "**/libc++_shared.so"
-
       resources.excludes.add("META-INF/LICENSE.md")
       resources.excludes.add("META-INF/LICENSE-notice.md")
     }
Step 2:

In expo-updates/android/**/UpdatesController.kt :

 import android.content.Context
 import com.facebook.react.ReactApplication
+import com.facebook.react.ReactHost
 import expo.modules.updates.events.IUpdatesEventManagerObserver

 @@ -25,6 +26,19 @@ object UpdatesController {
   @Volatile
   private var overrideConfiguration: UpdatesConfiguration? = null
 
+  @Volatile
+  private var reactHost: ReactHost? = null
+
+  @JvmStatic
+  fun setReactHost(reactHost: ReactHost) {
+    this.reactHost = reactHost
+  }
+
+  @JvmStatic
+  fun getReactHost(): ReactHost? {
+    return reactHost
+  }
+
Step 3:

In expo-updates/android/**/UpdatesPackage.kt :

@@ -49,6 +49,10 @@ class UpdatesPackage : Package {
       override fun onReactInstanceException(useDeveloperSupport: Boolean, exception: Exception) {
         UpdatesController.instance.onReactInstanceException(exception)
       }
+
+      override fun onDidCreateReactHost(context: Context, reactNativeHost: ReactHost) {
+        UpdatesController.setReactHost(reactNativeHost)
+      }
     }
     return listOf(handler)
   }
Step 4:

In expo-updates/android/**/RecreateReactContextProcedure.kt :

import android.content.Context
-import com.facebook.react.ReactApplication
 import expo.modules.updates.launcher.Launcher
 import expo.modules.updates.statemachine.UpdatesStateEvent
 import kotlinx.coroutines.CoroutineScope
@@ -20,16 +19,11 @@ class RecreateReactContextProcedure(
   override val loggerTimerLabel = "timer-recreate-react-context"
 
   override suspend fun run(procedureContext: ProcedureContext) {
-    val reactApplication = context.applicationContext as? ReactApplication ?: run inner@{
-      callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize()."))
-      return
-    }
-
     procedureContext.processStateEvent(UpdatesStateEvent.Restart())
     callback.onSuccess()
     procedureScope.launch {
       withContext(Dispatchers.Main) {
-        reactApplication.restart(weakActivity?.get(), "Restart from RecreateReactContextProcedure")
+        RestartReactAppExtensions.restart(weakActivity?.get(), "Restart from RecreateReactContextProcedure")
       }
     }
Step 5:

In expo-updates/android/**/RelaunchProcedure.kt :

import android.content.Context
-import com.facebook.react.ReactApplication
 import expo.modules.updates.UpdatesConfiguration
 import expo.modules.updates.db.DatabaseHolder
 import expo.modules.updates.db.Reaper
@@ -40,11 +39,6 @@ class RelaunchProcedure(
   override val loggerTimerLabel = "timer-relaunch"
 
   override suspend fun run(procedureContext: ProcedureContext) {
-    val reactApplication = context as? ReactApplication ?: run inner@{
-      callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize()."))
-      return
-    }
-
     procedureContext.processStateEvent(UpdatesStateEvent.Restart())
 
     val newLauncher = DatabaseLauncher(
@@ -71,7 +65,7 @@ class RelaunchProcedure(
     procedureScope.launch {
       withContext(Dispatchers.Main) {
         reloadScreenManager?.show(weakActivity?.get())
-        reactApplication.restart(weakActivity?.get(), "Restart from RelaunchProcedure")
+        RestartReactAppExtensions.restart(weakActivity?.get(), "Restart from RelaunchProcedure")
       }
     }
Step 6:

In expo-updates/android/**/RestartReactAppExtensions.kt :

import com.facebook.react.ReactApplication
+import com.facebook.react.ReactHost
 import com.facebook.react.common.LifecycleState
+import expo.modules.updates.UpdatesController
 
-/**
- * An extension for [ReactApplication] to restart the app
- *
- * @param activity For bridgeless mode if the ReactHost is destroyed, we need an Activity to resume it.
- * @param reason The restart reason. Only used on bridgeless mode.
- */
-internal fun ReactApplication.restart(activity: Activity?, reason: String) {
-  val reactHost = this.reactHost
-  check(reactHost != null)
-  if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
-    reactHost.onHostResume(activity)
+private fun getReactHostReflectively(app: ReactApplication): ReactHost? {
+  return runCatching {
+    app.javaClass.getMethod("getReactHost").invoke(app) as? ReactHost
+  }.getOrNull()
+}
+
+object RestartReactAppExtensions {
+  /**
+   * A function to restart the app
+   *
+   * @param activity For bridgeless mode if the ReactHost is destroyed, we need an Activity to resume it.
+   * @param reason The restart reason. Only used on bridgeless mode.
+   */
+  internal fun restart(activity: Activity?, reason: String) {
+    val reactHost = UpdatesController.getReactHost() ?: getReactHostReflectively(activity?.application as ReactApplication)
+    check(reactHost != null)
+    if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
+      reactHost.onHostResume(activity)
+    }
+    reactHost.reload(reason)
   }
-  reactHost.reload(reason)
 }

To see a complete patch, please visit:

Need React or React Native expertise you can count on?