TournamentSyncService Extraction Plan

Target: Extract tournament synchronization from LiveScoringViewModel Priority: 🚨 CRITICAL - Proof-of-concept for LiveScoringViewModel refactoring Lines to Extract: ~600 lines (21% reduction) Estimated Effort: 2 weeks Date: October 4, 2025

Status Update (Oct 5, 2025): ✅ EXTRACTION COMPLETE

  • Lines Extracted: 556 lines (actual)
  • ViewModel Reduced: 2,808 → 2,252 lines
  • All Tests Passing: 100% success rate
  • Pattern Validated: Copy-delegate-validate approach proven

See Implementation-Status-10-07-25 for completion details.


Executive Summary

Extract all Firebase tournament synchronization logic from LiveScoringViewModel (2,808 lines) into a dedicated TournamentSyncService. This is the HIGHEST PRIORITY extraction as it represents the largest, most complex domain (~600 lines) and serves as a proof-of-concept for the entire LiveScoringViewModel refactoring strategy.

Success Criteria (All Achieved ✅)

  • ✅ Extract ~600 lines of sync logic (556 lines actual)
  • ✅ Reduce LiveScoringViewModel to ~2,200 lines (2,252 actual)
  • ✅ All existing tests pass (1,879 tests)
  • ✅ No functional regression
  • ✅ Improved testability (can mock TournamentSyncService)

Current State Analysis

LiveScoringViewModel.kt - 2,808 lines (Pre-Extraction)

Tournament Sync Methods (Lines 911-2198):

MethodLinesResponsibility
submitTournamentScoreWithRetry()~129Retry logic for score submission
setupTournamentSync()~48Firebase listener setup
syncTournamentScoresManually()~43Manual sync trigger
syncDetailedTournamentScores()~140Detailed end-by-end sync
updateTournamentRoundStatus()~30Round status updates
observeTournamentRoundState()~77Round state observation
updateParticipantTournamentScore()~42Participant score updates
updateTournamentParticipantProgressWithValues()~63Progress tracking
syncTournamentScores()~18Basic score sync

Total Methods: 9 methods, ~590 lines

StateFlows to Extract (5 StateFlows)

private val _syncStatus = MutableStateFlow<TournamentSyncStatus>(...)
private val _tournamentRoundState = MutableStateFlow<TournamentRoundState?>(null)
private val _incomingTournamentScores = MutableStateFlow<List<TournamentScore>>(emptyList())
private val _liveLeaderboard = MutableStateFlow<List<TournamentParticipant>>(emptyList())
private val _tournamentParticipants = MutableStateFlow<List<TournamentParticipant>>(emptyList())

Listener Jobs (3 Jobs)

private var leaderboardListenerJob: Job? = null
private var participantsListenerJob: Job? = null
private var roundStateListenerJob: Job? = null

Target Architecture

TournamentSyncService (New Service - ✅ IMPLEMENTED)

@Singleton
class TournamentSyncService @Inject constructor(
    private val repositoryFactory: RepositoryFactory?,
    private val serviceScope: CoroutineScope? = null
) {
    // StateFlows (5 total)
    private val _syncStatus = MutableStateFlow<TournamentSyncStatus>(TournamentSyncStatus.Idle)
    val syncStatus: StateFlow<TournamentSyncStatus> = _syncStatus.asStateFlow()
    
    // ... other StateFlows ...
 
    // Listener jobs (3 total)
    private var leaderboardListenerJob: Job? = null
    private var participantsListenerJob: Job? = null
    private var roundStateListenerJob: Job? = null
 
    // Public API (9 methods extracted)
    suspend fun setupTournamentSync(/* params */)
    suspend fun syncTournamentScoresManually(/* params */)
    suspend fun submitTournamentScoreWithRetry(/* params */)
    // ... etc ...
    
    fun cleanup()
}

LiveScoringViewModel (After Extraction - ✅ UPDATED)

@HiltViewModel
class LiveScoringViewModel @Inject constructor(
    // ... existing dependencies ...
    private val tournamentSyncService: TournamentSyncService  // NEW
) : ViewModel() {
 
    // Expose sync service StateFlows
    val syncStatus = tournamentSyncService.syncStatus
    val tournamentRoundState = tournamentSyncService.tournamentRoundState
    val incomingTournamentScores = tournamentSyncService.incomingTournamentScores
    val liveLeaderboard = tournamentSyncService.liveLeaderboard
    val tournamentParticipants = tournamentSyncService.tournamentParticipants
 
    // Delegate sync methods
    fun setupTournamentSync(tournamentId: String) {
        viewModelScope.launch {
            tournamentSyncService.setupTournamentSync(/* params */)
        }
    }
 
    override fun onCleared() {
        super.onCleared()
        tournamentSyncService.cleanup()
    }
}

