Skip to content

Commit e06dd4a

Browse files
authored
refactor(android): use EventDispatcherListener for screen events (#438)
* feat(android): use EventDispatcherListener for screen events Replace fragment-based observer with event-based approach. Uses EventDispatcherListener to intercept screen lifecycle events. * fix(android): inherit presenter screen tag for stacked sheets Child sheets are rendered inside parent's coordinator layout, not in the screen hierarchy. Inherit parent's presenter tag.
1 parent 80bcf25 commit e06dd4a

4 files changed

Lines changed: 95 additions & 300 deletions

File tree

android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import com.facebook.react.util.RNLog
2525
import com.facebook.react.views.view.ReactViewGroup
2626
import com.google.android.material.bottomsheet.BottomSheetBehavior
2727
import com.lodev09.truesheet.core.GrabberOptions
28-
import com.lodev09.truesheet.core.RNScreensFragmentObserver
28+
import com.lodev09.truesheet.core.RNScreensEventObserver
29+
import com.lodev09.truesheet.core.RNScreensEventObserverDelegate
2930
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
3031
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
3132
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
@@ -153,7 +154,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
153154

154155
// Helper Objects
155156
private var keyboardObserver: TrueSheetKeyboardObserver? = null
156-
private var rnScreensObserver: RNScreensFragmentObserver? = null
157+
internal var rnScreensEventObserver: RNScreensEventObserver? = null
157158
internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
158159
delegate = this@TrueSheetViewController
159160
}
@@ -333,7 +334,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
333334

334335
private fun cleanupSheet() {
335336
cleanupKeyboardObserver()
336-
cleanupModalObserver()
337+
cleanupScreenEventObserver()
337338
cleanupBackCallback()
338339
sheetView?.animate()?.cancel()
339340

@@ -581,46 +582,40 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
581582
}
582583

583584
// =============================================================================
584-
// MARK: - Modal Observer (react-native-screens)
585+
// MARK: - Screen Event Observer (react-native-screens)
585586
// =============================================================================
586587

587-
private fun setupModalObserver() {
588-
rnScreensObserver = RNScreensFragmentObserver(
589-
reactContext = reactContext,
590-
onScreenPresented = {
591-
if (isPresented && isTopmostSheet) {
592-
if (isSheetVisible) {
588+
private fun setupScreenEventObserver() {
589+
rnScreensEventObserver = RNScreensEventObserver().apply {
590+
delegate = object : RNScreensEventObserverDelegate {
591+
override fun screenWillDisappear() {
592+
if (isPresented && isSheetVisible) {
593593
dismissKeyboard()
594594
post { hideForScreen() }
595-
} else {
596-
// Sheet is already hidden, just mark it
597-
wasHiddenByScreen = true
598595
}
599596
}
600-
},
601-
onScreenWillDismiss = {
602-
val hasPushedScreens = rnScreensObserver?.hasPushedScreens == true
603-
if (isPresented && wasHiddenByScreen && isTopmostSheet && !hasPushedScreens) {
604-
showAfterScreen()
605-
delegate?.viewControllerDidDetectScreenDismiss()
606-
}
607-
},
608-
onScreenDidDismiss = {
609-
if (isPresented && wasHiddenByScreen) {
610-
wasHiddenByScreen = false
611-
// Restore parent sheet after this sheet is restored
612-
parentSheetView?.viewController?.let { parent ->
613-
post { parent.showAfterScreen() }
597+
598+
override fun screenWillAppear() {
599+
if (isPresented && wasHiddenByScreen) {
600+
showAfterScreen()
614601
}
615602
}
616603
}
617-
)
618-
rnScreensObserver?.start()
604+
605+
// For stacked sheets, inherit parent's presenter screen tag
606+
val parentScreenTag = parentSheetView?.viewController?.rnScreensEventObserver?.presenterScreenTag ?: 0
607+
if (parentScreenTag != 0) {
608+
presenterScreenTag = parentScreenTag
609+
} else {
610+
capturePresenterScreenFromView(this@TrueSheetViewController.delegate as? View)
611+
}
612+
startObserving(eventDispatcher)
613+
}
619614
}
620615

621-
private fun cleanupModalObserver() {
622-
rnScreensObserver?.stop()
623-
rnScreensObserver = null
616+
private fun cleanupScreenEventObserver() {
617+
rnScreensEventObserver?.stopObserving()
618+
rnScreensEventObserver = null
624619
}
625620

626621
private fun setSheetVisibility(visible: Boolean) {
@@ -689,6 +684,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
689684
currentDetentIndex = detentIndex
690685
interactionState = InteractionState.Idle
691686

687+
// Capture presenter screen before view hierarchy changes
688+
setupScreenEventObserver()
689+
692690
// Setup sheet in coordinator layout
693691
setupSheetInCoordinator(coordinator, sheet)
694692

@@ -697,7 +695,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
697695
setupSheetDetents()
698696
setupDimmedBackground()
699697
setupKeyboardObserver()
700-
setupModalObserver()
701698
setupBackCallback()
702699

703700
sheet.setupBackground()
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.lodev09.truesheet.core
2+
3+
import android.view.View
4+
import com.facebook.react.uimanager.events.Event
5+
import com.facebook.react.uimanager.events.EventDispatcher
6+
import com.facebook.react.uimanager.events.EventDispatcherListener
7+
8+
private const val RN_SCREENS_VIEW_CLASS = "com.swmansion.rnscreens.Screen"
9+
10+
interface RNScreensEventObserverDelegate {
11+
fun screenWillDisappear()
12+
fun screenWillAppear()
13+
}
14+
15+
/**
16+
* Observes react-native-screens lifecycle events via EventDispatcherListener.
17+
* Detects when the presenting screen unmounts while sheet is presented.
18+
*/
19+
class RNScreensEventObserver : EventDispatcherListener {
20+
var delegate: RNScreensEventObserverDelegate? = null
21+
22+
private var eventDispatcher: EventDispatcher? = null
23+
var presenterScreenTag: Int = 0
24+
25+
fun startObserving(dispatcher: EventDispatcher?) {
26+
if (eventDispatcher != null || dispatcher == null) return
27+
28+
eventDispatcher = dispatcher
29+
dispatcher.addListener(this)
30+
}
31+
32+
fun stopObserving() {
33+
eventDispatcher?.removeListener(this)
34+
eventDispatcher = null
35+
}
36+
37+
fun capturePresenterScreenFromView(view: View?) {
38+
presenterScreenTag = 0
39+
40+
var current: View? = view
41+
while (current != null) {
42+
if (isScreenView(current)) {
43+
presenterScreenTag = current.id
44+
break
45+
}
46+
current = (current.parent as? View)
47+
}
48+
}
49+
50+
override fun onEventDispatch(event: Event<*>) {
51+
// Only process events for the presenter screen
52+
if (presenterScreenTag == 0 || event.viewTag != presenterScreenTag) return
53+
54+
when (event.eventName) {
55+
"topWillDisappear" -> delegate?.screenWillDisappear()
56+
"topWillAppear" -> delegate?.screenWillAppear()
57+
}
58+
}
59+
60+
companion object {
61+
private fun isScreenView(view: View): Boolean {
62+
return view.javaClass.name == RN_SCREENS_VIEW_CLASS
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)