♻️ Mobile | Offline Activity feed + PagedListSource pattern (exemplar for #1567)#1569
Draft
jernejk wants to merge 14 commits into
Draft
♻️ Mobile | Offline Activity feed + PagedListSource pattern (exemplar for #1567)#1569jernejk wants to merge 14 commits into
jernejk wants to merge 14 commits into
Conversation
…1567) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l declares intent only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…arate files & folders Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ine, cancellation) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the JSON-file IFileCacheService implementation with an Akavache v11 LocalMachine-backed cache, and add a standard HTTP resilience pipeline to the typed API clients (retries limited to safe/idempotent methods). Co-Authored-By: Claude Opus <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on, docs) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…reMock API, wired into PR CI Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, error, alert) in Core Add Core interfaces so ViewModels can drop MAUI statics: - IConnectivityService (online state + change event) - IAppNavigator (semantic navigation, no MAUI types) - IApiExceptionHandler (exception → handled?) - Move IAlertService interface into Core (no MAUI-typed members) Provide MAUI implementations (MauiConnectivityService, AppNavigator, ApiExceptionHandler) wrapping Connectivity.Current / Shell + ActivatorUtilities / static ExceptionHandler, and register them in DI. Co-Authored-By: Claude Opus <noreply@anthropic.com>
…he ViewModel Add OfflineAwareListErrorHandler (Core) capturing the decision that was inline in ActivityPageViewModel: run the API exception handler first, stay silent when a background refresh fails behind cached content, else alert with offline- or generic-worded copy based on connectivity. Refactor ActivityPageViewModel to depend on IAppNavigator + the handler only — it now references no MAUI statics (Shell/Connectivity/ExceptionHandler). Behaviour preserved: LoadFeed is a background refresh, a segment switch is user-requested, and the messages are unchanged. Co-Authored-By: Claude Opus <noreply@anthropic.com>
Cover the full decision matrix for OfflineAwareListErrorHandler with hand-rolled fakes (no Moq): no error → silent + handler untouched; handled 401 → no alert; background refresh behind cached content → silent; background refresh with no content → alert; user-requested (segment switch) with cached content → still alerts; offline routes the offline message and online routes the generic one. Co-Authored-By: Claude Opus <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
✏️ Part of #1567 — exemplar phase: make one page ViewModel offline-ready with a clean, replicable pattern before hardening the rest of the app.
✏️
Activity feed now works offline (was: "Oops…" alert + empty page in airplane mode), and the plumbing it needed became a reusable pattern:
PagedListSource<T>(Common/) — bindable list source owning the whole load lifecycle (caching, paging, cancellation, anti-flicker); ViewModels declare intent and handle a result — the reusable pattern for every list page.ActivityPageViewModelrewritten around it — cached feed renders offline; background-refresh failures stay silent behind cached content; failed segment switches tell the user; failedLoadMoreretries instead of faking "end of feed".IFileCacheServicegained never-throwGetAsync/SetAsync;AdvancedObservableCollectionmarked legacy (remaining pages migrate in ✨ Mobile | Harden offline mode – close the loop on #1306 #1567).ActivityFeedItemExtensions.PrepareForDisplay(runs for cached and fresh items, so timestamps stay current).ActivityPageViewModel+BaseViewModelon CommunityToolkit.Mvvm partial-property[ObservableProperty]syntax; toolkit 8.4.0 → 8.4.2 (needed for stable C# 14)._docs/agents/maui-patterns.md.MobileUI.Coreproject — platform-independent pieces in one-class-per-file folders (Paging/,Collections/,Caching/), making the pattern unit-testable (the MAUI head can't be referenced by test projects).PagedListSource(tests/MobileUI.Core.UnitTests) — caching, paging end-detection/retry, offline semantics, anti-flicker, stale-load cancellation.IFileCacheServicenow backed by Akavache's SQLiteLocalMachinestore (with optional expiry + flush-on-background); the JSON-file implementation is gone.AddStandardResilienceHandler()on the typed API clients (retry + circuit breaker + timeouts); retries disabled for non-idempotent methods so quiz-submit/redeem/scan are never replayed.tests/MobileUI.Core.IntegrationTests(10 tests) runs REAL Akavache SQLite on the host plus a WireMock in-process API: the go-offline scenario (cache fills online → server stops → cold start serves cache + reports the error), GET-retry-on-503, and POST-never-retried are now pinned by CI (new PR jobs for both new test projects).SSW.Rewards.slnx(CI, rewards-dev root detection, and docs updated;.slnremoved).IConnectivityService,IAppNavigator,IApiExceptionHandler,IAlertServicewith MAUI implementations;ActivityPageViewModelno longer touches any MAUI static (Shell/Connectivity/ExceptionHandler).OfflineAwareListErrorHandler— the offline alert policy (401 first, silent behind cached content, offline vs generic wording) extracted into Core and covered by 7 unit tests. Note: a 401 during a background refresh now correctly triggers re-login (previously swallowed).IFileCacheService.ResetAsync()invoked fromAuthenticationService.SignOut, so no cached user data outlives the session (same concern class as 🐛 Bug - Profile and email visibility is inconsistent after app restart in offline mode #1527).Figure: Activity feed in airplane mode — before: error alert + empty page; after: feed renders from cache (no alert, no crash).
Figure: Switching segments offline now explains why fresh data can't load instead of failing silently.
Figure: Feed loaded online (Wi-Fi icon) → app killed, relaunched in airplane mode → same feed served from cache.
✏️ Yes — JK pairing with Claude (Fable 5).