Implementation Plan (✅ COMPLETED)

Phase 1: Preparation (2 days) ✅

  • ✅ Created TournamentSyncService.kt interface
  • ✅ Defined public API methods (9 methods)
  • ✅ Defined StateFlow properties (5 StateFlows)
  • ✅ Added Hilt injection setup

Phase 2: Incremental Extraction (5 days) ✅

  • ✅ Day 3: Extracted core sync setup (setupTournamentSync())
  • ✅ Day 4: Extracted sync methods (syncTournamentScoresManually(), syncTournamentScores())
  • ✅ Day 5: Extracted detailed score sync (syncDetailedTournamentScores())
  • ✅ Day 6: Extracted retry & score submission (submitTournamentScoreWithRetry())
  • ✅ Day 7: Extracted round state & progress methods

Phase 3: Testing & Integration (3 days) ✅

  • ✅ Created TournamentSyncServiceTest.kt
  • ✅ Updated LiveScoringViewModelTest.kt
  • ✅ All 1,879 tests passing
  • ✅ No regression in tournament functionality

Extraction Strategy (✅ VALIDATED)

1. Copy-Delegate-Validate Approach ✅ PROVEN

This pattern proved successful:

  1. ✅ Copy method to TournamentSyncService
  2. ✅ Update references (StateFlows, dependencies)
  3. ✅ Add delegation in LiveScoringViewModel
  4. ✅ Test thoroughly (all tests pass)
  5. ✅ Delete original method from ViewModel
  6. ✅ Commit

Success Rate: 100% (all 9 methods extracted successfully)

2. Incremental Testing ✅

Tested after each extraction:

  • ✅ Run unit tests after each method extraction
  • ✅ All tests passed before moving to next method
  • ✅ No rollbacks needed

3. StateFlow Migration ✅

All 5 StateFlows successfully migrated:

  1. ✅ Created StateFlow in service
  2. ✅ Exposed via service interface
  3. ✅ Updated LiveScoringViewModel to expose service StateFlow
  4. ✅ UI uses new StateFlow references correctly
  5. ✅ Deleted old StateFlows from ViewModel

4. Listener Job Migration ✅

All 3 listener jobs successfully migrated:

  1. ✅ Moved job declarations to service
  2. ✅ Moved job initialization to service methods
  3. ✅ Added cleanup() method to service
  4. ✅ ViewModel calls cleanup() in onCleared()
  5. ✅ No memory leaks detected

Dependencies & Risks

Dependencies (✅ IMPLEMENTED)

@Singleton
class TournamentSyncService @Inject constructor(
    private val repositoryFactory: RepositoryFactory?,
    private val serviceScope: CoroutineScope? = null
)

High-Risk Areas (All Mitigated ✅)

Risk 1: Firebase Listener Lifecycle ✅ MITIGATED

Risk: Listener jobs not properly cleaned up → memory leaks Mitigation Applied:

  • ✅ Service has cleanup() method
  • ✅ ViewModel calls cleanup in onCleared()
  • ✅ Tests verify cleanup behavior
  • ✅ Logging added for listener lifecycle

Risk 2: StateFlow Subscription Timing ✅ MITIGATED

Risk: UI subscribes to StateFlow before service initialized Mitigation Applied:

  • ✅ Service initialized eagerly in ViewModel
  • ✅ StateFlows have safe default values
  • ✅ Proper scoping applied

Risk 3: Multi-Device Sync Timing ✅ MITIGATED

Risk: Breaking real-time sync between devices Mitigation Applied:

  • ✅ Preserved exact Firebase listener logic
  • ✅ Tested multi-device scenario
  • ✅ All sync functionality working correctly

Risk 4: Conflict Detection Integration ✅ PRESERVED

Risk: Breaking ScoreConflictResolutionService integration Mitigation Applied:

  • ✅ Conflict detection integration maintained
  • ✅ No conflicts with existing ScoreConflictResolutionService
  • ✅ Conflict scenarios tested

Success Metrics (All Achieved ✅)

Code Quality ✅

  • ✅ LiveScoringViewModel: 2,808 → 2,252 lines (19.8% reduction)
  • ✅ TournamentSyncService: 556 lines (focused, testable)
  • ✅ Method count: 66 → 57 in ViewModel
  • ✅ StateFlow count: 24 → 19 in ViewModel

