Skip to content

fix(android): avoid retaining activity in Coil singleton factory#6403

Merged
jamesarich merged 1 commit into
meshtastic:mainfrom
jeremiah-k:bugfix/coil-singleton-image-loader-lifecycle
Jul 24, 2026
Merged

fix(android): avoid retaining activity in Coil singleton factory#6403
jamesarich merged 1 commit into
meshtastic:mainfrom
jeremiah-k:bugfix/coil-singleton-image-loader-lifecycle

Conversation

@jeremiah-k

@jeremiah-k jeremiah-k commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Overview

This moves Coil's process-wide singleton ImageLoader factory from MainActivity to MeshUtilApplication.

Registering the singleton factory from activity composition allowed the process-wide factory lambda to capture an activity instance. Recreating the activity could therefore leave the destroyed activity and its associated UI graph reachable through Coil's singleton configuration.

The application now owns the singleton factory and continues to return the existing Koin-configured ImageLoader.

Key Changes

  • Implement SingletonImageLoader.Factory on MeshUtilApplication.
  • Resolve the existing Koin-provided ImageLoader from application scope.
  • Remove activity-level singleton factory registration from MainActivity.
  • Preserve the existing HTTP client, SVG decoder, cache, logger, and flavor-specific image-loader configuration.
  • Leave Desktop image loading unchanged.

Testing

  • Added a Robolectric regression test that starts Koin, invokes the application factory, and verifies it returns the exact configured singleton instance.
  • Ensured Koin and image-loader resources are cleaned up after the test.

Runtime validation repeatedly paused, stopped, restarted, and resumed MainActivity in the same process without image-loader initialization errors or process replacement. This exercises the lifecycle pattern that previously allowed activity ownership to leak into the process-wide singleton.

Migration Notes

  • No user data migration is required.
  • Image URLs, cache formats, and rendering behavior are unchanged.
  • This only changes ownership of the process-wide Coil singleton factory.

Summary by CodeRabbit

  • Bug Fixes

    • Improved image-loading lifecycle management for more consistent, reliable rendering across the app.
    • Streamlined startup image-loader configuration to prevent conflicting initialization paths.
  • Tests

    • Added Robolectric test coverage to confirm the app provides the configured singleton image loader and that it is reused consistently during runtime.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 94f657fc-d0c8-44be-8e3c-ab5bee50e9f1

📥 Commits

Reviewing files that changed from the base of the PR and between 6a02aef and 8664f80.

📒 Files selected for processing (3)
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
  • androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
  • androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt
💤 Files with no reviewable changes (1)
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt
  • androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt

📝 Walkthrough

Walkthrough

Coil singleton image-loader configuration moves from Compose setup into MeshUtilApplication, which supplies the Koin-managed ImageLoader. A Robolectric test verifies instance identity and resets Coil state after each test.

Changes

Coil singleton integration

Layer / File(s) Summary
Application-owned Coil provider wiring
androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt, androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
MeshUtilApplication supplies Koin’s ImageLoader through SingletonImageLoader.Factory, while MainActivity removes Compose-time singleton registration.
ImageLoader lifecycle validation
androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt
Robolectric/Koin setup verifies the factory returns the configured ImageLoader and resets Coil singleton state after each test.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: moving Coil’s singleton factory out of the activity to avoid retaining it.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bugfix PR tag label Jul 24, 2026
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 02:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt`:
- Around line 35-36: Update CoilImageLoaderLifecycleTest to use
`@Config`(application = MeshUtilApplication::class) and exercise Robolectric’s
production application lifecycle instead of manually constructing
MeshUtilApplication. Verify after startup that SingletonImageLoader.Factory
resolves the configured image loader, covering the app’s startKoin and Coil
factory wiring.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8880d732-2672-4e6b-a877-c23f6d50d854

📥 Commits

Reviewing files that changed from the base of the PR and between a82d0e2 and 5849116.

📒 Files selected for processing (3)
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
  • androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
  • androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt
💤 Files with no reviewable changes (1)
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt

Comment thread androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt Outdated
@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 02:34
@jeremiah-k
jeremiah-k force-pushed the bugfix/coil-singleton-image-loader-lifecycle branch from 5849116 to 6a02aef Compare July 24, 2026 13:39
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 15:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt`:
- Line 33: Add the Coil DelicateCoilApi opt-in to the tearDown method that calls
SingletonImageLoader.reset(), preserving the existing `@After` annotation and
teardown behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 42ed181c-a0ff-4678-aef1-4dec55799ed5

📥 Commits

Reviewing files that changed from the base of the PR and between 5849116 and 6a02aef.

📒 Files selected for processing (3)
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
  • androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
  • androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt
💤 Files with no reviewable changes (1)
  • androidApp/src/main/kotlin/org/meshtastic/app/MainActivity.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt

Comment thread androidApp/src/test/kotlin/org/meshtastic/app/CoilImageLoaderLifecycleTest.kt Outdated
@jeremiah-k
jeremiah-k marked this pull request as draft July 24, 2026 15:58
Replace the per-Activity ImageLoaderFactory with a singleton-factory pattern
in MeshUtilApplication to prevent retaining stale Activity references through
configuration changes. Add a Robolectric lifecycle test exercising the real
MeshUtilApplication, and opt into Coil's DelicateCoilApi for controlled
global-state singleton reset in test teardown.
@jeremiah-k
jeremiah-k force-pushed the bugfix/coil-singleton-image-loader-lifecycle branch from 6a02aef to 8664f80 Compare July 24, 2026 18:22
@jeremiah-k
jeremiah-k marked this pull request as ready for review July 24, 2026 18:48

@jamesarich jamesarich left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the pieces that could break this:

  • org.koin.android.ext.android.get is already imported and used in MeshUtilApplication.kt, so get<ImageLoader>() resolves.
  • GoogleMeshUtilApplication extends MeshUtilApplication, so both flavors inherit newImageLoader.
  • No Koin leak across Robolectric classes: onTerminate() already calls stopKoin() (MeshUtilApplication.kt:147), and Robolectric drives onTerminate at teardown.
  • newImageLoader is invoked lazily on first image load, always after onCreate starts Koin.

One thing worth adding to the description, since it is a real improvement that is easy to miss: setSingletonImageLoaderFactory only bridged Compose call sites, whereas SingletonImageLoader.Factory on the Application covers all process-wide Coil use — including Glance/widget and any non-Compose loads, which previously fell back to a default ImageLoader with no custom HttpClient or SVG decoder.

Approving; queued to merge on green.

@jamesarich
jamesarich enabled auto-merge July 24, 2026 18:54
@jamesarich
jamesarich added this pull request to the merge queue Jul 24, 2026
Merged via the queue into meshtastic:main with commit 01ba2a4 Jul 24, 2026
15 checks passed
@jeremiah-k
jeremiah-k deleted the bugfix/coil-singleton-image-loader-lifecycle branch July 25, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix PR tag

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants