Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/android-initialize-retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@callstack/react-native-brownfield': patch
---

Android: `ReactNativeBrownfield.initialize` can now be called again after it throws. Before, a failure while loading native libraries or creating the `ReactHost` left it marked as initialized, so the next call returned without error and `ReactNativeBrownfield.shared` was never set.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.soloader.OpenSourceMergedSoMapping
import com.facebook.soloader.SoLoader
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean

fun interface OnJSBundleLoaded {
operator fun invoke(timings: JSBundleTimings)
Expand All @@ -33,17 +32,15 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {

companion object {
private lateinit var instance: ReactNativeBrownfield
private val initialized = AtomicBoolean()
private val nativeLibsLoaded = AtomicBoolean()
private val initialization = RetryableOnce()
private val nativeLibsLoading = RetryableOnce()
private lateinit var applicationContext: Application

@JvmStatic
val shared: ReactNativeBrownfield get() = instance

private fun loadNativeLibs(application: Application) {
if (!nativeLibsLoaded.getAndSet(true)) {
loadNativeLibsInternal(application)
}
nativeLibsLoading.run { loadNativeLibsInternal(application) }
}

private fun loadNativeLibsInternal(application: Application) {
Expand All @@ -65,11 +62,12 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
onJSBundleLoaded: OnJSBundleLoaded? = null
) {
applicationContext = application
if (!initialized.getAndSet(true)) {
val ranNow = initialization.run {
AndroidJSBundleTimingObserver.install()
loadNativeLibs(application)
installAndPreload(reactHost, onJSBundleLoaded)
} else {
}
if (!ranNow) {
invokeWhenJSBundleLoaded(onJSBundleLoaded)
}
}
Expand All @@ -81,11 +79,12 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
reactHostFactory: () -> ReactHost
) {
applicationContext = application
if (!initialized.getAndSet(true)) {
val ranNow = initialization.run {
AndroidJSBundleTimingObserver.install()
loadNativeLibs(application)
installAndPreload(reactHostFactory(), onJSBundleLoaded)
} else {
}
if (!ranNow) {
invokeWhenJSBundleLoaded(onJSBundleLoaded)
}
}
Expand All @@ -98,7 +97,7 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
onJSBundleLoaded: OnJSBundleLoaded? = null
) {
applicationContext = application
if (!initialized.getAndSet(true)) {
val ranNow = initialization.run {
AndroidJSBundleTimingObserver.install()
loadNativeLibs(application)
val reactHost = getDefaultReactHost(
Expand All @@ -114,7 +113,8 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
jsRuntimeFactory = null
)
installAndPreload(reactHost, onJSBundleLoaded)
} else {
}
if (!ranNow) {
invokeWhenJSBundleLoaded(onJSBundleLoaded)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.callstack.reactnativebrownfield

import java.util.concurrent.atomic.AtomicBoolean

/**
* Runs a block at most once, counting only a run that completes.
*
* If the block throws, the next [run] call runs it again instead of treating
* the work as done. Calls made while a run is in progress are skipped.
*/
internal class RetryableOnce {
private val claimed = AtomicBoolean()

/** Returns `true` when [block] ran and completed during this call. */
fun run(block: () -> Unit): Boolean {
if (claimed.getAndSet(true)) {
return false
}
try {
block()
} catch (error: Throwable) {
claimed.set(false)
throw error
}
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.callstack.reactnativebrownfield

import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Test

class RetryableOnceTest {
@Test fun `runs the block once after it completes`() {
val once = RetryableOnce()
var runs = 0

assertTrue(once.run { runs++ })
assertFalse(once.run { runs++ })
assertEquals(1, runs)
}

@Test fun `runs the block again after it throws`() {
val once = RetryableOnce()
var runs = 0

try {
once.run {
runs++
throw UnsatisfiedLinkError("dlopen failed")
}
fail("expected the error to propagate")
} catch (expected: UnsatisfiedLinkError) {
assertEquals("dlopen failed", expected.message)
}

assertTrue(once.run { runs++ })
assertFalse(once.run { runs++ })
assertEquals(2, runs)
}
}
Loading