Skip to content

🔄️ Improve conversation avatar caching - #6499

Merged
mahibi merged 3 commits into
masterfrom
feat/noid/convoAvatarCaching
Aug 19, 2026
Merged

🔄️ Improve conversation avatar caching#6499
mahibi merged 3 commits into
masterfrom
feat/noid/convoAvatarCaching

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Aug 16, 2026

Copy link
Copy Markdown
Member

Conversation avatars somtimes flashed the app-shipped fallback avatar (account_circle_96dp) while scrolling / when loading before rendering the actual avatar - espesially after cold starts, sporadically during a session, and while scrolling. Three changes rework how avatars are fetched, cached and rendered.

So this PR changes the avatar rendering berhavior and loading/caching logic.

Details

1. Versioned avatars are cached immutably

The avatar endpoints send no-cache headers, so Coil revalidated avatars over the network on every memory-cache miss — every cold start refetched all list avatars. Since the avatarVersion URL parameter already is the invalidation token, versioned avatar URLs are immutable content:
a dedicated avatar ImageLoader (sharing memory/disk caches with the default one) now ignores the cache headers. Cached avatars never expire or revalidate; a new avatarVersion from the room list sync changes the URL and forces the fetch. Cold starts and offline usage render avatars straight from the disk cache.

One-to-one conversations previously used the unversioned user-avatar endpoint, outside the version scheme. They now use the versioned conversation-avatar endpoint (which natively serves the peer's avatar), so their refresh is also driven purely by avatarVersion changes.

Server compatibility: both changes are gated on the avatar capability (Talk 17+). On older servers, one-to-one rooms keep the unversioned user-avatar endpoint with header-driven revalidation, and group/public rooms show themed default icons — which also fixes the previous behavior of unconditionally requesting the conversation-avatar endpoint that does not exist there.

2. A known avatar is never replaced by the fallback

Every successful avatar load also stores its bitmap under a stable per-room alias in the shared memory cache. Requests use that alias as placeholder and error image: an avatarVersion bump or theme switch crossfades old → new, and transient load failures keep the last known avatar.

3. The fallback icon is a fallback, not a loading indicator

While an avatar loads and no previous one is known (e.g. rows entering the viewport for the first time in a session while scrolling), nothing is rendered — the slot keeps its size and the avatar crossfades in. The fallback icon only appears when a load fails and no avatar was ever shown.

Resulting behavior

Scenario Rendered avatar
Memory-cache hit (most scroll re-entries) avatar instantly, no intermediate state
Loading, avatar was shown before in this session last shown avatar, crossfade if it changed
Loading, not shown yet this session nothing (blank slot), avatar crossfades in from disk/network
Load failed, previous avatar known previous avatar
Load failed, nothing ever shown fallback icon (its only remaining appearance)
Server without avatar capability (pre Talk 17) 1:1 via user-avatar endpoint, group/public rooms show themed default icons

Testing

  • Unit tests cover the endpoint/capability selection (ConversationAvatarContentTest), incl. the fallback matrix for servers without the avatar capability.
  • Manual: cold start with warm cache (no flash, avatars from disk), avatar change on another client (old → new crossfade after the next room list sync), airplane-mode cold start (avatars render offline), fast scrolling (blank slots instead of fallback flashes), theme switch.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔖 Capability is checked or not needed
  • 🔙 Backport requests are created or not needed: /backport to stable-xx.x
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@AndyScherzinger AndyScherzinger added enhancement New feature or request 3. to review Waiting for reviews AI assisted labels Aug 16, 2026
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoAvatarCaching branch from 27f49e3 to e99ca60 Compare August 16, 2026 10:04
@AndyScherzinger AndyScherzinger added this to the 25.0.0 milestone Aug 16, 2026
@AndyScherzinger
AndyScherzinger requested a review from mahibi August 16, 2026 10:14
@AndyScherzinger
AndyScherzinger marked this pull request as ready for review August 16, 2026 10:14
@AndyScherzinger AndyScherzinger changed the title Improve conversation avatar caching 🔄️ Improve conversation avatar caching Aug 16, 2026
Comment thread app/src/main/java/com/nextcloud/talk/conversationlist/ui/AvatarContent.kt Outdated
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoAvatarCaching branch from e99ca60 to 7cb05b8 Compare August 17, 2026 20:28
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoListUpdate branch from 1374082 to 883b385 Compare August 18, 2026 22:40
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoAvatarCaching branch from 7cb05b8 to 7462a23 Compare August 18, 2026 22:48
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoListUpdate branch from 883b385 to f0b7da9 Compare August 19, 2026 05:03
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoAvatarCaching branch 2 times, most recently from 602b830 to 1aa4411 Compare August 19, 2026 08:50
@AndyScherzinger
AndyScherzinger force-pushed the feat/noid/convoListUpdate branch from f0b7da9 to 7701c50 Compare August 19, 2026 08:50
Base automatically changed from feat/noid/convoListUpdate to master August 19, 2026 15:14
Conversation avatar responses are cacheable for one day only (the
server sends private, max-age=86400, immutable), so once that lifetime
expires every memory-cache miss goes back to the network although a
versioned avatar URL cannot change - cold starts refetched long-known
avatars and showed the fallback avatar in the meantime.

Treat versioned conversation-avatar URLs as immutable content: the
avatarVersion parameter is the invalidation token, so a dedicated
avatar image loader ignores the cache headers and cached avatars never
expire or revalidate - a new version changes the URL and forces the
fetch, cold starts and offline serve straight from the disk cache.

One-to-one rooms are deliberately outside this scheme: their
avatarVersion is a server-side constant derived from a static icon path
(the conversation avatar setters reject one-to-one rooms) and never
changes when the peer updates their user avatar. With the avatar
capability they use the conversation-avatar endpoint, which resolves
the peer avatar and handles federation proxying, but without a version
parameter and on the default header-respecting loader, so the one-day
cache lifetime keeps picking up new peer avatars - matching what
talk-ios does on purpose.

Everything is gated on the avatar capability (Talk 17+; the app has no
minimum Talk version, only the Nextcloud 17 EOL block, so far older
servers remain supported): without it, one-to-one rooms keep the
unversioned user-avatar endpoint and group/public rooms show themed
default icons - which also fixes the previous behavior of
unconditionally requesting the conversation-avatar endpoint that
doesn't exist on such servers. The avatar content resolution moves to a
testable top-level function with unit tests for the endpoint and
capability selection.

Assisted-by: Claude Code:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
When an avatar's cache key changed (avatarVersion bump from a room list
sync, theme switch) or a load transiently failed, the list flashed the
generic fallback avatar until the fetch completed - even though the
previous avatar had just been on screen.

