Skip to content

Conversation

@torlando-tech
Copy link
Owner

Summary

  • Fix StateFlow caching that prevented loading spinners from showing on tab switches
  • Use onStart {} operator to emit loading state each time a flow is collected
  • Set replayExpirationMillis = 0 to prevent stale cached values being shown
  • Add loading state handling for Network tab which uses Paging3 (loadState.refresh)

Changes

  • ChatsViewModel.kt: Updated StateFlow to emit loading state on each collection
  • ContactsViewModel.kt: Same pattern applied for contacts loading state
  • AnnounceStreamScreen.kt: Added LoadingNetworkState composable for Paging3 loading detection
  • ContactsScreen.kt: Fixed debug log line length

Closes #341

Test plan

  • Switch between Chats/Contacts tabs - verify loading spinner appears briefly
  • Verify Network subtab shows loading spinner
  • Verify My Contacts subtab shows loading spinner
  • Verify empty states still show correctly when no data

🤖 Generated with Claude Code

torlando-tech and others added 6 commits January 28, 2026 18:56
Phase 02.3: Loading States for Tabs
- 1 plan in 1 wave
- Adds ChatsState and ContactsState with isLoading flag
- Updates ChatsScreen and ContactsScreen to show loading before empty
- Follows MapViewModel boolean flag pattern

Ready for execution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ChatsState data class with isLoading flag
- Replace conversations StateFlow with chatsState StateFlow
- Add LoadingConversationsState composable with spinner and text
- Update ChatsScreen to check isLoading before showing empty state

Prevents "No conversations yet" flash while data loads from database.
- Add ContactsState data class with isLoading flag
- Replace groupedContacts StateFlow with contactsState StateFlow
- Add LoadingContactsState composable with spinner and text
- Update ContactsScreen to check isLoading before showing empty state

Prevents "No contacts yet" flash while data loads from database.
Tasks completed: 2/2
- Task 1: Add loading states to ChatsViewModel and ChatsScreen
- Task 2: Add loading states to ContactsViewModel and ContactsScreen

SUMMARY: .planning/phases/02.3-loading-states-for-tabs/02.3-01-SUMMARY.md
- Phase 2.3 verified: 5/5 must-haves pass
- UX-LOADING-01 requirement complete
- Issue #341 resolved

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…kground

Previously, Skip blocked for 8-12 seconds while applyInterfaceChanges()
restarted the Reticulum service. Now navigation happens immediately and
service restart runs in background. User sees "Loading conversations..."
(from phase 2.3) while service initializes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 29, 2026

Greptile Overview

Greptile Summary

This PR fixes the issue where loading spinners weren't appearing when switching between tabs. The root cause was StateFlow caching - stateIn() would replay the last emitted value immediately to new collectors, preventing the loading state from showing.

Key changes:

  • Wrapped data in state classes (ChatsState, ContactsState) that include isLoading boolean
  • Added .onStart { emit(loadingState) } to emit loading state on each new collection
  • Set replayExpirationMillis = 0 to prevent cached values from being replayed
  • Added loading state detection for Network tab using Paging3's loadState.refresh
  • Updated all three screens (Chats, Contacts, Network) with loading composables
  • Refactored onboarding skip flow to navigate immediately, then restart service in background for better UX

Trade-off note: Setting stopTimeoutMillis = 0 removes the 5-second keepalive optimization, meaning the upstream flow restarts on every tab switch. This ensures fresh loading states but may increase database query frequency. Only replayExpirationMillis = 0 might be sufficient to achieve the loading state goal while keeping the keepalive.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - it's a focused UX improvement with well-tested changes
  • Score reflects solid implementation of loading states with clear pattern applied consistently. The StateFlow configuration change (stopTimeoutMillis=0) may cause slightly more database queries than necessary, but this is a performance optimization opportunity rather than a bug. All changes are defensive (adding loading states) and thoroughly tested per the verification checklist.
  • Consider the stopTimeoutMillis configuration in ChatsViewModel.kt and ContactsViewModel.kt - may want to test with only replayExpirationMillis=0 to optimize query frequency

Important Files Changed

Filename Overview
app/src/main/java/com/lxmf/messenger/viewmodel/ChatsViewModel.kt Added ChatsState data class with loading state; uses onStart{} and replayExpirationMillis=0 to emit loading on each collection
app/src/main/java/com/lxmf/messenger/viewmodel/ContactsViewModel.kt Added ContactsState data class with loading state; applies same onStart{} and replayExpirationMillis=0 pattern as ChatsViewModel
app/src/main/java/com/lxmf/messenger/viewmodel/OnboardingViewModel.kt Reordered skip logic to navigate immediately, then restart service in background; improves perceived responsiveness
app/src/main/java/com/lxmf/messenger/ui/screens/ChatsScreen.kt Updated to use chatsState instead of conversations; added LoadingConversationsState composable for loading UI
app/src/main/java/com/lxmf/messenger/ui/screens/ContactsScreen.kt Updated to use contactsState; added LoadingContactsState composable; fixed debug log formatting
app/src/main/java/com/lxmf/messenger/ui/screens/AnnounceStreamScreen.kt Added loading state detection using Paging3's loadState.refresh; added LoadingNetworkState composable

Sequence Diagram

sequenceDiagram
    participant User
    participant Screen as ChatsScreen/ContactsScreen
    participant ViewModel as ChatsViewModel/ContactsViewModel
    participant StateFlow
    participant Repository

    Note over User,Repository: Tab Switch / Screen Entry
    User->>Screen: Switch to tab
    Screen->>StateFlow: collectAsState()
    
    Note over StateFlow: New collector subscribes
    StateFlow->>StateFlow: onStart {} triggered
    StateFlow->>Screen: emit(State(isLoading=true))
    Screen->>Screen: Show loading spinner
    
    StateFlow->>Repository: Query data (via flatMapLatest)
    Repository-->>StateFlow: Flow<List<Data>>
    StateFlow->>StateFlow: map to State(data, isLoading=false)
    StateFlow->>Screen: emit(State(data, isLoading=false))
    Screen->>Screen: Show data list
    
    Note over User,Repository: User navigates away
    User->>Screen: Switch to different tab
    Screen->>StateFlow: Stop collecting
    StateFlow->>StateFlow: Last collector gone, stop immediately (stopTimeoutMillis=0)
    StateFlow->>Repository: Cancel upstream flow
    
    Note over StateFlow,Repository: replayExpirationMillis=0 prevents caching
Loading

@sentry
Copy link

sentry bot commented Jan 29, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

6 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

- Use onStart{} operator to emit loading state each time flow is collected
- Set replayExpirationMillis=0 to prevent stale cached values
- Add loading state handling for Network tab (Paging3)
- Update tests to work with new ChatsState/ContactsState wrappers

Closes #341

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@torlando-tech torlando-tech force-pushed the fix/loading-states-for-tabs branch from 42de5dd to a9c514a Compare January 29, 2026 00:52
@torlando-tech torlando-tech merged commit a2e4ae8 into main Jan 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add loading screens instead of telling something is empty while its been loading it

2 participants