My primary issue is the slowness and crash that occurs when exploring observations (load observation, then back), ID’ing, and annotating observations. I think there was a comment along the lines of: well, the app was not designed to be used for that.
That’s a 100% reproducible case. Load an observation, set Life Stage, go back. Repeat 100 times. Or Load an Observation, go back. Load next, go back, keep doing it until the app crashes.
So a few months ago I looked at the android app code, built it and ran it in an emulator. The primary issue in that case is that objects keep being accumulated. You can see that in the Android Studio Profiler.
The slowness occurs when the JVM starts reclaiming memory (which is normal in Java apps) you can see that in Logcat. At some point, the JVM spends a lot of time looking for objects to free up. Then, it has to free up objects more often. That progresses until it can’t and the app crashes.
These are the typical symptoms of “memory leaks”. In Java, it means that objects are kept alive because they are referenced somewhere and because of that they are not candidates to be freeed by the JVM. e.g. add a map, reference bitmaps and never clear them, keep that map alive and see what happens (even if you don’t use those bitmaps).
Here’s what the LLM had to say as I was debugging it (yes, I know… but we’re all “a bit” busy these days and I’m not going to learn a huge codebase right now):
–
1. Observation photo memory leak (the big one)
Symptom: Large bitmaps (~16 MB each) stayed in memory after viewing observations, especially Explore → open obs → back.
Root cause: The inner PhotosViewPagerAdapter in ObservationViewerFragment kept every loaded photo in a mBitmaps HashMap for Zoomy. destroyItem only removed views; close() only closed cursors. Off-screen pages and swiped-away photos kept full decoded bitmaps alive. CustomTarget loads were not tied to the ImageView, so Glide.clear(imageView) did not cancel them.
Fixes prototyped:
- Removed
mBitmaps; zoom reads the ImageView drawable.
Glide.with(imageView).into(imageView) instead of anonymous CustomTarget.
destroyItem: Zoomy.unregister, Glide.clear (later: app context — see below), setImageDrawable(null).
onDestroyView: clear photos pager adapter, destroy() + close() on photo adapter.
reloadPhotos(): set new adapter first, then tear down the old one.
ObservationViewerSlider.onDestroy(): clear outer pager, destroy() on slider adapter (receivers + cursor), clear fragment map.
- Same Glide/Zoomy pattern in standalone
PhotosViewPagerAdapter.java and teardown in TaxonSuggestionsActivity.
–
After I looked at it, I truly believe it can be fixed or at least eliminate most of hte pain points. But the changes are unfortunately large enough that it essentially brings that app into active development.
The memory issues appear app-wide and also there seems to be that there is no will to invest time and money in this app because the new one is under development. And any contributions need to be QA’d which also costs time and money. All of this is 100% reasonable, no blame on that.
I think iNaturalist could get a Cursor account and have someone (in iNaturalist) + Opus 4.6 (or even one of the cheaper models like Composer 2.5) look at this and have some of these issues fixed with under $1000 or $2000 dollars. Take a hot shower after that to get rid of the LLM stench :)
Anyway, it was ‘fun’…