Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refact(Android): allow for different fragment types inside ScreenContainer #1887

Merged
merged 30 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
55170ce
Add FragmentHolder interface
kkafar Sep 11, 2023
1f31afb
Add ScreenFragmentWrapper interface
kkafar Sep 11, 2023
103ce7e
Add onContainerUpdate to ScreenFragmentWrapper interface
kkafar Sep 11, 2023
920a1a0
Add activity & context resolving methods to ScreenFragmentWrapper
kkafar Sep 11, 2023
6e1ba67
Add childScreenContainers to ScreenFragmentWrapper interface
kkafar Sep 11, 2023
50ccfaa
Add ScreenLifecycleEventDispatcher interface
kkafar Sep 11, 2023
5c7f455
Rename ScreenLifecycleEventDispatcher -> ScreenEventDispatcher
kkafar Sep 11, 2023
9692479
ScreenFragmentWrapper extends ScreenEventDispatcher
kkafar Sep 11, 2023
108bdfa
Add more communication-with-continer-related methods to ScreenFragmen…
kkafar Sep 11, 2023
2f8cbc8
Add View.recycle extension method
kkafar Sep 11, 2023
36ca192
Fix method signatures in ScreenEventDispatcherInterface
kkafar Sep 11, 2023
2562241
Partially migrate ScreenFragment
kkafar Sep 11, 2023
6eb753f
Finish initial implementation of ScreenFragmentWrapper for ScreenFrag…
kkafar Sep 11, 2023
33f4e11
Test migration of ScreenContainer to use ScreenFragmentWrapper instea…
kkafar Sep 11, 2023
433441d
Fix type specs for ScreenContainer<*> -> just ScreenContainer
kkafar Sep 11, 2023
aa1fa27
Fix type specs in ScreenStackFragment
kkafar Sep 11, 2023
519180b
Pass fragment wrapper to a Screen
kkafar Sep 11, 2023
73e7d88
More adaptation in ScreenContainer
kkafar Sep 11, 2023
732a0ec
Start separating ScreenStackFragmentWrapper interface
kkafar Sep 11, 2023
745a647
Implement ScreenStackFragmentWrapper for ScreenStackFragment (in curr…
kkafar Sep 11, 2023
fafc033
Initial changes in ScreenStack
kkafar Sep 11, 2023
b05fdbd
Add canNavigateBack method to wrapper interface
kkafar Sep 11, 2023
b6f02bd
Add dismiss method to wrapper interface
kkafar Sep 11, 2023
3e4373d
Finish initial migration of ScreenStack
kkafar Sep 11, 2023
277fbd9
Make it compile
kkafar Sep 11, 2023
de2dadc
Remove cherry-pick leftover
kkafar Sep 15, 2023
e947d9a
Unify naming between properties and methods
kkafar Sep 15, 2023
ee40686
Revert "Add View.recycle extension method"
kkafar Sep 15, 2023
cc8b2df
Merge branch 'main' into @kkafar/partial-android-refactor
kkafar Sep 15, 2023
eac2adc
Fix nullability handling while casting newTop
kkafar Sep 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.swmansion.rnscreens

import androidx.fragment.app.Fragment

interface FragmentHolder {
val fragment: Fragment
}
Comment on lines +1 to +7
Copy link
Member Author

Choose a reason for hiding this comment

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

Decided to split this property to separate interface as this is the key idea behind this PR.

Also in the future I plan to make ScreenFragmentWrapper interfaces much more granular (split those into number of fine-grained interfaces.

21 changes: 12 additions & 9 deletions android/src/main/java/com/swmansion/rnscreens/Screen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.webkit.WebView
import androidx.core.view.children
import androidx.fragment.app.Fragment
import com.facebook.react.bridge.GuardedRunnable
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerModule

@SuppressLint("ViewConstructor")
class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(context) {
val fragment: Fragment?
get() = fragmentWrapper?.fragment

var fragment: ScreenFragment? = null
var container: ScreenContainer<*>? = null
var fragmentWrapper: ScreenFragmentWrapper? = null
var container: ScreenContainer? = null
var activityState: ActivityState? = null
private set
private var mTransitioning = false
Expand Down Expand Up @@ -150,7 +153,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
else -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}

fragment?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) }
fragmentWrapper?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) }
}

// Accepts one of 4 accessibility flags
Expand All @@ -167,7 +170,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
ScreenWindowTraits.applyDidSetStatusBarAppearance()
}
mStatusBarStyle = statusBarStyle
fragment?.let { ScreenWindowTraits.setStyle(this, it.tryGetActivity(), it.tryGetContext()) }
fragmentWrapper?.let { ScreenWindowTraits.setStyle(this, it.tryGetActivity(), it.tryGetContext()) }
}

var isStatusBarHidden: Boolean?
Expand All @@ -177,7 +180,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
ScreenWindowTraits.applyDidSetStatusBarAppearance()
}
mStatusBarHidden = statusBarHidden
fragment?.let { ScreenWindowTraits.setHidden(this, it.tryGetActivity()) }
fragmentWrapper?.let { ScreenWindowTraits.setHidden(this, it.tryGetActivity()) }
}

