callstack / callstack/react-native-brownfield

[Android] ReactNativeFragment leaks OnBackPressedCallback, breaking back after re-entry

Abierto
#457 1 comentario 0 reacciones 1 asignado Reclamado por @likevy Ver en GitHub
Lenguaje dominante
TypeScript
Estrellas
547
Forks
52
Merge medio
3 d 16 h
PR fusionados (30 d)
5

Descripción

### Summary

`ReactNativeBrownfield.createView` adds an `OnBackPressedCallback` without a `LifecycleOwner` and never removes it. `ReactNativeFragment.onCreateView` calls `createView` for each new view, so callbacks from destroyed views remain enabled for the lifetime of the activity.

In a single-activity host, back works on the first visit to the React Native screen. After leaving and reopening it, hardware/gesture back stops working.

### Environment

| | |
| --- | --- |
| `@callstack/react-native-brownfield` | 5.1.0 |
| React Native | 0.87.0 |
| Android | API 35 emulator |
| Host | Single activity; `ReactNativeFragment` is directly on the activity fragment back stack |

### Minimal reproduction

[findoo/react-native-brownfield-back-press-repro](https://github.com/findoo/react-native-brownfield-back-press-repro)

The reproduction reduces this repository's Android example to one `AppCompatActivity`, one native fragment, one `ReactNativeFragment`, and a React Native component containing only `Text`. It has no JavaScript navigation stack or `BackHandler`.

1. Tap **Open React Native**.
2. Press hardware or gesture back. The native fragment returns.
3. Tap **Open React Native** again.
4. Press back again.

Expected: the native fragment returns.

Actual: the React Native fragment remains visible. Further visits add further callbacks, and back remains broken until the activity is recreated.

https://github.com/user-attachments/assets/7c60a9af-38c7-47d1-8c03-6275d89842ac

### Cause

[`createView`](https://github.com/callstack/react-native-brownfield/blob/bfd88af993733450c7f3a74d829f81c63120fac1/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt#L200-L234) registers the callback using the non-lifecycle-aware overload:

```kotlin
activity?.onBackPressedDispatcher?.addCallback(backPressedCallback)
```

That overload requires explicit removal, but no removal exists. [`ReactNativeFragment.onCreateView`](https://github.com/callstack/react-native-brownfield/blob/bfd88af993733450c7f3a74d829f81c63120fac1/packages/react-native-brownfield/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeFragment.kt#L49-L59) calls `createView` again for every view creation.

When the active callback disables itself to pass an unhandled press to the dispatcher, a callback retained from the previous view receives it instead. The dispatcher therefore never reaches the host fragment manager.

The lifecycle observer at lines 232–233 does not cover this: `ReactNativeFragment` supplies a delegate, so that branch is skipped, and the observer does not remove the callback in any case.

### Expected behaviour

Callbacks belonging to destroyed fragment views should not receive back presses. Back should behave the same on every visit.

### Related

- #227 fixes a callback not being re-enabled after forwarding a back press. This issue concerns callbacks from destroyed views not being removed.
- #354 concerns multiple React Navigation stacks on iOS. This reproduction is Android-only and has no JavaScript navigation stack.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.