Skip to content

fix(navigation): reset graph state after client removal [WPB-27228] [WPB-27362] - #5084

Open
MohamadJaara wants to merge 3 commits into
developfrom
mo/fix-removed-client-navigation
Open

fix(navigation): reset graph state after client removal [WPB-27228] [WPB-27362]#5084
MohamadJaara wants to merge 3 commits into
developfrom
mo/fix-removed-client-navigation

Conversation

@MohamadJaara

@MohamadJaara MohamadJaara commented Jul 21, 2026

Copy link
Copy Markdown
Member

https://wearezeta.atlassian.net/browse/WPB-27228

Intent

Make removed-client recovery safe while the app process remains alive: rebuild navigation from synchronized session state, discard all state owned by the invalid session, and allow the same user to log in again with a fresh Kalium session.

Depends on wireapp/kalium#4318.

Reproduction

  1. Log in and delete the current client remotely.
  2. Let the app receive its own client-removal event.
  3. Confirm the removed-client dialog without swiping the app away.
  4. The activity can navigate with a replacement controller before its graph is installed.
  5. After logging in again, retained session graphs, activity-scoped view models, or image requests can still belong to the deleted session.
  6. The replacement session can then fail to join MLS conversations or be logged out again by stale session state.

Root cause

The navigation controller was replaced in place while Compose could still expose the old back-stack entry. That stale route could trigger navigation against the new graphless controller.

The activity also retained its session graph and activity-scoped view models for the process lifetime. Removing a client did not invalidate those objects, so logging the same user in again could reuse dependencies and resources tied to the destroyed Kalium session.

flowchart LR
    A["Remote client removal"] --> B["Block session UI"]
    B --> C["Confirm dialog"]
    C --> D["Invalidate retained graph"]
    D --> E["Clear session view models and image loader"]
    E --> F["Rebuild authentication graph"]
    F --> G["Login same user"]
    G --> H["Create fresh app and Kalium session state"]
    H --> I["Join MLS conversations"]
Loading

Changes

  • Reset observed back-stack state whenever the navigation controller is replaced.
  • Derive the background from the active back-stack entry rather than the discarded controller.
  • Give each retained session graph its own ViewModelStore and session image loader lifetime.
  • Invalidate the retained graph when the UI is blocked by a terminal session error or a session transition starts.
  • Recreate the graph and activity-scoped view models when the same user logs in again.
  • Point the app at the Kalium client-registration and teardown fixes from PR feat(cells): show Pdf preview in conversation screen (WPB-20712) #4318.
  • Cover repeated removal/re-login cycles, multi-account isolation, all terminal Kalium logout reasons, and image-loader shutdown.
  • Extend the staging E2E flow through same-process re-login and bidirectional MLS messaging, with the test-service device verifying receipt and decryption of the app reply.

Coverage

  • Removed-client navigation and retained-graph regression tests.
  • Complete app unit suite and static analysis.
  • Staging E2E source compiled with the real backend and test-service flow.

@MohamadJaara MohamadJaara changed the title fix(navigation): reset graph state after client removal fix(navigation): reset graph state after client removal [WPB-27228] [WPB-27362] Jul 21, 2026
@MohamadJaara
MohamadJaara marked this pull request as ready for review July 21, 2026 16:52
@MohamadJaara
MohamadJaara requested review from Garzas and saleniuk July 21, 2026 16:53
@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.25000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.60%. Comparing base (012cde7) to head (8be797e).
⚠️ Report is 2 commits behind head on develop.

Files with missing lines Patch % Lines
...rc/main/kotlin/com/wire/android/ui/WireActivity.kt 80.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #5084      +/-   ##
===========================================
+ Coverage    49.53%   49.60%   +0.06%     
===========================================
  Files          659      659              
  Lines        24007    24019      +12     
  Branches      3733     3735       +2     
===========================================
+ Hits         11893    11915      +22     
+ Misses       10984    10974      -10     
  Partials      1130     1130              
Files with missing lines Coverage Δ
...com/wire/android/util/ui/WireSessionImageLoader.kt 23.07% <100.00%> (+23.07%) ⬆️
...rc/main/kotlin/com/wire/android/ui/WireActivity.kt 63.02% <80.00%> (+6.54%) ⬆️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 012cde7...8be797e. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines +435 to +436
val backgroundType = currentBackStackEntryState.value?.safeDestination()?.style.let {
(it as? BackgroundStyle)?.backgroundType() ?: BackgroundType.Default

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.

Why not a derivedStateOf anymore?

}

@Composable
internal fun rememberWireActivityCurrentBackStackEntryState(

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.

It doesn't actually use remember, so maybe remove the "remember" prefix from the name? 🤔

@MohamadJaara
MohamadJaara requested a review from emmaoke-w July 27, 2026 08:43
): WireActivityGraphContext? {
if (!shouldInvalidate) return lastSessionGraphContext

lastSessionGraphContext?.sessionGraph?.currentAccount?.let(sessionGraphStore::invalidate)

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.

Could we avoid relying exclusively on lastSessionGraphContext to identify the graph to invalidate?
SessionGraphStoreViewModel survives an Activity recreation, while lastSessionGraphContext is held with Compose remember and is reset. If the Activity is recreated while a terminal error is already blocking the UI, rememberWireActivityGraphContext() returns null, so this value is never restored and the retained graph remains in sessionGraphs.
Logging the same user in again then returns the old RetainedSessionGraph from getOrPut(), including its session-scoped state.
I reproduced this with a unit test: retain a graph, simulate recreation by losing the remembered context while keeping the store, trigger invalidation, and request the same user again. It fails with:

expected: not same but was: <RetainedSessionGraph@...>

Could the store retain enough information to invalidate the affected user independently of the composition state, or invalidate all retained graphs for a terminal session transition?

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