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

feat: remove adding to back stack logic on Android #1148

Merged
merged 1 commit into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
96 changes: 0 additions & 96 deletions android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package com.swmansion.rnscreens
import android.content.Context
import android.graphics.Canvas
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import com.facebook.react.bridge.ReactContext
import com.facebook.react.uimanager.UIManagerModule
Expand All @@ -20,21 +18,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
private val drawingOpPool: MutableList<DrawingOp> = ArrayList()
private val drawingOps: MutableList<DrawingOp> = ArrayList()
private var mTopScreen: ScreenStackFragment? = null
private val mBackStackListener = FragmentManager.OnBackStackChangedListener {
if (mFragmentManager?.backStackEntryCount == 0) {
// when back stack entry count hits 0 it means the user's navigated back using hw back
// button. As the "fake" transaction we installed on the back stack does nothing we need
// to handle back navigation on our own.
mTopScreen?.let { dismiss(it) }
}
}
private val mLifecycleCallbacks: FragmentManager.FragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
if (mTopScreen === f) {
setupBackHandlerIfNeeded(f)
}
}
}
private var mRemovalTransitionStarted = false
private var isDetachingCurrentScreen = false
private var reverseLastTwoChildren = false
Expand Down Expand Up @@ -65,28 +48,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
return ScreenStackFragment(screen)
}

override fun onDetachedFromWindow() {
mFragmentManager?.let {
it.removeOnBackStackChangedListener(mBackStackListener)
it.unregisterFragmentLifecycleCallbacks(mLifecycleCallbacks)
if (!it.isStateSaved && !it.isDestroyed) {
// State save means that the container where fragment manager was installed has been
// unmounted.
// This could happen as a result of dismissing nested stack. In such a case we don't need to
// reset back stack as it'd result in a crash caused by the fact the fragment manager is no
// longer attached.
it.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
}
super.onDetachedFromWindow()
}

override fun onAttachedToWindow() {
super.onAttachedToWindow()
val fragmentManager = requireNotNull(mFragmentManager, { "mFragmentManager is null when ScreenStack attached to window" })
fragmentManager.registerFragmentLifecycleCallbacks(mLifecycleCallbacks, false)
}

override fun startViewTransition(view: View) {
super.startViewTransition(view)
mRemovalTransitionStarted = true
Expand Down Expand Up @@ -278,7 +239,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
mStack.clear()
mStack.addAll(mScreenFragments)
it.commitNowAllowingStateLoss()
mTopScreen?.let { screen -> setupBackHandlerIfNeeded(screen) }
}
}

Expand All @@ -288,61 +248,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
}
}

/**
* The below method sets up fragment manager's back stack in a way that it'd trigger our back
* stack change listener when hw back button is clicked.
*
*
* Because back stack by default rolls back the transaction the stack entry is associated with
* we generate a "fake" transaction that hides and shows the top fragment. As a result when back
* stack entry is rolled back nothing happens and we are free to handle back navigation on our own
* in `mBackStackListener`.
*
*
* We pop that "fake" transaction each time we update stack and we add a new one in case the
* top screen is allowed to be dismissed using hw back button. This way in the listener we can
* tell if back button was pressed based on the count of the items on back stack. We expect 0
* items in case hw back is pressed because we try to keep the number of items at 1 by always
* resetting and adding new items. In case we don't add a new item to back stack we remove
* listener so that it does not get triggered.
*
*
* It is important that we don't install back handler when stack contains a single screen as in
* that case we want the parent navigator or activity handler to take over.
*/
private fun setupBackHandlerIfNeeded(topScreen: ScreenStackFragment) {
if (mTopScreen?.isResumed != true) {
// if the top fragment is not in a resumed state, adding back stack transaction would throw.
// In such a case we skip installing back handler and use FragmentLifecycleCallbacks to get
// notified when it gets resumed so that we can install the handler.
return
}
mFragmentManager?.let {
it.removeOnBackStackChangedListener(mBackStackListener)
it.popBackStack(BACK_STACK_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
var firstScreen: ScreenStackFragment? = null
var i = 0
val size = mStack.size
while (i < size) {
val screen = mStack[i]
if (!mDismissed.contains(screen)) {
firstScreen = screen
break
}
i++
}
if (topScreen !== firstScreen && topScreen.isDismissible) {
it
.beginTransaction()
.show(topScreen)
.addToBackStack(BACK_STACK_TAG)
.setPrimaryNavigationFragment(topScreen)
.commitNowAllowingStateLoss()
it.addOnBackStackChangedListener(mBackStackListener)
}
}
}

// below methods are taken from
// https://github.com/airbnb/native-navigation/blob/9cf50bf9b751b40778f473f3b19fcfe2c4d40599/lib/android/src/main/java/com/airbnb/android/react/navigation/ScreenCoordinatorLayout.java#L43
// and are used to swap the order of drawing views when navigating forward with the transitions
Expand Down Expand Up @@ -418,7 +323,6 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
}

companion object {
private const val BACK_STACK_TAG = "RN_SCREEN_LAST"
private fun isSystemAnimation(stackAnimation: StackAnimation): Boolean {
return stackAnimation === StackAnimation.DEFAULT || stackAnimation === StackAnimation.FADE || stackAnimation === StackAnimation.NONE
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,6 @@ class ScreenStackFragment : ScreenFragment {
return view
}

val isDismissible: Boolean
get() = screen.isGestureEnabled

fun canNavigateBack(): Boolean {
val container: ScreenContainer<*>? = screen.container
check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
Expand Down
4 changes: 4 additions & 0 deletions guides/GUIDE_FOR_LIBRARY_AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,7 @@ Check [LifecycleAwareView.java](https://github.com/kmagiera/react-native-screens
In addition to that, you will need to register for receiving these updates. This can be done using [`LifecycleHelper.register`](https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java#L50).
Remember to call [`LifecycleHelper.unregister`](https://github.com/kmagiera/react-native-screens/blob/master/android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.java#L59) before the view is dropped.
Please refer to [SampleLifecycleAwareViewManager.java](https://github.com/kmagiera/react-native-screens/blob/master/Example/android/app/src/main/java/com/swmansion/rnscreens/example/SampleLifecycleAwareViewManager.java) from our example app to see what are the best ways of using the above methods.

## Android hardware back button

In order to properly handle the hardware back button on Android, you should implement the navigation logic concerning it. You can see an example of how it is done in `react-navigation` here: https://github.com/react-navigation/react-navigation/blob/6cba517b74f5fd092db21d5574b558ef2d80897b/packages/native/src/useBackButton.tsx.