element-hq / element-hq/element-x-android
Appyx Nodes and dependencies are being held in memory
- Dominant language
- Kotlin
- Stars
- 2.4k
- Forks
- 624
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 149
Description
We received an issue recently about the `Client` instance of a logged out session wasn't immediately being cleaned up and that started and investigation.
Apparently, there were several places where a `MatrixClient` wrapping this Rust `Client` instance had a strong reference and a path to the GC root and could cause it to be retained more timed than we'd want it to, but it also uncovered a larger issue: Appyx nodes are being retained in memory for some time even when they're no longer in the navigation stack.
Why is this happening? There are 2 main reasons:
1. Appyx keeps a `Map` of navigation keys linked to `ChildEntry` instances to represent the navigation state at each `ParentNode`. However, this representation of the navigation state is used in several places of the navigation library with an operator that effectively caches the previous state to diff it with the next one, so we're keeping a reference to a map of ChildEntries, which also keep references to their associated Nodes, and these to their dependencies (Presenters, services, etc.). Several of these hold a direct or transitive reference to a `MatrixClient`.
2. For some reason, something similar happens with `Compose`: anything referenced in the `Presenter.present` composable function will be held in memory even when the composable is no longer in use (maybe [this issue](https://issuetracker.google.com/issues/399397350)?), and we use a pattern for relaying events back to the `Presenter` using `eventSink` lambdas that sometimes leak a reference to the presenters through extension functions, or referencing some of the presenter's dependencies directly. We believe this behaviour might have been introduced recently in Compose, and given a heap dump, it looks like this:
Apparently, both cases can be solved if several navigation steps happen, clearing some kind of internal caches.
---
Some more info: the 2nd case seems related to some bug with a combination of the `pointerInput()` and `semantics()` modifiers. Reproducing it is as easy as adding a `Surface` to the composition, anything that goes inside that `Surface`'s content will be held by the `AndroidComposeViewAccessibilityDelegateCompat`'s listeners.
Contributor guide
Assessment
This issue has not been assessed yet.