diff --git a/android/src/main/java/com/swmansion/rnscreens/Screen.kt b/android/src/main/java/com/swmansion/rnscreens/Screen.kt index 76bb694854b29d7f779f38244cd48113b429ea1f..fd402ac938862e8d39c5467c1b5c86c4e7eb0c83 100644 --- a/android/src/main/java/com/swmansion/rnscreens/Screen.kt +++ b/android/src/main/java/com/swmansion/rnscreens/Screen.kt @@ -16,6 +16,7 @@ import androidx.annotation.RequiresApi import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.children import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.PixelUtil @@ -462,7 +463,7 @@ class Screen( endTransitionRecursive(childView.toolbar) } - if (childView is ViewGroup) { + if (childView is ViewGroup && childView !is RecyclerView) { endTransitionRecursive(childView) } } @@ -491,7 +492,10 @@ class Screen( startTransitionRecursive(child.toolbar) } - if (child is ViewGroup) { + // Transition a RecyclerView as one unit. Marking its recyclable children as + // transitioning keeps their parent set after removal, so RecyclerView crashes + // when it tries to recycle them during the screen transition. + if (child is ViewGroup && child !is RecyclerView) { startTransitionRecursive(child) } } diff --git a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt index 9f9f8647a50a537d9eec0643db5be69d05089688..09390fc732652ec63b78dbaf2e877b5063886706 100644 --- a/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +++ b/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt @@ -419,7 +419,23 @@ class ScreenStack( private fun performDraw(op: DrawingOp) { // Canvas parameter can not be null here https://developer.android.com/reference/android/view/ViewGroup#drawChild(android.graphics.Canvas,%20android.view.View,%20long) // So if we are passing null here, we would crash anyway - super.drawChild(op.canvas!!, op.child, op.drawingTime) + val child = op.child!! + try { + super.drawChild(op.canvas!!, child, op.drawingTime) + } catch (exception: NullPointerException) { + // Deferred draws can outlive a child's attachment. Android 8.1 can then dereference + // cleared attachment state while applying the child's legacy animation. + val topFrame = exception.stackTrace.firstOrNull() + val isDetachedViewLegacyAnimationCrash = + Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1 && + !child.isAttachedToWindow && + topFrame?.className == View::class.java.name && + topFrame.methodName == "applyLegacyAnimation" + + if (!isDetachedViewLegacyAnimationCrash) { + throw exception + } + } } // Can't use `drawingOpPool.removeLast` here due to issues with static name resolution in Android SDK 35+.