var isStatusBarTranslucent: Boolean?
Expand All @@ -187,7 +190,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
ScreenWindowTraits.applyDidSetStatusBarAppearance()
}
mStatusBarTranslucent = statusBarTranslucent
fragment?.let {
fragmentWrapper?.let {
ScreenWindowTraits.setTranslucent(
this,
it.tryGetActivity(),
Expand All @@ -203,7 +206,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
ScreenWindowTraits.applyDidSetStatusBarAppearance()
}
mStatusBarColor = statusBarColor
fragment?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) }
fragmentWrapper?.let { ScreenWindowTraits.setColor(this, it.tryGetActivity(), it.tryGetContext()) }
}

var navigationBarColor: Int?
Expand All @@ -213,7 +216,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
ScreenWindowTraits.applyDidSetNavigationBarAppearance()
}
mNavigationBarColor = navigationBarColor
fragment?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) }
fragmentWrapper?.let { ScreenWindowTraits.setNavigationBarColor(this, it.tryGetActivity()) }
}

var isNavigationBarHidden: Boolean?
Expand All @@ -223,7 +226,7 @@ class Screen constructor(context: ReactContext?) : FabricEnabledViewGroup(contex
ScreenWindowTraits.applyDidSetNavigationBarAppearance()
}
mNavigationBarHidden = navigationBarHidden
fragment?.let {
fragmentWrapper?.let {
ScreenWindowTraits.setNavigationBarHidden(
this,
it.tryGetActivity(),
Expand Down
71 changes: 35 additions & 36 deletions android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import com.facebook.react.modules.core.ChoreographerCompat
import com.facebook.react.modules.core.ReactChoreographer
import com.swmansion.rnscreens.Screen.ActivityState

open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(context) {
open class ScreenContainer(context: Context?) : ViewGroup(context) {
Copy link
Member Author

Choose a reason for hiding this comment

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

ScreenContainer type is no longer parameterized with ScreenFragment type. Instead whole fragment type variance is handled by ScreenFragmentWrapper interface.

@JvmField
protected val mScreenFragments = ArrayList<T>()
protected val mScreenFragments = ArrayList<ScreenFragmentWrapper>()
@JvmField
protected var mFragmentManager: FragmentManager? = null
private var mIsAttached = false
Expand All @@ -34,7 +34,7 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
layout(left, top, right, bottom)
}
}
private var mParentScreenFragment: ScreenFragment? = null
private var mParentScreenFragment: ScreenFragmentWrapper? = null

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
var i = 0
Expand Down Expand Up @@ -87,14 +87,11 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
performUpdatesNow()
}

protected open fun adapt(screen: Screen): T {
@Suppress("UNCHECKED_CAST")
return ScreenFragment(screen) as T
}
protected open fun adapt(screen: Screen): ScreenFragmentWrapper = ScreenFragment(screen)

fun addScreen(screen: Screen, index: Int) {
val fragment = adapt(screen)
screen.fragment = fragment
screen.fragmentWrapper = fragment
mScreenFragments.add(index, fragment)
screen.container = this
onScreenChanged()
Expand All @@ -119,6 +116,8 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co

fun getScreenAt(index: Int): Screen = mScreenFragments[index].screen

fun getScreenFragmentWrapperAt(index: Int): ScreenFragmentWrapper = mScreenFragments[index]

open val topScreen: Screen?
get() = mScreenFragments.firstOrNull { getActivityState(it) === ActivityState.ON_TOP }?.screen

Expand Down Expand Up @@ -174,10 +173,10 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
// Otherwise we expect to connect directly with root view and get root fragment manager
if (parent is Screen) {
checkNotNull(
parent.fragment?.let { screenFragment ->
mParentScreenFragment = screenFragment
screenFragment.registerChildScreenContainer(this)
setFragmentManager(screenFragment.childFragmentManager)
parent.fragmentWrapper?.let { fragmentWrapper ->
mParentScreenFragment = fragmentWrapper
fragmentWrapper.addChildContainer(this)
setFragmentManager(fragmentWrapper.fragment.childFragmentManager)
}
) { "Parent Screen does not have its Fragment attached" }
} else {
Expand All @@ -197,19 +196,19 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
.setReorderingAllowed(true)
}

private fun attachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) {
transaction.add(id, screenFragment)
private fun attachScreen(transaction: FragmentTransaction, fragment: Fragment) {
transaction.add(id, fragment)
}

private fun detachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) {
transaction.remove(screenFragment)
private fun detachScreen(transaction: FragmentTransaction, fragment: Fragment) {
transaction.remove(fragment)
}

private fun getActivityState(screenFragment: ScreenFragment): ActivityState? =
screenFragment.screen.activityState
private fun getActivityState(screenFragmentWrapper: ScreenFragmentWrapper): ActivityState? =
screenFragmentWrapper.screen.activityState

open fun hasScreen(screenFragment: ScreenFragment?): Boolean =
mScreenFragments.contains(screenFragment)
open fun hasScreen(screenFragmentWrapper: ScreenFragmentWrapper?): Boolean =
mScreenFragments.contains(screenFragmentWrapper)

override fun onAttachedToWindow() {
super.onAttachedToWindow()
Expand Down Expand Up @@ -246,7 +245,7 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
}
}

mParentScreenFragment?.unregisterChildScreenContainer(this)
mParentScreenFragment?.removeChildContainer(this)
mParentScreenFragment = null

super.onDetachedFromWindow()
Expand Down Expand Up @@ -320,13 +319,13 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
"mFragmentManager is null when performing update in ScreenContainer"
}.fragments
)
for (screenFragment in mScreenFragments) {
if (getActivityState(screenFragment) === ActivityState.INACTIVE &&
screenFragment.isAdded
for (fragmentWrapper in mScreenFragments) {
if (getActivityState(fragmentWrapper) === ActivityState.INACTIVE &&
fragmentWrapper.fragment.isAdded
) {
detachScreen(it, screenFragment)
detachScreen(it, fragmentWrapper.fragment)
}
orphaned.remove(screenFragment)
orphaned.remove(fragmentWrapper.fragment)
}
if (orphaned.isNotEmpty()) {
val orphanedAry = orphaned.toTypedArray()
Expand All @@ -344,30 +343,30 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co

// attach newly activated screens
var addedBefore = false
val pendingFront: ArrayList<T> = ArrayList()
val pendingFront: ArrayList<ScreenFragmentWrapper> = ArrayList()

for (screenFragment in mScreenFragments) {
val activityState = getActivityState(screenFragment)
if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) {
for (fragmentWrapper in mScreenFragments) {
val activityState = getActivityState(fragmentWrapper)
if (activityState !== ActivityState.INACTIVE && !fragmentWrapper.fragment.isAdded) {
addedBefore = true
attachScreen(it, screenFragment)
attachScreen(it, fragmentWrapper.fragment)
} else if (activityState !== ActivityState.INACTIVE && addedBefore) {
// we detach the screen and then reattach it later to make it appear on front
detachScreen(it, screenFragment)
pendingFront.add(screenFragment)
detachScreen(it, fragmentWrapper.fragment)
pendingFront.add(fragmentWrapper)
}
screenFragment.screen.setTransitioning(transitioning)
fragmentWrapper.screen.setTransitioning(transitioning)
}

for (screenFragment in pendingFront) {
attachScreen(it, screenFragment)
attachScreen(it, screenFragment.fragment)
}

it.commitNowAllowingStateLoss()
}
}

protected open fun notifyContainerUpdate() {
topScreen?.fragment?.onContainerUpdate()
topScreen?.fragmentWrapper?.onContainerUpdate()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager

@ReactModule(name = ScreenContainerViewManager.REACT_CLASS)
class ScreenContainerViewManager : ViewGroupManager<ScreenContainer<*>>() {
class ScreenContainerViewManager : ViewGroupManager<ScreenContainer>() {
override fun getName(): String = REACT_CLASS

override fun createViewInstance(reactContext: ThemedReactContext): ScreenContainer<ScreenFragment> = ScreenContainer(reactContext)
override fun createViewInstance(reactContext: ThemedReactContext): ScreenContainer = ScreenContainer(reactContext)

override fun addView(parent: ScreenContainer<*>, child: View, index: Int) {
override fun addView(parent: ScreenContainer, child: View, index: Int) {
require(child is Screen) { "Attempt attach child that is not of type RNScreens" }
parent.addScreen(child, index)
}

override fun removeViewAt(parent: ScreenContainer<*>, index: Int) {
override fun removeViewAt(parent: ScreenContainer, index: Int) {
parent.removeScreenAt(index)
}

override fun removeAllViews(parent: ScreenContainer<*>) {
override fun removeAllViews(parent: ScreenContainer) {
parent.removeAllScreens()
}

override fun getChildCount(parent: ScreenContainer<*>): Int = parent.screenCount
override fun getChildCount(parent: ScreenContainer): Int = parent.screenCount

override fun getChildAt(parent: ScreenContainer<*>, index: Int): View = parent.getScreenAt(index)
override fun getChildAt(parent: ScreenContainer, index: Int): View = parent.getScreenAt(index)

override fun createShadowNodeInstance(context: ReactApplicationContext): LayoutShadowNode = ScreensShadowNode(context)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.swmansion.rnscreens

interface ScreenEventDispatcher {
fun canDispatchLifecycleEvent(event: ScreenFragment.ScreenLifecycleEvent): Boolean
fun updateLastEventDispatched(event: ScreenFragment.ScreenLifecycleEvent)

/**
* Dispatches given screen lifecycle event to JS using screen from given fragment `fragmentWrapper`
*/
fun dispatchLifecycleEvent(event: ScreenFragment.ScreenLifecycleEvent, fragmentWrapper: ScreenFragmentWrapper)

/**
* Dispatches given screen lifecycle event from all non-empty child containers to JS
*/
fun dispatchLifecycleEventInChildContainers(event: ScreenFragment.ScreenLifecycleEvent)

fun dispatchHeaderBackButtonClickedEvent()
fun dispatchTransitionProgressEvent(alpha: Float, closing: Boolean)

// Concrete dispatchers
}
Loading