Skip to content

Commit db41d3c

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 db41d3c

2 files changed

Lines changed: 64 additions & 0 deletions

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: 57 additions & 0 deletions
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 {
@@ -414,18 +427,62 @@ class DotlottieReactNativeView(context: ThemedReactContext) : FrameLayout(contex
414427

415428
override fun onDetachedFromWindow() {
416429
super.onDetachedFromWindow()
430+
hasAttachedToWindow = false
431+
removeCallbacks(measureAndLayout)
417432
cleanup()
418433
}
419434

420435
override fun onAttachedToWindow() {
421436
super.onAttachedToWindow()
437+
hasAttachedToWindow = true
422438
if (isReleased) {
423439
return
424440
}
425441
ensureStateMachineListener()
426442
if (!hasActiveComposition) {
427443
renderContent()
428444
}
445+
scheduleMeasureAndLayout()
446+
}
447+
448+
override fun requestLayout() {
449+
super.requestLayout()
450+
scheduleMeasureAndLayout()
451+
}
452+
453+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
454+
if (hasAttachedToWindow) {
455+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
456+
} else {
457+
setMeasuredDimension(
458+
MeasureSpec.getSize(widthMeasureSpec),
459+
MeasureSpec.getSize(heightMeasureSpec)
460+
)
461+
}
462+
}
463+
464+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
465+
super.onLayout(changed, left, top, right, bottom)
466+
layoutComposeViewIfReady()
467+
}
468+
469+
private fun layoutComposeViewIfReady() {
470+
if (!hasAttachedToWindow || width <= 0 || height <= 0) {
471+
return
472+
}
473+
composeView.measure(
474+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
475+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
476+
)
477+
composeView.layout(0, 0, width, height)
478+
}
479+
480+
private fun scheduleMeasureAndLayout() {
481+
if (!hasAttachedToWindow) {
482+
return
483+
}
484+
removeCallbacks(measureAndLayout)
485+
post(measureAndLayout)
429486
}
430487

431488
fun release() {

0 commit comments

Comments
 (0)