Skip to content

fix: resolve blur animator race, scroll lag, and Android full-screen blur#102

Merged
DanielAraldi merged 4 commits into
mainfrom
fix/blur-issues-85-89-100-101
May 1, 2026
Merged

fix: resolve blur animator race, scroll lag, and Android full-screen blur#102
DanielAraldi merged 4 commits into
mainfrom
fix/blur-issues-85-89-100-101

Conversation

@sbaiahmed1
Copy link
Copy Markdown
Owner

@sbaiahmed1 sbaiahmed1 commented Apr 25, 2026

Summary

  • iOS Facing issue with ios 26.2 #85 / Blur intensity changes unexpectedly on navigation (iOS) #101BlurEffectView.setupBlur() was calling stopAnimation/finishAnimation inside a DispatchQueue.main.async, leaving a one-frame window where the animator ran at the wrong fractionComplete. This caused blur intensity to flicker on navigation and produced fallback-color flashes when 10+ BlurView instances existed on screen (iOS 26.2). Fix: call both synchronously immediately after setting fractionComplete.

  • iOS Scroll lag when using BlurView #100 — During FlashList recycling, Blur.updateUIView fires on every layout pass even when style and intensity are unchanged. Each call was unconditionally recreating the UIViewPropertyAnimator, causing scroll jank. Fix: early-exit guard in updateBlur() — skip setupBlur() when nothing changed.

  • Android Blur Effect doesnt work on Android it gives full screen blur effect not parent component #89findOptimalBlurRoot() returned null when no com.swmansion.rnscreens.Screen ancestor was found (plain View hierarchies without a Stack navigator). QmBlurView then defaulted to the activity decor view as its blur capture root, blurring the entire screen. Fix: added findNearestReactRootView() fallback that scopes capture to com.facebook.react.ReactRootView instead. Applied to both ReactNativeBlurView and ReactNativeProgressiveBlurView.

Test plan

Summary by CodeRabbit

  • Bug Fixes
    • Android blur now falls back to the app root when screen containers are absent, improving blur coverage.
    • iOS blur animations no longer recreate animators unnecessarily, reducing flicker during transitions and view recycling.
    • Modals: improved blur and dismissal behavior; centered content no longer blocks outside-press interactions.

…blur