Every successful avatar load now also stores its bitmap under a stable
per-room alias in the shared memory cache. New requests use that alias
as both placeholder and error image, so a refresh renders as a
crossfade from the old avatar to the new one and transient failures
keep the last known avatar. The generic fallback only remains for
avatars that were never shown, and for the rare coincidence of a cold
start with a simultaneous version change, since the alias lives in
memory only.

Assisted-by: Claude Code:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Scrolling the conversation list flashed the generic fallback avatar on
rows entering the viewport for the first time in a session: the lazy
list disposes off-screen rows, and re-entering rows without a memory
cache entry showed the fallback as a de-facto loading indicator until
the disk read or fetch completed.

Render nothing while an avatar loads (the slot keeps its size and the
avatar crossfades in), keep the last shown avatar as the placeholder
when one is known, and reserve the fallback icon for what its name
says: a failed load with no previously shown avatar.

Assisted-by: Claude Code:claude-fable-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
@mahibi
mahibi force-pushed the feat/noid/convoAvatarCaching branch from 1aa4411 to baea94a Compare August 19, 2026 17:23
@github-actions

Copy link
Copy Markdown
Contributor

APK file: https://github.com/nextcloud/talk-android/actions/runs/32281286920/artifacts/9376490161
To test this change/fix you can simply download above APK file and install and test it in parallel to your existing Nextcloud app.
qrcode (please click on link to get QR code displayed)

@mahibi
mahibi merged commit 263312a into master Aug 19, 2026
14 of 20 checks passed
@mahibi
mahibi deleted the feat/noid/convoAvatarCaching branch August 19, 2026 17:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants