Repository navigation
Conversation
rive-android's ViewModel.create*Instance hands Kotlin a pointer that already owns a native ref and the ViewModelInstance constructor adds another that is never dropped, so created instances and their bound images outlive their File. Drop it for rive-android 10.4.5 through 11.14.3, gated on the version the module is built against.
mfazekas
force-pushed
the
fix/android-legacy-vmi-extra-ref
branch
from
October 28, 2026 08:41
87bb26f to
4115e96
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Private JNI refcount manipulation changes native lifetimes and needs maintainer review and runtime validation across supported SDK versions, especially when Gradle resolves a version different from the BuildConfig gate.
No open issues 🎉
What changed in this PR
This PR addresses the remaining legacy Android view-model instance leak described in #426 by dropping an extra native reference, allowing instances and their bound images to be released.
Changes:
- Adds a reflection-based workaround gated to rive-android versions 10.4.5–11.14.3.
- Applies it to instance creation in
HybridViewModeland the view’sAuto/ByNamebindings. - Exposes the configured rive-android version through
BuildConfigfor the version gate.
| File | Description |
|---|---|
| android/src/legacy/java/com/rive/RiveReactNativeView.kt | Applies the workaround to Auto and ByName binding instances. |
| android/src/legacy/java/com/margelo/nitro/rive/ViewModelInstanceExtraRef.kt | Adds the version-gated native-reference workaround. |
| android/src/legacy/java/com/margelo/nitro/rive/HybridViewModel.kt | Applies the workaround to all four instance-creation methods. |
| android/build.gradle | Exposes RIVE_ANDROID_VERSION in BuildConfig. |
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
rive-android's legacy
ViewModel.create*Instancereturns a native pointer that already owns one ref, and theViewModelInstanceconstructor takes a second ref thatrelease()never drops. Every instance we create therefore outlives its File, along with any image bound to it. In theAndroidMemoryLeakreproducer (#426), mounting a screen with a bound 2048×2048 image grew GPU memory from 342 MB to 6.7 GB over 294 mounts. With this change it stays flat at ~305 MB over 366 mounts.The workaround drops the extra ref, via reflection on
cppDerefInstance, right after each instance we create inHybridViewModeland in the view'sAuto/ByNamebinding. Doing that on a rive-android release without the bug would free an instance still in use, so it's gated on the rive-android version this module is built against (now exposed asBuildConfig.RIVE_ANDROID_VERSION). It applies only to 10.4.5 through 11.14.3, the range where the double ref exists (every release up to the current latest). Other versions, or a failed reflection lookup, leave behavior unchanged. One residual risk: if an app forces a different rive-android through Gradle dependency resolution rather thanRiveRuntimeAndroidVersion, the gate sees the wrong version. Instances that rive-android auto-binds internally (autoBind) aren't covered.Based on a patch from a user. Verified on an emulator with the legacy backend: the full harness suite passes (including
useViewModelInstance, view-recreate and instance lookup/reuse flows), and the mount/unmount and image swap scenarios ran for several hundred cycles without crashes. Independent of #426 in code; the measurement needs both.