Skip to content

Commit a4af202

Browse files
committed
feat(Android): add lifecycle events support for bottom navigation view impl (#112)
## Description This PR add basic support for `on{Will,Did}{Appear,Disappear}` events for bottom tabs on Android. The exact order of events is not stable yet & might be adjusted later on as-needed basis. ## Checklist - [ ] Ensured that CI passes
1 parent aa75e7c commit a4af202

File tree

11 files changed

+250
-17
lines changed

11 files changed

+250
-17
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.swmansion.rnscreens.gamma.common
2+
3+
internal interface NamingAwareEventType {
4+
/**
5+
* React event name with `top` prefix
6+
*/
7+
fun getEventName(): String
8+
9+
/**
10+
* Name of the event as expected in Element Tree.
11+
*/
12+
fun getEventRegistrationName(): String
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.swmansion.rnscreens.gamma.helpers
2+
3+
import com.swmansion.rnscreens.gamma.common.NamingAwareEventType
4+
5+
internal fun makeEventRegistrationInfo(event: NamingAwareEventType): Pair<String, HashMap<String, String>> {
6+
return event.getEventName() to hashMapOf("registrationName" to event.getEventRegistrationName())
7+
}

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreen.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import android.util.Log
44
import android.view.ViewGroup
55
import com.facebook.react.uimanager.ThemedReactContext
66

7+
/**
8+
* React Component view.
9+
*/
710
class TabScreen(
811
val reactContext: ThemedReactContext,
912
) : ViewGroup(reactContext) {
@@ -15,6 +18,13 @@ class TabScreen(
1518
b: Int,
1619
) = Unit
1720

21+
internal val eventEmitter = TabScreenEventEmitter(reactContext)
22+
23+
override fun setId(id: Int) {
24+
super.setId(id)
25+
eventEmitter.viewTag = id
26+
}
27+
1828
override fun onAttachedToWindow() {
1929
Log.d(TAG, "TabScreen attached to window")
2030
super.onAttachedToWindow()
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.swmansion.rnscreens.gamma.tabs
2+
3+
import android.util.Log
4+
import android.view.View
5+
import com.facebook.react.bridge.ReactContext
6+
import com.facebook.react.uimanager.UIManagerHelper
7+
import com.facebook.react.uimanager.events.EventDispatcher
8+
import com.swmansion.rnscreens.gamma.tabs.TabScreenEventEmitter.Companion.TAG
9+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenDidAppearEvent
10+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenDidDisappearEvent
11+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenWillAppearEvent
12+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenWillDisappearEvent
13+
14+
class TabScreenEventEmitter(val reactContext: ReactContext) {
15+
var viewTag: Int = View.NO_ID
16+
17+
private val reactEventDispatcher: EventDispatcher?
18+
get() {
19+
// Lets assert for now to make sure we won't miss event delivery
20+
checkValidViewTag()
21+
return UIManagerHelper.getEventDispatcherForReactTag(reactContext, viewTag)
22+
}
23+
24+
private val surfaceId: Int
25+
get() = UIManagerHelper.getSurfaceId(reactContext)
26+
27+
fun emitOnWillAppear() {
28+
checkValidViewTag()
29+
logEventDispatch(viewTag, TabScreenWillAppearEvent.EVENT_REGISTRATION_NAME)
30+
reactEventDispatcher?.dispatchEvent(TabScreenWillAppearEvent(surfaceId, viewTag))
31+
}
32+
33+
fun emitOnDidAppear() {
34+
checkValidViewTag()
35+
logEventDispatch(viewTag, TabScreenDidAppearEvent.EVENT_REGISTRATION_NAME)
36+
reactEventDispatcher?.dispatchEvent(TabScreenDidAppearEvent(surfaceId, viewTag))
37+
}
38+
39+
fun emitOnWillDisappear() {
40+
checkValidViewTag()
41+
logEventDispatch(viewTag, TabScreenWillDisappearEvent.EVENT_REGISTRATION_NAME)
42+
reactEventDispatcher?.dispatchEvent(TabScreenWillDisappearEvent(surfaceId, viewTag))
43+
}
44+
45+
fun emitOnDidDisappear() {
46+
checkValidViewTag()
47+
logEventDispatch(viewTag, TabScreenDidDisappearEvent.EVENT_REGISTRATION_NAME)
48+
reactEventDispatcher?.dispatchEvent(TabScreenDidDisappearEvent(surfaceId, viewTag))
49+
}
50+
51+
private fun checkValidViewTag() {
52+
check(viewTag != View.NO_ID) { "[RNScreens] Attempt to use viewTag before the value was provided"}
53+
}
54+
55+
companion object {
56+
const val TAG = "TabScreenEventEmitter"
57+
}
58+
}
59+
60+
private fun logEventDispatch(viewTag: Int, eventName: String) {
61+
Log.d(TAG, "TabScreen [$viewTag] emits event: $eventName")
62+
}
Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
11
package com.swmansion.rnscreens.gamma.tabs
22

3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
37
import androidx.fragment.app.Fragment
48

5-
class TabScreenFragment(
6-
internal val tabScreen: TabScreen,
7-
) : Fragment()
9+
class TabScreenFragment(internal val tabScreen: TabScreen) : Fragment() {
10+
override fun onCreateView(
11+
inflater: LayoutInflater,
12+
container: ViewGroup?,
13+
savedInstanceState: Bundle?
14+
): View {
15+
return tabScreen
16+
}
17+
18+
override fun onStart() {
19+
tabScreen.eventEmitter.emitOnWillAppear()
20+
super.onStart()
21+
}
22+
23+
override fun onResume() {
24+
tabScreen.eventEmitter.emitOnDidAppear()
25+
super.onResume()
26+
}
27+
28+
override fun onPause() {
29+
tabScreen.eventEmitter.emitOnWillDisappear()
30+
super.onPause()
31+
}
32+
33+
override fun onStop() {
34+
tabScreen.eventEmitter.emitOnDidDisappear()
35+
super.onStop()
36+
}
37+
}

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabScreenViewManager.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import com.facebook.react.uimanager.ViewGroupManager
77
import com.facebook.react.uimanager.ViewManagerDelegate
88
import com.facebook.react.viewmanagers.RNSBottomTabsScreenManagerDelegate
99
import com.facebook.react.viewmanagers.RNSBottomTabsScreenManagerInterface
10+
import com.swmansion.rnscreens.gamma.helpers.makeEventRegistrationInfo
11+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenDidAppearEvent
12+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenDidDisappearEvent
13+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenWillAppearEvent
14+
import com.swmansion.rnscreens.gamma.tabs.event.TabScreenWillDisappearEvent
1015

1116
@ReactModule(name = TabScreenViewManager.REACT_CLASS)
1217
class TabScreenViewManager :
@@ -83,21 +88,19 @@ class TabScreenViewManager :
8388
value: String?,
8489
) = Unit
8590

86-
// override fun setBadgeColor(
87-
// view: TabScreen?,
88-
// value: Int?
89-
// ) = Unit
90-
//
91-
// override fun setTitleFontSize(
92-
// view: TabScreen?,
93-
// value: Float
94-
// ) = Unit
95-
9691
override fun setTitle(
9792
view: TabScreen?,
9893
value: String?,
9994
) = Unit
10095

96+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> =
97+
mutableMapOf(
98+
makeEventRegistrationInfo(TabScreenWillAppearEvent),
99+
makeEventRegistrationInfo(TabScreenDidAppearEvent),
100+
makeEventRegistrationInfo(TabScreenWillDisappearEvent),
101+
makeEventRegistrationInfo(TabScreenDidDisappearEvent),
102+
)
103+
101104
companion object {
102105
const val REACT_CLASS = "RNSBottomTabsScreen"
103106
}

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/TabsHost.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,25 +71,21 @@ class TabsHost(
7171
) {
7272
val tabScreenFragment = TabScreenFragment(reactSubview)
7373
tabScreenFragments.add(index, tabScreenFragment)
74-
contentView.addView(reactSubview, index)
7574
scheduleContainerUpdate()
7675
}
7776

7877
internal fun unmountReactSubviewAt(index: Int) {
7978
tabScreenFragments.removeAt(index)
80-
contentView.removeViewAt(index)
8179
scheduleContainerUpdate()
8280
}
8381

8482
internal fun unmountReactSubview(reactSubview: TabScreen) {
8583
tabScreenFragments.removeIf { it.tabScreen === reactSubview }
86-
contentView.removeView(reactSubview)
8784
scheduleContainerUpdate()
8885
}
8986

9087
internal fun unmountAllReactSubviews() {
9188
tabScreenFragments.clear()
92-
contentView.removeAllViews()
9389
scheduleContainerUpdate()
9490
}
9591

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.swmansion.rnscreens.gamma.tabs.event
2+
3+
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
5+
import com.facebook.react.uimanager.events.Event
6+
import com.swmansion.rnscreens.gamma.common.NamingAwareEventType
7+
8+
class TabScreenDidAppearEvent(
9+
surfaceId: Int,
10+
viewId: Int,
11+
) : Event<TabScreenDidAppearEvent>(surfaceId, viewId), NamingAwareEventType {
12+
override fun getEventName() = EVENT_NAME
13+
14+
override fun getEventRegistrationName() = EVENT_REGISTRATION_NAME
15+
16+
// All events for a given view can be coalesced.
17+
override fun getCoalescingKey(): Short = 0
18+
19+
override fun getEventData(): WritableMap? = Arguments.createMap()
20+
21+
companion object : NamingAwareEventType {
22+
const val EVENT_NAME = "topDidAppear"
23+
const val EVENT_REGISTRATION_NAME = "onDidAppear"
24+
25+
override fun getEventName() = EVENT_NAME
26+
override fun getEventRegistrationName() = EVENT_REGISTRATION_NAME
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.swmansion.rnscreens.gamma.tabs.event
2+
3+
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
5+
import com.facebook.react.uimanager.events.Event
6+
import com.swmansion.rnscreens.gamma.common.NamingAwareEventType
7+
8+
class TabScreenDidDisappearEvent(
9+
surfaceId: Int,
10+
viewId: Int,
11+
) : Event<TabScreenDidDisappearEvent>(surfaceId, viewId), NamingAwareEventType {
12+
override fun getEventName() = EVENT_NAME
13+
14+
override fun getEventRegistrationName() = EVENT_REGISTRATION_NAME
15+
16+
// All events for a given view can be coalesced.
17+
override fun getCoalescingKey(): Short = 0
18+
19+
override fun getEventData(): WritableMap? = Arguments.createMap()
20+
21+
companion object : NamingAwareEventType {
22+
const val EVENT_NAME = "topDidDisappear"
23+
const val EVENT_REGISTRATION_NAME = "onDidDisappear"
24+
25+
override fun getEventName() = EVENT_NAME
26+
override fun getEventRegistrationName() = EVENT_REGISTRATION_NAME
27+
}
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.swmansion.rnscreens.gamma.tabs.event
2+
3+
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
5+
import com.facebook.react.uimanager.events.Event
6+
import com.swmansion.rnscreens.gamma.common.NamingAwareEventType
7+
8+
class TabScreenWillAppearEvent(
9+
surfaceId: Int,
10+
viewId: Int,
11+
) : Event<TabScreenWillAppearEvent>(surfaceId, viewId), NamingAwareEventType {
12+
override fun getEventName() = EVENT_NAME
13+
14+
override fun getEventRegistrationName() = EVENT_REGISTRATION_NAME
15+
16+
// All events for a given view can be coalesced.
17+
override fun getCoalescingKey(): Short = 0
18+
19+
override fun getEventData(): WritableMap? = Arguments.createMap()
20+
21+
companion object : NamingAwareEventType {
22+
const val EVENT_NAME = "topWillAppear"
23+
const val EVENT_REGISTRATION_NAME = "onWillAppear"
24+
25+
override fun getEventName() = EVENT_NAME
26+
override fun getEventRegistrationName() = EVENT_REGISTRATION_NAME
27+
}
28+
}

0 commit comments

Comments
 (0)