- iOS (#85, #101): remove DispatchQueue.main.async in BlurEffectView.setupBlur()
  and stop/finish the UIViewPropertyAnimator synchronously. The async dispatch
  left a one-frame window where the wrong intensity was visible, causing flickers
  on navigation and fallback-color flashes with 10+ instances on iOS 26.2.

- iOS (#100): add early-exit guard in BlurEffectView.updateBlur() to skip
  animator teardown/recreation when style and intensity are unchanged. During
  FlashList recycling, updateUIView fires on every layout pass even with identical
  props, making this guard essential for smooth scrolling.

- Android (#89): add findNearestReactRootView() fallback in ReactNativeBlurView
  and ReactNativeProgressiveBlurView. When no react-native-screens Screen
  ancestor exists, QmBlurView was falling back to the activity decor view as
  blur root, causing the entire screen to blur instead of just the component.
  The new fallback scopes capture to ReactRootView.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 25, 2026

📝 Walkthrough

Walkthrough

Android: broadened blur root resolution to prefer a react-native-screens Screen, then fall back to the nearest ReactRootView before returning null. iOS: avoid redundant animator teardown/recreation by skipping setup when style/intensity unchanged and stabilize animator state when view moves windows.

Changes

Cohort / File(s) Summary
Android Blur Root Selection
android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt, android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt
findOptimalBlurRoot() now tries a Screen ancestor first, then falls back to the nearest com.facebook.react.ReactRootView (via new findNearestReactRootView()), returning null only when neither is found. Updated KDoc to document priority and modal behavior.
iOS Blur Animator & Lifecycle
ios/Views/BlurEffectView.swift
updateBlur early-returns when style/intensity are unchanged to avoid unnecessary animator teardown. didMoveToWindow restores fractionComplete and pauses/resumes animator to prevent drift. setupBlur creates a paused animator deterministically (uses pausesOnCompletion), and deinit simplifies cleanup by removing async finish path.
Example: Modal switch to react-native-screens
example/app/(tabs)/index.tsx
Replaced React Native <Modal> with react-native-screens <FullWindowOverlay> for modal rendering; backdrop blur and dismissal preserved; content wrapper now uses pointerEvents="none" to avoid intercepting touches.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

android, ios

Suggested reviewers

  • DanielAraldi

Poem

🐰
I hopped through views both near and far,
Found roots for blur where shadows are.
Animator paused, no flicker to rue,
Smooth edges now — a clearer view!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing blur animator race conditions and scroll lag on iOS, and resolving full-screen blur issues on Android.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/blur-issues-85-89-100-101

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sbaiahmed1 sbaiahmed1 self-assigned this Apr 25, 2026
@sbaiahmed1 sbaiahmed1 requested a review from DanielAraldi April 25, 2026 23:01
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
ios/Views/BlurEffectView.swift (1)

50-58: Consider applying the same synchronous fix to VibrancyEffectView.updateEffect() as a follow-up.

ios/Views/VibrancyEffectView.swift:55-98 still dispatches stopAnimation/finishAnimation asynchronously and uses an identity check (currentAnimator === blurAnimator) to guard against the race that exact change in BlurEffectView.setupBlur() was just designed to eliminate. If consumers ever mount VibrancyEffectView in a FlashList/navigation-heavy scenario similar to the iOS 26.2 reproduction in #85, the same one-frame fallback-color flash is likely to surface there. Out of scope for this PR, but worth tracking — the fix pattern would be identical: drop the async wrapper and the identity check, and call stop/finish inline right after assigning fractionComplete.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Views/BlurEffectView.swift` around lines 50 - 58,
VibrancyEffectView.updateEffect() still uses an async wrapper and an identity
guard (currentAnimator === blurAnimator) before calling
stopAnimation/finishAnimation; replicate the BlurEffectView fix by removing the
DispatchQueue.async wrapper and the identity check, and instead set the vibrancy
animator's fractionComplete (e.g., vibrancyAnimator?.fractionComplete =
intensity) and immediately call vibrancyAnimator?.stopAnimation(true) and
vibrancyAnimator?.finishAnimation(at: .current) inline so the vibrancy effect is
applied synchronously.
android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt (1)

200-235: Consider extracting the ancestor-walk helpers to a shared utility.

findNearestScreenAncestor() and findNearestReactRootView() are now duplicated verbatim in ReactNativeBlurView and ReactNativeProgressiveBlurView, along with their priority-ordering logic in findOptimalBlurRoot(). A small private helper (e.g. BlurRootResolver object with findOptimalBlurRoot(view: View): ViewGroup?) would keep the two views in sync — particularly relevant given the New Architecture detection gap that needs to be patched in both places.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt`
around lines 200 - 235, Extract the repeated ancestor-walk logic into a shared
utility (e.g., an object/class named BlurRootResolver) that exposes a single
entry function findOptimalBlurRoot(view: View): ViewGroup?; move the
implementations of findOptimalBlurRoot(), findNearestScreenAncestor(), and
findNearestReactRootView() from ReactNativeBlurView into that utility (keeping
the same class-name checks for "com.swmansion.rnscreens.Screen",
"com.facebook.react.ReactRootView", and
"com.facebook.react.rootview.ReactRootView"), and update ReactNativeBlurView and
ReactNativeProgressiveBlurView to call
BlurRootResolver.findOptimalBlurRoot(this) instead of their local methods so
both classes share the same logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt`:
- Around line 224-235: The method findNearestReactRootView in
ReactNativeBlurView and its duplicate in ReactNativeProgressiveBlurView fails to
recognize bridgeless/new-architecture surfaces and checks a non-existent class;
update both methods to also detect "com.facebook.react.runtime.ReactSurfaceView"
and remove the incorrect check for "com.facebook.react.rootview.ReactRootView",
keeping the existing "com.facebook.react.ReactRootView" check, and continue
returning the found parent cast as ViewGroup (as currently done).

---

Nitpick comments:
In `@android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt`:
- Around line 200-235: Extract the repeated ancestor-walk logic into a shared
utility (e.g., an object/class named BlurRootResolver) that exposes a single
entry function findOptimalBlurRoot(view: View): ViewGroup?; move the
implementations of findOptimalBlurRoot(), findNearestScreenAncestor(), and
findNearestReactRootView() from ReactNativeBlurView into that utility (keeping
the same class-name checks for "com.swmansion.rnscreens.Screen",
"com.facebook.react.ReactRootView", and
"com.facebook.react.rootview.ReactRootView"), and update ReactNativeBlurView and
ReactNativeProgressiveBlurView to call
BlurRootResolver.findOptimalBlurRoot(this) instead of their local methods so
both classes share the same logic.

In `@ios/Views/BlurEffectView.swift`:
- Around line 50-58: VibrancyEffectView.updateEffect() still uses an async
wrapper and an identity guard (currentAnimator === blurAnimator) before calling
stopAnimation/finishAnimation; replicate the BlurEffectView fix by removing the
DispatchQueue.async wrapper and the identity check, and instead set the vibrancy
animator's fractionComplete (e.g., vibrancyAnimator?.fractionComplete =
intensity) and immediately call vibrancyAnimator?.stopAnimation(true) and
vibrancyAnimator?.finishAnimation(at: .current) inline so the vibrancy effect is
applied synchronously.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e165e0dc-0a49-49f3-b0b0-dcf2d2ea36e5

📥 Commits

Reviewing files that changed from the base of the PR and between 23797fd and 39316d9.

📒 Files selected for processing (3)
  • android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt
  • android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt
  • ios/Views/BlurEffectView.swift

The second condition checking 'com.facebook.react.rootview.ReactRootView'
referenced a package path that has never existed in React Native. Only
'com.facebook.react.ReactRootView' is needed.
@DanielAraldi DanielAraldi added the bug Something isn't working label Apr 26, 2026
@DanielAraldi
Copy link
Copy Markdown
Collaborator

👀🚀🔥

Copy link
Copy Markdown
Collaborator

@DanielAraldi DanielAraldi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The #101 issue persists

Comment thread ios/Views/BlurEffectView.swift Outdated
Comment on lines +50 to +57
// Set intensity and immediately freeze the animator so the blur is
// applied synchronously. The previous async dispatch caused a one-frame
// window where the wrong intensity was visible, leading to flickers on
// navigation (issue #101) and fallback-color flashes with many instances
// on iOS 26.2 (issue #85).
animator?.fractionComplete = intensity
// Stop the animation at the current state
DispatchQueue.main.async { [weak self] in
self?.animator?.stopAnimation(true)
self?.animator?.finishAnimation(at: .current)
}
animator?.stopAnimation(true)
animator?.finishAnimation(at: .current)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested #101 with this example App: https://github.com/iqraKhaliq/BlurView

But now, the blur effect isn't applied.

See the snapshot:

Image

Note: The blur effect should be applied after the third element in the list.

Code:

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @DanielAraldi , sorry for the late fix
I have successfully fixed it
You can test if you got some time with this beta : @sbaiahmed1/react-native-blur@4.6.2-beta.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok my friend 🫡

tomorrow I’ll test it 😁

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt (1)

224-233: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Include ReactSurfaceView in RN root fallback detection.

At Line 227, the fallback only matches exact class name com.facebook.react.ReactRootView. In new-architecture/bridgeless paths, parent may be com.facebook.react.runtime.ReactSurfaceView, so this can still miss the RN root and fall back to decor-view blurring.

Suggested fix
 private fun findNearestReactRootView(): ViewGroup? {
   var currentParent = this.parent
   while (currentParent != null) {
-    if (currentParent.javaClass.name == "com.facebook.react.ReactRootView") {
+    val className = currentParent.javaClass.name
+    if (
+      className == "com.facebook.react.ReactRootView" ||
+      className == "com.facebook.react.runtime.ReactSurfaceView" ||
+      className == "com.facebook.react.bridgeless.ReactSurfaceView"
+    ) {
       return currentParent as? ViewGroup
     }
     currentParent = currentParent.parent
   }
   return null
 }

Apply the same change to android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt (Lines 245-254).

Confirm in current React Native Android sources whether bridgeless/new-architecture surfaces use `com.facebook.react.runtime.ReactSurfaceView`, and whether matching only `com.facebook.react.ReactRootView` can miss that ancestor.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt`
around lines 224 - 233, The findNearestReactRootView() function currently only
checks for "com.facebook.react.ReactRootView" and thus misses
bridgeless/new-architecture roots like
"com.facebook.react.runtime.ReactSurfaceView"; update the conditional in
ReactNativeBlurView.kt's findNearestReactRootView() to treat either class name
as a valid RN root (e.g., check parent.javaClass.name ==
"com.facebook.react.ReactRootView" || parent.javaClass.name ==
"com.facebook.react.runtime.ReactSurfaceView"), and apply the identical change
to the same function in ReactNativeProgressiveBlurView.kt so both components
detect ReactSurfaceView as a root fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt`:
- Around line 224-233: The findNearestReactRootView() function currently only
checks for "com.facebook.react.ReactRootView" and thus misses
bridgeless/new-architecture roots like
"com.facebook.react.runtime.ReactSurfaceView"; update the conditional in
ReactNativeBlurView.kt's findNearestReactRootView() to treat either class name
as a valid RN root (e.g., check parent.javaClass.name ==
"com.facebook.react.ReactRootView" || parent.javaClass.name ==
"com.facebook.react.runtime.ReactSurfaceView"), and apply the identical change
to the same function in ReactNativeProgressiveBlurView.kt so both components
detect ReactSurfaceView as a root fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b43469b-7916-411f-afff-cea869757c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 39316d9 and 8284975.

📒 Files selected for processing (4)
  • android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeBlurView.kt
  • android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt
  • example/app/(tabs)/index.tsx
  • ios/Views/BlurEffectView.swift

@DanielAraldi DanielAraldi self-requested a review May 1, 2026 13:31
Copy link
Copy Markdown
Collaborator

@DanielAraldi DanielAraldi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Both (iOS and Android) have worked perfectly!

@DanielAraldi DanielAraldi merged commit 292fbde into main May 1, 2026
6 checks passed
@DanielAraldi DanielAraldi deleted the fix/blur-issues-85-89-100-101 branch May 1, 2026 14:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

2 participants