Skip to content

Commit e3059a6

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 e3059a6

2 files changed

Lines changed: 60 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: 53 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,58 @@ class DotlottieReactNativeView(context: ThemedReactContext) : FrameLayout(contex
414427

415428
override fun onDetachedFromWindow() {
416429
super.onDetachedFromWindow()
430+
hasAttachedToWindow = false
417431
cleanup()
418432
}
419433

420434
override fun onAttachedToWindow() {
421435
super.onAttachedToWindow()
436+
hasAttachedToWindow = true
422437
if (isReleased) {
423438
return
424439
}
425440
ensureStateMachineListener()
426441
if (!hasActiveComposition) {
427442
renderContent()
428443
}
444+
scheduleMeasureAndLayout()
445+
}
446+
447+
override fun requestLayout() {
448+
super.requestLayout()
449+
scheduleMeasureAndLayout()
450+
}
451+
452+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
453+
if (hasAttachedToWindow) {
454+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
455+
} else {
456+
setMeasuredDimension(
457+
MeasureSpec.getSize(widthMeasureSpec),
458+
MeasureSpec.getSize(heightMeasureSpec)
459+
)
460+
}
461+
}
462+
463+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
464+
super.onLayout(changed, left, top, right, bottom)
465+
layoutComposeViewIfReady()
466+
}
467+
468+
private fun layoutComposeViewIfReady() {
469+
if (!hasAttachedToWindow || width <= 0 || height <= 0) {
470+
return
471+
}
472+
composeView.measure(
473+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
474+
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
475+
)
476+
composeView.layout(0, 0, width, height)
477+
}
478+
479+
private fun scheduleMeasureAndLayout() {
480+
removeCallbacks(measureAndLayout)
481+
post(measureAndLayout)
429482
}
430483

431484
fun release() {

0 commit comments

Comments
 (0)