Skip to content

Commit 67e798f

Browse files
committed
fix: prevent Android "Cannot locate windowRecomposer" crash when measured before window attach
Under the React Native new architecture (Fabric), SurfaceMountingManager measures a screen's views before they attach to the window, so the wrapped Compose `ComposeView` throws "Cannot locate windowRecomposer" during `onMeasure`. Guard `onMeasure` on the host `FrameLayout` to skip child measurement while detached, and force a measure/layout pass once attached (`onAttachedToWindow` / `onLayout` / `requestLayout`) so the animation still composes and renders. Fixes #33.
1 parent 815eed8 commit 67e798f

2 files changed

Lines changed: 67 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@lottiefiles/dotlottie-react-native": patch
3+
---
4+
5+
Fix an Android crash ("Cannot locate windowRecomposer") when a `<DotLottie>` is
6+
measured before its window is attached under the React Native new architecture
7+
(Fabric) — e.g. navigating to a screen that renders it via react-native-screens.

android/src/main/java/com/dotlottiereactnative/DotlottieReactNativeView.kt

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ class DotlottieReactNativeView(context: ThemedReactContext) : FrameLayout(contex
4141
private var stateMachineListenerRegistered: Boolean = false
4242
private var hasActiveComposition: Boolean = false
4343
private var isReleased: Boolean = false
44+
private var hasAttachedToWindow: Boolean = false
45+
46+
private val measureAndLayout =
47+
Runnable {
48+
if (!hasAttachedToWindow || width <= 0 || height <= 0) {
49+
return@Runnable
50+
}
51+
measure(
52+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
53+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
54+
)
55+
layout(left, top, right, bottom)
56+
}
4457

4558
private val composeView: ComposeView =
4659
ComposeView(context).apply {
@@ -50,7 +63,8 @@ class DotlottieReactNativeView(context: ThemedReactContext) : FrameLayout(contex
5063
init {
5164
addView(composeView)
5265
ensureStateMachineListener()
53-
renderContent()
66+
// Composition is created once the view is attached (onAttachedToWindow /
67+
// setSource); creating it here while detached is unnecessary.
5468
}
5569

5670
fun onReceiveNativeEvent(eventName: String, value: WritableMap?) {
@@ -414,18 +428,63 @@ class DotlottieReactNativeView(context: ThemedReactContext) : FrameLayout(contex
414428

415429
override fun onDetachedFromWindow() {
416430
super.onDetachedFromWindow()
431+
hasAttachedToWindow = false
432+
removeCallbacks(measureAndLayout)
417433
cleanup()
418434
}
419435

420436
override fun onAttachedToWindow() {
421437
super.onAttachedToWindow()
438+
hasAttachedToWindow = true
422439
if (isReleased) {
423440
return
424441
}
425442
ensureStateMachineListener()
426443
if (!hasActiveComposition) {
427444
renderContent()
428445
}
446+
scheduleMeasureAndLayout()
447+
}
448+
449+
override fun requestLayout() {
450+
super.requestLayout()
451+
scheduleMeasureAndLayout()
452+
}
453+
454+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
455+
if (!hasAttachedToWindow) {
456+
setMeasuredDimension(
457+
MeasureSpec.getSize(widthMeasureSpec),
458+
MeasureSpec.getSize(heightMeasureSpec)
459+
)
460+
return
461+
}
462+
463+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
464+
}
465+
466+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
467+
super.onLayout(changed, left, top, right, bottom)
468+
layoutComposeViewIfReady()
469+
}
470+
471+
private fun layoutComposeViewIfReady() {
472+
if (!hasAttachedToWindow || width <= 0 || height <= 0) {
473+
return
474+
}
475+
composeView.measure(
476+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
477+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
478+
)
479+
composeView.layout(0, 0, width, height)
480+
}
481+
482+
private fun scheduleMeasureAndLayout() {
483+
if (!hasAttachedToWindow) {
484+
return
485+
}
486+
removeCallbacks(measureAndLayout)
487+
post(measureAndLayout)
429488
}
430489

431490
fun release() {

0 commit comments

Comments
 (0)