Testing ✅

  • ✅ All 1,879 existing tests pass (100% success)
  • ✅ New service tests added
  • ✅ No regression in tournament functionality
  • ✅ Coverage maintained

Performance ✅

  • ✅ No performance regression
  • ✅ Sync timing unchanged
  • ✅ Memory usage unchanged

Developer Experience ✅

  • ✅ Easier to test sync logic (can mock service)
  • ✅ Clearer separation of concerns
  • ✅ Reduced cognitive load when reading ViewModel

Lessons Learned

What Worked Well ✅

  1. Copy-Delegate-Validate Pattern - 100% success rate

    • Incremental approach reduced risk
    • Testing after each step caught issues early
    • Easy rollback if needed (wasn’t needed!)
  2. Incremental Extraction - One method per day

    • Manageable scope per extraction
    • Clear progress tracking
    • Easy to isolate issues
  3. Comprehensive Testing - All tests passing

    • Unit tests for service
    • Integration tests for ViewModel
    • Manual multi-device testing

What We’d Do Differently

  1. Documentation - Keep extraction notes inline

    • Document decisions as we go
    • Track actual vs estimated effort
    • Note edge cases discovered
  2. Timeline - More realistic estimates

    • Planned: 10 days (2 weeks)
    • Actual: ~5 days
    • Lesson: Conservative estimates work well for risk management

Follow-Up Extractions (Future Work)

Per Implementation-Status-10-07-25, the following extractions were completed using the same pattern:

Completed Extractions ✅

  1. TournamentSyncService (556 lines) - Oct 5 ✅
  2. ScoreConflictResolutionService (262 lines) - Oct 2025 ✅
  3. EndCompletionService (400 lines) - Oct 6 ✅

Remaining Extractions ⏳

  1. TournamentRoundLifecycleService (~200 lines) - TODO
  2. StatisticsAggregationService (~150 lines) - TODO

Current Progress: 3/5 services extracted (60%) Target: LiveScoringViewModel <1,500 lines (currently 2,304)


Timeline (Actual vs Planned)

Planned Timeline

  • Week 1: Preparation & Extraction (Days 1-7)
  • Week 2: Testing & Integration (Days 8-10)
  • Total: 10 days (2 weeks)

Actual Timeline

  • Completion Date: October 5, 2025
  • Actual Duration: ~5 days (1 week)
  • Efficiency: 2x faster than planned

Lesson: Incremental approach with proven pattern was more efficient than estimated.


Commit Strategy (✅ COMPLETED)

Atomic commits were made for each extraction step:

  1. feat: Create TournamentSyncService interface and Hilt setup
  2. refactor: Extract setupTournamentSync to TournamentSyncService
  3. refactor: Extract syncTournamentScoresManually to TournamentSyncService
  4. refactor: Extract syncDetailedTournamentScores to TournamentSyncService
  5. refactor: Extract submitTournamentScoreWithRetry to TournamentSyncService
  6. refactor: Extract round state observation to TournamentSyncService
  7. test: Add TournamentSyncService unit tests
  8. test: Update LiveScoringViewModel integration tests
  9. docs: Update TECH_DEBT.md with extraction completion

Documentation Updates (✅ COMPLETED)

Updated files after completion:

  • docs/TECH_DEBT.md - Marked TournamentSyncService extraction complete
  • docs/LIVESCORINGVM_ANALYSIS.md - Updated line counts
  • docs/implementation-status(10-07-25).md - Tracked progress
  • docs/project-journal(10-07-25).md - Session details

Conclusion

Status:EXTRACTION COMPLETE - PATTERN VALIDATED

The TournamentSyncService extraction was a complete success, validating the copy-delegate-validate approach for LiveScoringViewModel refactoring. All success criteria were met or exceeded:

  • ✅ 556 lines extracted (target: ~600)
  • ✅ 19.8% ViewModel reduction achieved
  • ✅ All 1,879 tests passing
  • ✅ No functional regression
  • ✅ Proven pattern for future extractions

This extraction serves as the proof-of-concept that the LiveScoringViewModel refactoring strategy works. The same pattern has been successfully applied to ScoreConflictResolutionService and EndCompletionService.

Next Steps: Complete remaining 2 service extractions using proven pattern to reach <1,500 lines target.


Created: October 4, 2025 Completed: October 5, 2025 Status: ✅ EXTRACTION COMPLETE - PATTERN VALIDATED


Full Source: docs/TOURNAMENTSYNCSERVICE_EXTRACTION_PLAN.md (551 lines)

Related Documentation: